flymoon

flymoon


  • 首页

  • 归档

数据库索引

发表于 2019-06-01 更新于 2019-11-05

在数据库设计中,索引是必不可少的。合理的索引设计可以提升查询的效率。在设计数据库索引的时候需要注意以下几点误区:

很多研发同学评估表数据量不是很大,比如就几千或几万行,如此小表查询不会导致性能问题,因此就不需要添加索引了。

如果数据库就几条这样的并发查询语句,那么大家的想法也没错,但是线上的情况异常复杂,往往评估非常不准确,或对异常情况考虑不足,并发查询量超出预期,业务接口超时报错导致故障。表数据量虽然不多,只有几千或几万行,但大并发下的全面扫描会导致系统CPU资源耗尽,资源争抢导致SQL执行时间变长,进而导致数据库资源池被占满,业务请求因获取连接超时失败而出现了雪崩。

因业务需求的调整,反应到数据库上可能就是一些查询条件的改变,需要对索引进行调整,有的同学选择了先删掉旧索引然后再创建新索引的方式。这种独立拆分方式的索引变更不具备原子性,在旧索引被删除后新索引建立前这期间,如果线上的查询刚好依赖这个旧索引,之前索引扫描的高效查询变成了全表扫描的低效查询,可能导致系统CPU资源耗尽,数据库资源池被占满,进而业务查询超时导致故障。

当 MySQL 中建立的联合索引, 只要索引中的某一列的值为空时(NULL),即便其他的字段完全相同,也不会引起唯一索引冲突,导致唯一约束失效,执行 insert 操作时,即使已经有了该数据,仍然会插入成功。MySQL 官方文档解释只有 BDB 类型的存储引擎支持包含 NULL values 的唯一约束,所以在其他引擎上这种情况发生的时候很容易导致数据冲突。

【最佳实践】
建议无论表的大小,都根据业务查询需求添加合适的索引,避免全表扫描;同时不滥用索引。

选择区分度高的字段建立索引。

多字段and查询时,根据业务查询特点和数据分布评估后需创建联合索引,则需满足最左前缀原则,同时区分度相对高的字段放在联合索引前面。

创建索引,推荐的原则:

  1. 能用单索引,不用联合索引;
  2. 能用窄索引,不用宽索引。
  3. 避免重复建索引,尽量复用已有索引,提高索引使用率。
  4. 尽量使用覆盖索引,无需回表查询,避免随机IO。

满足一定条件后,建议尽量使用前缀索引,用列的前缀代替整个列作为索引key。当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。

Join查询中连接字段建立索引,避免全表扫描。

对于范围,<>等非等值查询,只能利用索引的最左列,需要根据实际业务调整规避。

order/group by等语句的字段,适当添加索引可避免排序,如果是多列排序,需要所有列排序方向一致,才能利用索引。
建议给要加唯一索引的属性加一个非空限制或者提供默认值。

索引调整需要仔细评估,特别是删除索引这种会改变 SQL 执行计划的操作,对于索引的重建一定确保drop与add通过一条alter语句执行,删除旧索引并添加新索引,会自动完成转换。对于唯一索引调整,建议删掉唯一键约束但保留索引,做法就是drop唯一索引的同时add一个相同结构的普通索引,且drop和add通过一条alter语句执行,这样确保调整过程中及之后对SQL查询性能无影响。

TraceId 生成规则

发表于 2019-05-09

[TOC]

1. 背景介绍

在常用的日志系统中,都会生成一个唯一的标识,来标明本次请求的ID,所有具有一个实际业务含义的标识比UUID更加的具有可读性。

2. 实现说明

aaaaaaaatttttttttttttccccfpppp

TraceId的a段是8个字符的IP地址(IPv4的每个段用两位16进制数表示),t段是13个字符的生成TraceId的毫秒时间,c段是4位(1000-9999)的整数递增量。对于线上环境,a段IP地址使用的最前端负责生成TraceId的那台服务器的IP,使用a、t、c三段能从TraceId直接得到机器IP和生成时间,能够满足直接根据机器IP 扫描检索TraceId的需求;时间也有助于TraceId的分布足够分散的同时又可以按时间进行存储分区。c段的4位顺序数用于避免多线程并发时TraceId碰撞(可以保证在qps=9000000以下不碰撞)。a、t、c三段的TraceId是现在用的最多的方案,但这个方案并不完美,如果同一台机器上同时部署多个应用,就很容易出现TraceId碰撞的情况,为了支持多JVM实例环境,最新版的 TraceId 末尾又增加了一个扩展字符 f (一般用于排查问题,例如f 段是字符e,表示由nginx模块生成的TraceId)和进程号p段(用四位16进制数表示)。

缓存设计

发表于 2019-05-01 更新于 2019-11-05

缓存是系统设计中必不可少的一环,但很多时候缓存设计不当可能会导致一些问题,

比如:缓存穿透,是指每次查询,都走了从缓存,再穿透到DB这个过程,原因一般是数据在DB中不存在。一般的缓存使用流程是,先进行缓存查询,如果key不存在或者key已经过期,再对DB进行查询,并把查询到的对象,放进缓存。如果DB查询对象为空,则不放进缓存。如果查询一定不存在的对象,就会每次都去查询 DB,而每次查询都是空,每次又都不会进行缓存。假如有恶意攻击,就可以利用这个漏洞,对DB造成压力,甚至压垮DB。缓存设计时,应考虑缓存穿透保护,即针对缓存穿透问题采取有效措施,防止大量请求穿透缓存访问 DB 导致 DB 挂掉。

【最佳实践】

  1. 一般缓存使用,先进行缓存查询,如果 key 不存在或者 key 已经过期,再从 DB 进行查询,并把查询到的对象放进缓存;

    如果 DB 查询对象为空,也将空值写进缓存,只是设定的缓存过期时间较短,比如设置为 60 秒。当有新 key 产生的时候,对缓存进行清理。倘若请求为空的情况太多,导致缓存空数据占用内存空间太大,推荐使用布隆过滤器。即创建一个足够大的 bitmap,用于存储可能访问的 key,不存在的 key 直接被过滤。通过一致性 hash 来进行热点散列。

  2. 业务代码中,缓存层统一添加在数据层上,服务层下,禁止出现嵌套调用。

  3. 构建数据发生异常时,错误数据不放入缓存。
  4. 在任何需要远程调用的逻辑,都应该有恰当的缓存策略,特别是元数据的场景。

哈希表

发表于 2019-03-29

根据键key而直接访问内存存储位置的数据结构,它通过计算一个关于键值的函数,将所需要查询的数据映射到表中一个位置来访问记录。可以加快查找的速度。这个映射函数称做散列函数,存放记录的数组称做散列表

1.基本概念

2.构造散列函数

· 直接地址法
· 数字分析法
· 平方取中法
· 折叠法
· 除留余数法

向下转型和自动包装

发表于 2019-03-20

问题说明

在debug的时候遇到一个傻逼的问题,排查很长时间没有找到具体的原因。最后发现使用判等2这种方式进行判断的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean testNumEquals() {
Long num1 = 0L;
int num = 0;
// 判等1
System.out.println(num == num1);
// 判等2
System.out.printlb(num1.equals(0));
// 判等3
System.out.println(num1.equals(0L));
}

out:
true;
false;
true;

问题分析

在分析之前需要说明几个关键的名字自动拆箱,自动装箱,隐式类型转化 进行说明。

  • 自动装箱:

    将基本类型转化为该基本类型的实际包装类

    eg: int –> Integer

  • 自动拆箱

    将包装类型转化为该包装类的基本类型

1
2
3
4
5
6
7
8
9
public static void test() {
int a = 1;
Integer a1 = new Intger(1);
System.out.println(a == a1);
System.out.println(a1.equals(a));
}
out:
true;
true;

问题总结

  1. 进行== 操作的时候,会对Intger进行自动拆箱的操作,使用Integer.equals(int)进行比较的时候,会对int进行自动装箱的操作。
  2. 对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间
1
2
3
4
5
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
  1. 对于Integer i = 100;的操作等同于Ineger i = Integer.valueof(100);

Java异常体系结构

发表于 2019-03-20

Java异常体系结构

image

Throwable

Thorwable类所有异常和错误的超类,有两个子类Error和Exception,分别表示错误和异常

Exception

程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。
程序中应当尽可能去处理这些异常。

  • 运行时异常

    运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,
    这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,
    程序应该从逻辑角度尽可能避免这类异常的发生。

  • 非运行时异常

    非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。
    从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。
    如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

Error

Error是程序无法处理的错误,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,
Java虚拟机(JVM)一般会选择线程终止。

springboot获取不到配置

发表于 2019-03-20

问题现象

  • 使用
    1
    2
    3
    * 使用的代码从网上找的,大家都是这样写的,无任何特殊的配置

    ### 源码示例

es:
ip: 172.16.33.29
port: 9201
username: admin
password: 123

1
2


@Value("${es.ip}")
private String esIp;

@Value("${es.port}")
private String esHttpPort;

@Value("${es.username}")
private String esUserName;

@Value("$(es.password)")
private String esUserPw;

/** jestClient 实例化对象 */
private JestClient jestClient;

public EsInstance() {
    initJestClient();
}

/**
 * 初始化ES
 */
private void initJestClient() {
    String url = "http://" + esIp + ":" + esHttpPort;
    HttpClientConfig httpClientConfig = new HttpClientConfig.Builder(url)
            .defaultCredentials(esUserName, esUserPw)
            .build();
    CredentialsProvider credentialsProvider = httpClientConfig.getCredentialsProvider();
    HttpClientContext httpClientContextTemplate = HttpClientContext.create();
    httpClientContextTemplate.setCredentialsProvider(credentialsProvider);
    JestHttpClient clientWithMockedHttpClient;
    JestClientFactory factory = new JestClientFactory();
    factory.setHttpClientConfig(httpClientConfig);
    clientWithMockedHttpClient = (JestHttpClient) factory.getObject();
    jestClient = clientWithMockedHttpClient;
}

`

  • 此处获取的相关属性都是空的

问题原因

  • Spring在扫描注解的时候,当扫描到会进行new对象的操作,因为此类里面有默认无参构造器,所以会直接初始化initJestClient方法,但是此时属性注解还是没有注入进去,所以获取的值就是空的!!!

解决方案

  • 当执行完Spring对象初始化的时候,才走这个初始化的方法

问题反思

  • 原来那样写,是因为使用的是工具类获取,工具类已经缓存到所有的属性,在bean初始化之前就已经缓存好了

生命周期

  • Spring Bean的生命周期

    image

Maven scope的几种类型

发表于 2019-03-14

1 问题背景

  • 在处理单元测试的时候,多个模块需要处理相同的单元测试逻辑,所以就将重复的逻辑给抽象出来,组成一个base,让各个基类实现各自的内容。

2 问题现象

  • 但是集成的过程中发现始终import不进去base jar里面的内容

3 问题定位

  • 最终定位是pom.xml依赖这个jar,他的scope是test,也就是说只有test-source才会import这个jar,正常的src是依赖不上的。

4 问题总结

  • 本次问题还是对mvn的scope不太熟悉导致的。所以就将maven的几种scope给列出来,复习一下。

  • scope

    依赖范围控制哪些依赖在哪些classpath 中可用,哪些依赖包含在一个应用中。

  • compile (编译范围)

    compile是默认的范围;如果没有提供一个范围,那该依赖的范围就是编译范围。编译范围依赖在所有的classpath 中可用,同时它们也会被打包。

  • provided (已提供范围)

    provided 依赖只有在当JDK 或者一个容器已提供该依赖之后才使用。例如, 如果你开发了一个web 应用,你可能在编译 classpath 中需要可用的Servlet API 来编译一个servlet,但是你不会想要在打包好的WAR 中包含这个Servlet API;这个Servlet API JAR 由你的应用服务器或者servlet 容器提供。已提供范围的依赖在编译classpath (不是运行时)可用。它们不是传递性的,也不会被打包。

  • runtime (运行时范围)

    runtime 依赖在运行和测试系统的时候需要,但在编译的时候不需要。比如,你可能在编译的时候只需要JDBC API JAR,而只有在运行的时候才需要JDBC驱动实现。

  • test (测试范围)

    test范围依赖 在一般的编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用。

  • system (系统范围)

    system范围依赖与provided 类似,但是你必须显式的提供一个对于本地系统中JAR 文件的路径。这么做是为了允许基于本地对象编译,而这些对象是系统类库的一部分。这样的构件应该是一直可用的,Maven 也不会在仓库中去寻找它。如果你将一个依赖范围设置成系统范围,你必须同时提供一个 systemPath 元素。注意该范围是不推荐使用的(你应该一直尽量去从公共或定制的 Maven 仓库中引用依赖)。

  • scope的依赖传递

    A–>B–>C。当前项目为A,A依赖于B,B依赖于C。知道B在A项目中的scope,那么怎么知道C在A中的scope呢?答案是: 当C是test或者provided时,C直接被丢弃,A不依赖C; 否则A依赖C,C的scope继承于B的scope。

Java List中的clear 和 removeAll性能比较

发表于 2019-03-14

问题背景

有一个需求的场景就是,有个庞大的

1
2

计划每做一次写入的操作,对```List```进行一次清空的操作

public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}
1
2


public boolean removeAll(Collection<?> c) {
return batchRemove(c, false);
}

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
            if (c.contains(elementData[r]) == complement)
                elementData[w++] = elementData[r];
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}   

`

线程池

发表于 2019-03-14

[TOC]

优势

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的使用

1 线程池的创建

通过ThreadPoolExecutor来创建一个线程池。

1
2
new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

  • corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
  • runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

    1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

    2. LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

    3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

    4. PriorityBlockingQueue:一个具有优先级得无限阻塞队列。
  • maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
  • ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。

  • RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。n AbortPolicy:直接抛出异常。

  • keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

  • TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

2 线程池的关闭

  • 调用线程池的shutdown

    1. 将线程池的状态设置成SHUTDOWN状态
    2. 中断所有没有正在执行任务的线程
  • 调用线程池的shutdownNow

    1. 遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程
    2. 无法响应中断的任务可能永远无法终止
    3. 将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

线程池的分析

流程分析:线程池的主要工作流程如下图:
线程池分析

  1. 首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
  2. 其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
  3. 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

合理的配置线程池

要想合理的配置线程池,就必须首先分析任务特性

  1. 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
  2. 任务的优先级:高,中和低。
  3. 任务的执行时间:长,中和短。
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。

1 任务的性质

  • CPU密集型任务

    要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。

    任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低

    配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池

  • IO密集型任务

    涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成

    等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。

  • 混合型的任务

    如果两个任务执行的时间相差不是太大,可以拆分成CPU密集型任务和一个IO密集型任务

    并行执行的吞吐率要高于串行执行的吞吐率

2 任务的优先级

  • 使用优先级队列PriorityBlockingQueue

    它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

3 任务的执行时间

  • 执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。

4 任务的依赖性

  • 依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。

线程池的监控

通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用

  1. taskCount:线程池需要执行的任务数量。

  2. completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。

  3. largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。

  4. getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。

  5. getActiveCount:获取活动的线程数。

扩展线程池

通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。

ES

发表于 2019-03-14

[TOC]

1 介绍

  • 分布式 可扩展 实时的搜索引擎
  • 接近实时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    * 支持多种客户端
    * 强大的统计功能
    * ```RESTful``` ```Api``` 接口

    ## 2 使用场景
    * 全文检索
    * 结构化检索
    * 数据分析
    * ```NoSQL``` ```Json``` 文档数据库
    ### 2.1 材料分析使用场景
    * 文档数据存储(存储文本的结构化数据)
    * 文本搜索(关键字搜索)
    * 数据统计(用户使用习惯统计)

    ## 3 如何部署
    ### 3.1 服务端
    支持linux和window平台部署
    * 硬件要求
    > 内存要求比较高,排序和聚合比较好内存

    > 硬盘尽可能快

    > cpu要求不高,核数多,支持的并发就多
    * 操作系统
    > 推荐部署在linux下。 (磁盘阵列)???
    * JDK
    > JDK 1.7 +
    * 部署配置说明
    > ```1.7.5``` 部署说明

cluster.name: clfx-test
配置es的集群名称,默认是elasticsearch,es会自动发现在同一网段下的es,如果在同一网段下有多个集群,就可以用这个属性来区分不同的集群。
node.name: “data-0”
节点名,默认随机指定一个name列表中名字,该列表在es的jar包中config文件夹里name.txt文件中,其中有很多作者添加的有趣名字。
node.master: true
指定该节点是否有资格被选举成为node,默认是true,es是默认集群中的第一台机器为master,如果这台机挂了就会重新选举master。
node.data: true
指定该节点是否存储索引数据,默认为true。
index.number_of_shards: 5
设置默认索引分片个数,默认为5片。
index.number_of_replicas: 1
设置默认索引副本个数,默认为1个副本。
path.conf: /path/to/conf
设置配置文件的存储路径,默认是es根目录下的config文件夹。
path.data: /path/to/data
设置索引数据的存储路径,默认是es根目录下的data文件夹,可以设置多个存储路径,用逗号隔开,例:
path.data: /path/to/data1,/path/to/data2
path.work: /path/to/work
设置临时文件的存储路径,默认是es根目录下的work文件夹。
path.logs: /path/to/logs
设置日志文件的存储路径,默认是es根目录下的logs文件夹
path.plugins: /path/to/plugins
设置插件的存放路径,默认是es根目录下的plugins文件夹
bootstrap.mlockall: true
设置为true来锁住内存。因为当jvm开始swapping时es的效率会降低,所以要保证它不swap,可以把ES_MIN_MEM和 ES_MAX_MEM两个环境变量设置成同一个值,并且保证机器有足够的内存分配给es。同时也要允许elasticsearch的进程可以锁住内存,linux下可以通过ulimit -l unlimited命令。
network.bind_host: 192.168.0.1
设置绑定的ip地址,可以是ipv4或ipv6的,默认为0.0.0.0。
network.publish_host: 192.168.0.1
设置其它节点和该节点交互的ip地址,如果不设置它会自动判断,值必须是个真实的ip地址。
network.host: 192.168.0.1
这个参数是用来同时设置bind_host和publish_host上面两个参数。
transport.tcp.port: 9300
设置节点间交互的tcp端口,默认是9300。
transport.tcp.compress: true
设置是否压缩tcp传输时的数据,默认为false,不压缩。
http.port: 9200
设置对外服务的http端口,默认为9200。
http.max_content_length: 100mb
设置内容的最大容量,默认100mb
http.enabled: false
是否使用http协议对外提供服务,默认为true,开启。
gateway.type: local
gateway的类型,默认为local即为本地文件系统,可以设置为本地文件系统,分布式文件系统,Hadoop的HDFS,和amazon的s3服务器。
gateway.recover_after_nodes: 1
设置集群中N个节点启动时进行数据恢复,默认为1。
gateway.recover_after_time: 5m
设置初始化数据恢复进程的超时时间,默认是5分钟。
gateway.expected_nodes: 2
设置这个集群中节点的数量,默认为2,一旦这N个节点启动,就会立即进行数据恢复。
cluster.routing.allocation.node_initial_primaries_recoveries: 4
初始化数据恢复时,并发恢复线程的个数,默认为4。
cluster.routing.allocation.node_concurrent_recoveries: 2
添加删除节点或负载均衡时并发恢复线程的个数,默认为4。
indices.recovery.max_size_per_sec: 0
设置数据恢复时限制的带宽,如入100mb,默认为0,即无限制。
indices.recovery.concurrent_streams: 5
设置这个参数来限制从其它分片恢复数据时最大同时打开并发流的个数,默认为5。
discovery.zen.minimum_master_nodes: 1
设置这个参数来保证集群中的节点可以知道其它N个有master资格的节点。默认为1,对于大的集群来说,可以设置大一点的值(2-4)
discovery.zen.ping.timeout: 3s
设置集群中自动发现其它节点时ping连接超时时间,默认为3秒,对于比较差的网络环境可以高点的值来防止自动发现时出错。
discovery.zen.ping.multicast.enabled: false
设置是否打开多播发现节点,默认是true。
discovery.zen.ping.unicast.hosts: [“host1”, “host2:port”, “host3[portX-portY]”]
设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点。

1
2
3
4
* 分词插件
> IK分词

```http://172.16.33.29:9201/_analyze?text=%E6%9D%90%E6%96%99%E5%88%86%E6%9E%90

3.2 客户端

客户端采用1.7.5

  • Java

    1
    2
    3
    4
    5
    6
    Settings settings = ImmutableSettings.settingsBuilder()
    .put("cluster.name", clusterName)
    .put("client.transport.sniff", true).build();
    Esclient esClient = new TransportClient(settings)
    .addTransportAddress(new InetSocketTransportAddress(ip,
    port));
  • HTTP

1
2
3
4
5
6
7
8
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}

4 存储结构

4.1 document

  • 文档

    一个文档是可以被索引的最基本的单位。
    文档相当于关系型数据库中的row,也就是一行行的数据。
    例如一个用户可以是一个文档,一个产品可以是一个文档,一条订单记录可以是一个文档。一般用作json格式来展示。

4.2 index

  • 索引

    index 是一些列具有相同特性的文档的集合。类似于关系型数据库中的库。
    举例说明,你可以为客户数据创建索引,可以为产品目录创建索引,也可以为其他任何数据创建索引。
    需要注意的是,index name 必须为小写字母 ,通常索引的名称会用在索引数据,搜索,更新或者删除数据的地方。

4.3 type

  • 类型

    在一个索引中,你可以定义多个 type ,type 是索引中的逻辑分类。当然这个type的意义完全取决于你。
    type 相当于关系型数据数据库中的表。
    举例来说,在一个博客系统中,你可以定义一个 user type,可以定义一个 blog type,还可以定义一个 comment type。

4.4 Cluser

  • 聚集

    cluser 也称聚集,是由一个或者多个 node 组成的集合,保存了你所有的数据并且为所有节点提供索引及搜索的能力。cluser 的名称唯一。在聚集中,你可以有一个或者多个 node

    4.5 Node

  • 节点

    一个 node 是一个单个服务器,属于 cluser 的一部分,提供了索引及搜索的能力。和 cluser 一样, node 也是通过名字来确保唯一性的。当 node 启动时,会生成一个随机的字符作为 node 的名字。
    node 可以通过配置进而加入到特定的 cluser 中。默认的话会被加入到 elasticsearch 这个 cluster。

4.6 Shards

  • 碎片

    一个索引可能会存储大量的数据,进而会让单个节点超出硬件能承受范围。举例来说,存储了10亿文档的单个节点,会占用1TB磁盘空间,并且会导致查询的时候速度很慢。
    为了解决这个问题,Elasticsearch 提供了 碎片 也就是 shards 对 index 进行划分成更小的部分。 当你创建 index 的时候,你可以简单地指定你想要的碎片数量。每一个碎片具有和 index 完全相同的功能。

    碎片最主要的两个作用是:

    它允许你水平地切割你的容量体积,它允许你并行地分发作业,提高系统的性能

4.7 Replicas

  • 副本

    因为各种原因,所以数据丢失等问题会时有发生,碎片也可能会丢失,为了防止这个问题,所以你可以将一个或多个索引碎片复制到所谓的复制碎片,简称为副本

    副本最主要的两个作用是:

    它提供了高可用性,以防碎片/节点失败。之于这点,所以副本的永远不要和原始碎片分布在同一个节点上。
    它可以扩展系统的吞吐量,因为搜索可以在所有副本执行。=
    总的来说,每个 index 可以被分布到不同的碎片中,节点 可以有0个或者多个副本。一旦复制了,那么 index 就拥有一个主要的碎片和一个复制的碎片。碎片的数量和副本的数量可以在创建索引之前定义,在索引创建之后,你可以动态地改变副本的数量,但是却不能改变碎片的数量。
    默认情况下,Elasticsearch 为每个索引分配了5个主碎片和1个副本,这意味着在你的集群中,如果至少有两个节点,那么每个索引将有5个主碎片和5个复制碎片,总共10碎片/索引。

5 API

5.1 增

1
2
3
4
5
JSONObject json = new JSONObject();
json.put("name", "张三2222");
json.put("sex", "男");
json.put("age", "25");
instance.getElasticWriter().index("lixiangyangtest", "qaht_type_wsinfo", "1113333333333333", json.toJSONString(), true);

5.2 删

1
2
esInstance.getElasticWriter().delete("qaht_index_backfill", "clfx_qaht_type_backfill_field",
hit.getId());

5.3 改

1
查完在存储

5.4 查

1
2
GetResponse response = esinstace.getElasticReader().searchByID(index, wstype, cBh);
String jsonStr = response.getSourceAsString();

6 开发建议

6.1 索引

  • 功能描述_index_v版本号
  • 所有索引必须要创建别名,创建别名之后不能修改
  • 时间类型用long类型的存储
  • 最好index里面只创建一个type
  • 创建索引时,必须要创建mapping,指定好每一个字段的属性

6.2 搜索Api

  • org.elasticsearch.index.query.QueryBuilders.termQuery(String, String)

term是代表完全匹配,即不进行分词器分析,文档中必须包含整个搜索的词汇,但是ES存储field的分词属性不能设置

  • matchPhraseQuery
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "query": {
    "match_phrase": {
    "content" : {
    "query" : "我的宝马多少马力"
    }
    }
    }
    }

完全匹配

  • matchQuery
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "query": {
    "match": {
    "content" : {
    "query" : "我的宝马多少马力"
    }
    }
    }
    }

上面的查询匹配就会进行分词,比如”宝马多少马力”会被分词为”宝马 多少 马力”, 所有有关”宝马 多少 马力”, 那么所有包含这三个词中的一个或多个的文档就会被搜索出来。

  • boolQuery

    must 相当于SQl里面的AND

    should 相当于SQL里面的OR

7 问题

  • 重迁索引

  • 分布式事务

    对单个文件的变更是可以支持事务的

    但是包含多个文档的变更不支持

  • 版本升级

    进行数据迁移,数据量大怎么迁移

自我保护

发表于 2019-01-01 更新于 2019-07-18

系统的处理能力都有一个上限,这个可能是受硬件能力限制,如其所在的服务器硬件性能,网络带宽能力;

可能是受下游系统的能力限制,如后端连接的数据库并发能力;也可能是为了保护自己的核心功能。

系统自保护是一个非常重要的功能,它可以防止单点问题扩大化、瞬时问题长尾化,以及非关键问题灾难化。

业务系统调用各种其他系统时应该有面向失败的设计,不要无条件的相信其他系统,设计一套自己的容错方案。系统设计的一个重要原则是认为所有的依赖都是不靠谱的,基于此原则做依赖管理。

【最佳实践】

  1. 通过 TCP 滑动窗口和拥塞控制等机制来缓解请求拥塞的情况。
  2. 服务端要借助限流组件,合理地配置限流规则,来应对突发的大流量,防止被流量洪峰打垮。
  3. 客户端对不稳定或超长时间的调用进行自动熔断,配置合理的超时时间,防止被不稳定下游服务阻塞而导致级联失败。
  4. 限流是为了减少业务流量,降低系统压力。限流的处理逻辑应简单快速,不应加剧系统开销
123

flymoon

总有一天,那些让你难过的事情,你都能笑着说出来。
27 日志
4 标签
© 2019 flymoon
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Muse v7.2.0