CentOS7安装Elasticsearch7

CentOS7安装Elasticsearch7下载地址:https://www.elastic.co/cn/dow...使用YUM安装# 下载并安装公共签名密钥rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch# 配置RPM仓库vim /etc/yum.repos.d/elasticsearch.repo[elasticsearch-7.x]name=Elasticsearch repository for 7.x packagesbaseurl=https://artifacts.elastic.co/packages/7.x/yum# Apache 2.0 license#baseurl=https://artifacts.elastic.co/packages/oss-7.x/yumgpgcheck=1gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearchenabled=1autorefresh=1type=rpm-mdyum install -y elasticsearch配置服务启动服务之前一定要先配置/etc/elasticsearch/elasticsearch.yml的network.host、http.port和cluster.initial_master_nodes。 firewall-cmd --zone=public --add-port=9200/tcp --permanentfirewall-cmd --reloadsudo /bin/systemctl daemon-reloadsudo /bin/systemctl enable elasticsearch.service# 启动停止服务sudo systemctl start elasticsearchsudo systemctl stop elasticsearch测试服务[root@localhost ~]# curl "http://127.0.0.1:9200/"{ "name" : "localhost.localdomain", "cluster_name" : "elasticsearch", "cluster_uuid" : "Pxdp0Z24SJ-MIBH_2oMe2A", "version" : { "number" : "7.1.1", "build_flavor" : "default", "build_type" : "rpm", "build_hash" : "7a013de", "build_date" : "2019-05-23T14:04:00.380842Z", "build_snapshot" : false, "lucene_version" : "8.0.0", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search"}常用文件# 配置文件vim /etc/elasticsearch/elasticsearch.yml# JVM配置vim /etc/elasticsearch/jvm.options# 启动日志tail -n 10 -f /var/log/elasticsearch/elasticsearch.log问题处理绑定IP和跨域vim /etc/elasshellticsearch/elasticsearch.yml# 允许任意IP访问network.host: 0.0.0.0# 修改开放的端口http.port: 9200# 最后添加跨域http.cors.enabled: truehttp.cors.allow-origin: "*"启动失败启动报错信息如下:the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configuredvim /etc/elasticsearch/elasticsearch.yml# 修改【#cluster.initial_master_nodes: ["node-1", "node-2"] 】cluster.initial_master_nodes: ["node-1"]进程虚拟内存不足vim /etc/sysctl.conf# 添加vm.max_map_count=262144# 保存后执行sysctl -pRPM目录类型描述默认位置设置homeElasticsearch主目录或 $ES_HOME/usr/share/elasticsearch bin二进制脚本,包括elasticsearch启动节点和elasticsearch-plugin安装插件/usr/share/elasticsearch/bin conf配置文件elasticsearch.yml/etc/elasticsearchES_PATH_CONFconf环境变量,包括堆大小,文件描述符/etc/sysconfig/elasticsearc data节点上分配的每个索引、分片的数据文件的位置。可以容纳多个位置。/var/lib/elasticsearchpath.datalogs日志文件位置/var/log/elasticsearchpath.logsplugins插件文件位置。每个插件都将包含在一个子目录中。/usr/share/elasticsearch/plugins repo共享文件系统存储库位置。可以容纳多个位置。文件系统存储库可以放在此处指定的任何目录的任何子目录中。Not configuredpath.repo配置ElasticsearchElasticsearch默认使用/etc/elasticsearch运行时配置。此目录的所有权以及此目录中的所有文件在安装时都设置为root:elasticsearch,并且目录设置了setgid标志,以便在/etc/elasticsearch下创建的所有文件和子目录,例如使用密钥库创建密钥库工具等。 ...

June 11, 2019 · 3 min · jiezi

聊聊Elasticsearch的RunOnce

序本文主要研究一下Elasticsearch的RunOnce RunOnceelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/RunOnce.java public class RunOnce implements Runnable { private final Runnable delegate; private final AtomicBoolean hasRun; public RunOnce(final Runnable delegate) { this.delegate = Objects.requireNonNull(delegate); this.hasRun = new AtomicBoolean(false); } @Override public void run() { if (hasRun.compareAndSet(false, true)) { delegate.run(); } } /** * {@code true} if the {@link RunOnce} has been executed once. */ public boolean hasRun() { return hasRun.get(); }}RunOnce实现了Runnable接口,它的构造器要求输入Runnable,同时构造了hasRun变量;run方法会先使用compareAndSet将hasRun由false设置为true,如果成功则执行代理的Runnable的run方法;hasRun方法则返回hasRun值实例elasticsearch-7.0.1/server/src/test/java/org/elasticsearch/common/util/concurrent/RunOnceTests.java public class RunOnceTests extends ESTestCase { public void testRunOnce() { final AtomicInteger counter = new AtomicInteger(0); final RunOnce runOnce = new RunOnce(counter::incrementAndGet); assertFalse(runOnce.hasRun()); runOnce.run(); assertTrue(runOnce.hasRun()); assertEquals(1, counter.get()); runOnce.run(); assertTrue(runOnce.hasRun()); assertEquals(1, counter.get()); } public void testRunOnceConcurrently() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(0); final RunOnce runOnce = new RunOnce(counter::incrementAndGet); final Thread[] threads = new Thread[between(3, 10)]; final CountDownLatch latch = new CountDownLatch(1); for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { try { latch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } runOnce.run(); }); threads[i].start(); } latch.countDown(); for (Thread thread : threads) { thread.join(); } assertTrue(runOnce.hasRun()); assertEquals(1, counter.get()); } public void testRunOnceWithAbstractRunnable() { final AtomicInteger onRun = new AtomicInteger(0); final AtomicInteger onFailure = new AtomicInteger(0); final AtomicInteger onAfter = new AtomicInteger(0); final RunOnce runOnce = new RunOnce(new AbstractRunnable() { @Override protected void doRun() throws Exception { onRun.incrementAndGet(); throw new RuntimeException("failure"); } @Override public void onFailure(Exception e) { onFailure.incrementAndGet(); } @Override public void onAfter() { onAfter.incrementAndGet(); } }); final int iterations = randomIntBetween(1, 10); for (int i = 0; i < iterations; i++) { runOnce.run(); assertEquals(1, onRun.get()); assertEquals(1, onFailure.get()); assertEquals(1, onAfter.get()); assertTrue(runOnce.hasRun()); } }}testRunOnce方法验证了顺序多次执行runOnce的场景;testRunOnceConcurrently方法则验证了并发多次执行runOnce的场景;testRunOnceWithAbstractRunnable则验证了使用AbstractRunnable作为runnable的场景小结RunOnce实现了Runnable接口,它的构造器要求输入Runnable,同时构造了hasRun变量;run方法会先使用compareAndSet将hasRun由false设置为true,如果成功则执行代理的Runnable的run方法;hasRun方法则返回hasRun值 ...

June 10, 2019 · 2 min · jiezi

聊聊Elasticsearch的SingleObjectCache

序本文主要研究一下Elasticsearch的SingleObjectCache SingleObjectCacheelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/SingleObjectCache.java public abstract class SingleObjectCache<T>{ private volatile T cached; private Lock refreshLock = new ReentrantLock(); private final TimeValue refreshInterval; protected long lastRefreshTimestamp = 0; protected SingleObjectCache(TimeValue refreshInterval, T initialValue) { if (initialValue == null) { throw new IllegalArgumentException("initialValue must not be null"); } this.refreshInterval = refreshInterval; cached = initialValue; } /** * Returns the currently cached object and potentially refreshes the cache before returning. */ public T getOrRefresh() { if (needsRefresh()) { if(refreshLock.tryLock()) { try { if (needsRefresh()) { // check again! cached = refresh(); assert cached != null; lastRefreshTimestamp = System.currentTimeMillis(); } } finally { refreshLock.unlock(); } } } assert cached != null; return cached; } /** Return the potentially stale cached entry. */ protected final T getNoRefresh() { return cached; } /** * Returns a new instance to cache */ protected abstract T refresh(); /** * Returns <code>true</code> iff the cache needs to be refreshed. */ protected boolean needsRefresh() { if (refreshInterval.millis() == 0) { return true; } final long currentTime = System.currentTimeMillis(); return (currentTime - lastRefreshTimestamp) > refreshInterval.millis(); }}SingleObjectCache的构造器要求refreshInterval、initialValue两个参数;它提供了getOrRefresh方法,该方法会判断该cached值是否过期,如果过期则调用refresh方法刷新值,同时更新lastRefreshTimestamp,如果没过期则返回cached值实例elasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/fs/FsService.java ...

June 9, 2019 · 2 min · jiezi

聊聊Elasticsearch的LazyInitializable

序本文主要研究一下Elasticsearch的LazyInitializable LazyInitializableelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/LazyInitializable.java public final class LazyInitializable<T, E extends Exception> { private final CheckedSupplier<T, E> supplier; private final Consumer<T> onGet; private final Consumer<T> onReset; private volatile T value; /** * Creates the simple LazyInitializable instance. * * @param supplier * The {@code CheckedSupplier} to generate values which will be * served on {@code #getOrCompute()} invocations. */ public LazyInitializable(CheckedSupplier<T, E> supplier) { this(supplier, v -> {}, v -> {}); } /** * Creates the complete LazyInitializable instance. * * @param supplier * The {@code CheckedSupplier} to generate values which will be * served on {@code #getOrCompute()} invocations. * @param onGet * A {@code Consumer} which is called on each value, newly forged or * stale, that is returned by {@code #getOrCompute()} * @param onReset * A {@code Consumer} which is invoked on the value that will be * erased when calling {@code #reset()} */ public LazyInitializable(CheckedSupplier<T, E> supplier, Consumer<T> onGet, Consumer<T> onReset) { this.supplier = supplier; this.onGet = onGet; this.onReset = onReset; } /** * Returns a value that was created by <code>supplier</code>. The value might * have been previously created, if not it will be created now, thread safe of * course. */ public T getOrCompute() throws E { final T readOnce = value; // Read volatile just once... final T result = readOnce == null ? maybeCompute(supplier) : readOnce; onGet.accept(result); return result; } /** * Clears the value, if it has been previously created by calling * {@code #getOrCompute()}. The <code>onReset</code> will be called on this * value. The next call to {@code #getOrCompute()} will recreate the value. */ public synchronized void reset() { if (value != null) { onReset.accept(value); value = null; } } /** * Creates a new value thread safely. */ private synchronized T maybeCompute(CheckedSupplier<T, E> supplier) throws E { if (value == null) { value = Objects.requireNonNull(supplier.get()); } return value; }}LazyInitializable封装了CheckedSupplier,类似CachedSupplier,不过它提供了reset方法可以重置以反复使用,另外还支持了onGet、onReset的回调实例elasticsearch-7.0.1/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java ...

June 8, 2019 · 2 min · jiezi

聊聊Elasticsearch的CachedSupplier

序本文主要研究一下Elasticsearch的CachedSupplier CachedSupplierelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/CachedSupplier.java public final class CachedSupplier<T> implements Supplier<T> { private Supplier<T> supplier; private T result; private boolean resultSet; public CachedSupplier(Supplier<T> supplier) { this.supplier = supplier; } @Override public synchronized T get() { if (resultSet == false) { result = supplier.get(); resultSet = true; } return result; }}CachedSupplier实现了Supplier接口,它包装了一个supplier,其get方法只调用一次原始supplier的get方法并缓存其结果,下次调用直接返回缓存的结果实例elasticsearch-7.0.1/server/src/main/java/org/elasticsearch/search/query/TopDocsCollectorContext.java abstract static class SimpleTopDocsCollectorContext extends TopDocsCollectorContext { private static TopDocsCollector<?> createCollector(@Nullable SortAndFormats sortAndFormats, int numHits, @Nullable ScoreDoc searchAfter, int hitCountThreshold) { if (sortAndFormats == null) { return TopScoreDocCollector.create(numHits, searchAfter, hitCountThreshold); } else { return TopFieldCollector.create(sortAndFormats.sort, numHits, (FieldDoc) searchAfter, hitCountThreshold); } } protected final @Nullable SortAndFormats sortAndFormats; private final Collector collector; private final Supplier<TotalHits> totalHitsSupplier; private final Supplier<TopDocs> topDocsSupplier; private final Supplier<Float> maxScoreSupplier; /** * Ctr * @param reader The index reader * @param query The Lucene query * @param sortAndFormats The query sort * @param numHits The number of top hits to retrieve * @param searchAfter The doc this request should "search after" * @param trackMaxScore True if max score should be tracked * @param trackTotalHitsUpTo True if the total number of hits should be tracked * @param hasFilterCollector True if the collector chain contains at least one collector that can filters document */ private SimpleTopDocsCollectorContext(IndexReader reader, Query query, @Nullable SortAndFormats sortAndFormats, @Nullable ScoreDoc searchAfter, int numHits, boolean trackMaxScore, int trackTotalHitsUpTo, boolean hasFilterCollector) throws IOException { super(REASON_SEARCH_TOP_HITS, numHits); this.sortAndFormats = sortAndFormats; final TopDocsCollector<?> topDocsCollector; if (trackTotalHitsUpTo == SearchContext.TRACK_TOTAL_HITS_DISABLED) { // don't compute hit counts via the collector topDocsCollector = createCollector(sortAndFormats, numHits, searchAfter, 1); topDocsSupplier = new CachedSupplier<>(topDocsCollector::topDocs); totalHitsSupplier = () -> new TotalHits(0, TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO); } else { // implicit total hit counts are valid only when there is no filter collector in the chain final int hitCount = hasFilterCollector ? -1 : shortcutTotalHitCount(reader, query); if (hitCount == -1) { topDocsCollector = createCollector(sortAndFormats, numHits, searchAfter, trackTotalHitsUpTo); topDocsSupplier = new CachedSupplier<>(topDocsCollector::topDocs); totalHitsSupplier = () -> topDocsSupplier.get().totalHits; } else { // don't compute hit counts via the collector topDocsCollector = createCollector(sortAndFormats, numHits, searchAfter, 1); topDocsSupplier = new CachedSupplier<>(topDocsCollector::topDocs); totalHitsSupplier = () -> new TotalHits(hitCount, TotalHits.Relation.EQUAL_TO); } } MaxScoreCollector maxScoreCollector = null; if (sortAndFormats == null) { maxScoreSupplier = () -> { TopDocs topDocs = topDocsSupplier.get(); if (topDocs.scoreDocs.length == 0) { return Float.NaN; } else { return topDocs.scoreDocs[0].score; } }; } else if (trackMaxScore) { maxScoreCollector = new MaxScoreCollector(); maxScoreSupplier = maxScoreCollector::getMaxScore; } else { maxScoreSupplier = () -> Float.NaN; } this.collector = MultiCollector.wrap(topDocsCollector, maxScoreCollector); } @Override Collector create(Collector in) { assert in == null; return collector; } TopDocsAndMaxScore newTopDocs() { TopDocs in = topDocsSupplier.get(); float maxScore = maxScoreSupplier.get(); final TopDocs newTopDocs; if (in instanceof TopFieldDocs) { TopFieldDocs fieldDocs = (TopFieldDocs) in; newTopDocs = new TopFieldDocs(totalHitsSupplier.get(), fieldDocs.scoreDocs, fieldDocs.fields); } else { newTopDocs = new TopDocs(totalHitsSupplier.get(), in.scoreDocs); } return new TopDocsAndMaxScore(newTopDocs, maxScore); } @Override void postProcess(QuerySearchResult result) throws IOException { final TopDocsAndMaxScore topDocs = newTopDocs(); result.topDocs(topDocs, sortAndFormats == null ? null : sortAndFormats.formats); } }SimpleTopDocsCollectorContext的构造器使用CachedSupplier创建了topDocsSupplier;之后newTopDocs方法会调用topDocsSupplier.get()来获取TopDocs小结CachedSupplier实现了Supplier接口,它包装了一个supplier,其get方法只调用一次原始supplier的get方法并缓存其结果,下次调用直接返回缓存的结果 ...

June 7, 2019 · 3 min · jiezi

聊聊Elasticsearch的Iterables

序本文主要研究一下Elasticsearch的Iterables Iterableselasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/iterable/Iterables.java public class Iterables { public static <T> Iterable<T> concat(Iterable<T>... inputs) { Objects.requireNonNull(inputs); return new ConcatenatedIterable<>(inputs); } static class ConcatenatedIterable<T> implements Iterable<T> { private final Iterable<T>[] inputs; ConcatenatedIterable(Iterable<T>[] inputs) { this.inputs = Arrays.copyOf(inputs, inputs.length); } @Override public Iterator<T> iterator() { return Stream .of(inputs) .map(it -> StreamSupport.stream(it.spliterator(), false)) .reduce(Stream::concat) .orElseGet(Stream::empty).iterator(); } } /** Flattens the two level {@code Iterable} into a single {@code Iterable}. Note that this pre-caches the values from the outer {@code * Iterable}, but not the values from the inner one. */ public static <T> Iterable<T> flatten(Iterable<? extends Iterable<T>> inputs) { Objects.requireNonNull(inputs); return new FlattenedIterables<>(inputs); } static class FlattenedIterables<T> implements Iterable<T> { private final Iterable<? extends Iterable<T>> inputs; FlattenedIterables(Iterable<? extends Iterable<T>> inputs) { List<Iterable<T>> list = new ArrayList<>(); for (Iterable<T> iterable : inputs) { list.add(iterable); } this.inputs = list; } @Override public Iterator<T> iterator() { return StreamSupport .stream(inputs.spliterator(), false) .flatMap(s -> StreamSupport.stream(s.spliterator(), false)).iterator(); } } public static <T> T get(Iterable<T> iterable, int position) { Objects.requireNonNull(iterable); if (position < 0) { throw new IllegalArgumentException("position >= 0"); } if (iterable instanceof List) { List<T> list = (List<T>)iterable; if (position >= list.size()) { throw new IndexOutOfBoundsException(Integer.toString(position)); } return list.get(position); } else { Iterator<T> it = iterable.iterator(); for (int index = 0; index < position; index++) { if (!it.hasNext()) { throw new IndexOutOfBoundsException(Integer.toString(position)); } it.next(); } if (!it.hasNext()) { throw new IndexOutOfBoundsException(Integer.toString(position)); } return it.next(); } }}Iterables提供了concat、flatten、get三个静态方法,其中concat返回的是ConcatenatedIterable;flatten返回的是FlattenedIterables;get方法会先判断是否是List类型是的话直接通过position取值返回,不是则遍历iterable获取指定index的元素实例elasticsearch-7.0.1/server/src/test/java/org/elasticsearch/common/util/iterable/IterablesTests.java ...

June 6, 2019 · 3 min · jiezi

贷前系统ElasticSearch实践总结

贷前系统负责从进件到放款前所有业务流程的实现,其中涉及一些数据量较大、条件多样且复杂的综合查询,引入ElasticSearch主要是为了提高查询效率,并希望基于ElasticSearch快速实现一个简易的数据仓库,提供一些OLAP相关功能。本文将介绍贷前系统ElasticSearch的实践经验。 一、索引描述:为快速定位数据而设计的某种数据结构。 索引好比是一本书前面的目录,能加快数据库的查询速度。了解索引的构造及使用,对理解ES的工作模式有非常大的帮助。 常用索引: 位图索引哈希索引BTREE索引倒排索引1.1 位图索引(BitMap)位图索引适用于字段值为可枚举的有限个数值的情况。 位图索引使用二进制的数字串(bitMap)标识数据是否存在,1标识当前位置(序号)存在数据,0则表示当前位置没有数据。 下图1 为用户表,存储了性别和婚姻状况两个字段; 图2中分别为性别和婚姻状态建立了两个位图索引。 例如:性别->男 对应索引为:101110011,表示第1、3、4、5、8、9个用户为男性。其他属性以此类推。 使用位图索引查询: 男性 并且已婚 的记录 = 101110011 & 11010010 = 100100010,即第1、4、8个用户为已婚男性。女性 或者未婚的记录 = 010001100 | 001010100 = 011011100, 即第2、3、5、6、7个用户为女性或者未婚。1.2 哈希索引顾名思义,是指使用某种哈希函数实现key->value 映射的索引结构。 哈希索引适用于等值检索,通过一次哈希计算即可定位数据的位置。 下图3 展示了哈希索引的结构,与JAVA中HashMap的实现类似,是用冲突表的方式解决哈希冲突的。 1.3 BTREE索引BTREE索引是关系型数据库最常用的索引结构,方便了数据的查询操作。 BTREE: 有序平衡N阶树, 每个节点有N个键值和N+1个指针, 指向N+1个子节点。 一棵BTREE的简单结构如下图4所示,为一棵2层的3叉树,有7条数据: 以Mysql最常用的InnoDB引擎为例,描述下BTREE索引的应用。 Innodb下的表都是以索引组织表形式存储的,也就是整个数据表的存储都是B+tree结构的,如图5所示。 主键索引为图5的左半部分(如果没有显式定义自主主键,就用不为空的唯一索引来做聚簇索引,如果也没有唯一索引,则innodb内部会自动生成6字节的隐藏主键来做聚簇索引),叶子节点存储了完整的数据行信息(以主键 + row_data形式存储)。 二级索引也是以B+tree的形式进行存储,图5右半部分,与主键不同的是二级索引的叶子节点存储的不是行数据,而是索引键值和对应的主键值,由此可以推断出,二级索引查询多了一步查找数据主键的过程。 维护一颗有序平衡N叉树,比较复杂的就是当插入节点时节点位置的调整,尤其是插入的节点是随机无序的情况;而插入有序的节点,节点的调整只发生了整个树的局部,影响范围较小,效率较高。 可以参考红黑树的节点的插入算法: https://en.wikipedia.org/wiki... 因此如果innodb表有自增主键,则数据写入是有序写入的,效率会很高;如果innodb表没有自增的主键,插入随机的主键值,将导致B+tree的大量的变动操作,效率较低。这也是为什么会建议innodb表要有无业务意义的自增主键,可以大大提高数据插入效率。 注: Mysql Innodb使用自增主键的插入效率高。使用类似Snowflake的ID生成算法,生成的ID是趋势递增的,插入效率也比较高。1.4 倒排索引(反向索引)倒排索引也叫反向索引,可以相对于正向索引进行比较理解。 正向索引反映了一篇文档与文档中关键词之间的对应关系;给定文档标识,可以获取当前文档的关键词、词频以及该词在文档中出现的位置信息,如图6 所示,左侧是文档,右侧是索引。 反向索引则是指某关键词和该词所在的文档之间的对应关系;给定了关键词标识,可以获取关键词所在的所有文档列表,同时包含词频、位置等信息,如图7所示。 反向索引(倒排索引)的单词的集合和文档的集合就组成了如图8所示的”单词-文档矩阵“,打钩的单元格表示存在该单词和文档的映射关系。 ...

June 6, 2019 · 3 min · jiezi

聊聊Elasticsearch的EvictingQueue

序本文主要研究一下Elasticsearch的EvictingQueue EvictingQueueelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/collect/EvictingQueue.java public class EvictingQueue<T> implements Queue<T> { private final int maximumSize; private final ArrayDeque<T> queue; /** * Construct a new {@code EvictingQueue} that holds {@code maximumSize} elements. * * @param maximumSize The maximum number of elements that the queue can hold * @throws IllegalArgumentException if {@code maximumSize} is less than zero */ public EvictingQueue(int maximumSize) { if (maximumSize < 0) { throw new IllegalArgumentException("maximumSize < 0"); } this.maximumSize = maximumSize; this.queue = new ArrayDeque<>(maximumSize); } /** * @return the number of additional elements that the queue can accommodate before evictions occur */ public int remainingCapacity() { return this.maximumSize - this.size(); } /** * Add the given element to the queue, possibly forcing an eviction from the head if {@link #remainingCapacity()} is * zero. * * @param t the element to add * @return true if the element was added (always the case for {@code EvictingQueue} */ @Override public boolean add(T t) { if (maximumSize == 0) { return true; } if (queue.size() == maximumSize) { queue.remove(); } queue.add(t); return true; } /** * @see #add(Object) */ @Override public boolean offer(T t) { return add(t); } @Override public T remove() { return queue.remove(); } @Override public T poll() { return queue.poll(); } @Override public T element() { return queue.element(); } @Override public T peek() { return queue.peek(); } @Override public int size() { return queue.size(); } @Override public boolean isEmpty() { return queue.isEmpty(); } @Override public boolean contains(Object o) { return queue.contains(o); } @Override public Iterator<T> iterator() { return queue.iterator(); } @Override public Object[] toArray() { return queue.toArray(); } @Override public <T1> T1[] toArray(T1[] a) { return queue.toArray(a); } @Override public boolean remove(Object o) { return queue.remove(o); } @Override public boolean containsAll(Collection<?> c) { return queue.containsAll(c); } /** * Add the given elements to the queue, possibly forcing evictions from the head if {@link #remainingCapacity()} is * zero or becomes zero during the execution of this method. * * @param c the collection of elements to add * @return true if any elements were added to the queue */ @Override public boolean addAll(Collection<? extends T> c) { boolean modified = false; for (T e : c) if (add(e)) modified = true; return modified; } @Override public boolean removeAll(Collection<?> c) { return queue.removeAll(c); } @Override public boolean retainAll(Collection<?> c) { return queue.retainAll(c); } @Override public void clear() { queue.clear(); }}EvictingQueue实现了Queue接口,它的构造器要求输入maximumSize,然后根据maximumSize创建ArrayDeque;其add方法会判断当前队列大小是否等于maximumSize,等于则移除队首的元素然后再添加新元素实例elasticsearch-7.0.1/server/src/test/java/org/elasticsearch/search/aggregations/pipeline/MovFnWhitelistedFunctionTests.java ...

June 5, 2019 · 3 min · jiezi

聊聊Elasticsearch的AtomicArray

序本文主要研究一下Elasticsearch的AtomicArray AtomicArrayelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/AtomicArray.java public class AtomicArray<E> { private final AtomicReferenceArray<E> array; private volatile List<E> nonNullList; public AtomicArray(int size) { array = new AtomicReferenceArray<>(size); } /** * The size of the expected results, including potential null values. */ public int length() { return array.length(); } /** * Sets the element at position {@code i} to the given value. * * @param i the index * @param value the new value */ public void set(int i, E value) { array.set(i, value); if (nonNullList != null) { // read first, lighter, and most times it will be null... nonNullList = null; } } public final void setOnce(int i, E value) { if (array.compareAndSet(i, null, value) == false) { throw new IllegalStateException("index [" + i + "] has already been set"); } if (nonNullList != null) { // read first, lighter, and most times it will be null... nonNullList = null; } } /** * Gets the current value at position {@code i}. * * @param i the index * @return the current value */ public E get(int i) { return array.get(i); } /** * Returns the it as a non null list. */ public List<E> asList() { if (nonNullList == null) { if (array == null || array.length() == 0) { nonNullList = Collections.emptyList(); } else { List<E> list = new ArrayList<>(array.length()); for (int i = 0; i < array.length(); i++) { E e = array.get(i); if (e != null) { list.add(e); } } nonNullList = list; } } return nonNullList; } /** * Copies the content of the underlying atomic array to a normal one. */ public E[] toArray(E[] a) { if (a.length != array.length()) { throw new ElasticsearchGenerationException("AtomicArrays can only be copied to arrays of the same size"); } for (int i = 0; i < array.length(); i++) { a[i] = array.get(i); } return a; }}AtomicArray封装了AtomicReferenceArray并定义了nonNullList,提供了asList方法转换为ArrayList;而setOnce方法则使用了AtomicReferenceArray的compareAndSet方法来实现;另外set及setOnce都会判断nonNullList是否为null,不为null则重新设置为nullGroupedActionListenerelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/action/support/GroupedActionListener.java ...

June 4, 2019 · 3 min · jiezi

PHP技术栈

本文旨在给要学习 PHP 的新手一个大概的认知轮廓,在心里有个学习的结构,有的放矢,避免走太多弯路。大神请忽略。 入门阶段预备知识1、掌握基本HTML、JS、CSS语法;熟悉 Bootstrap。 参考: https://www.runoob.com/html/h... https://www.liaoxuefeng.com/w... https://www.runoob.com/css/cs... 验收最低标准:模仿写出jd.com或者vip.com首页第一屏内容。2、熟悉Linux命令行 熟悉常用发行版系统(CentOS、Ubuntu)安装 熟悉常用命令行操作,包括文件管理、用户管理、权限管理、防火墙管理等 熟悉VIM使用 验收最低标准:掌握lnmp环境搭建。PHP基础掌握PHP基础语法、文件上传、cookie、Session、JSON。 掌握MySQL数据库连接:pdo使用。 掌握redis连接及简单应用。 掌握命名空间。 掌握面向对象编程思想。 参考:https://www.runoob.com/php/php-tutorial.html 学习框架学会使用ThinkPHP框架。主要是该框架在国内使用普及率太高了。不建议使用Laravel入门,因为该框架使用了较多的语法糖、第三方库,对新手可能有难度。 验收最低标准:可以使用ThinkPHP最新版作为入手框架,写出一个简单的博客。页面简单写就行。数据之间使用TP的 display 渲染到页面。学习写接口学会 Charles 抓包,看豆瓣的接口返回的数据。 学会写接口(GET、POST)的就行。 学会使用 POSTMAN。 验收标最低准:把上面的博客项目改成前后端分离的,先写完接口(最好有文档),再在页面里使用ajax调用接口数据。至此,你已经入门了。如果需要继续往下,还要学习。 第二阶段PHP使用 composer 安装PHP第三方库 对于PHP断点调试非常熟悉 学习常用PHP扩展 使用 SPL 掌握 PSR 规范 掌握反射的使用 掌握设计模式 熟练使用常用框架。 了解php和php-fpm的大部分配置选项和含义。 熟悉HTTP协议。 熟悉正则表达式。 MYSQL熟悉MYSQL优化的一些技巧,例如MySQL的性能追查,包括slow_log/explain等;对于order by、limit、like等一些坑能避开;能够熟练使用常用的索引;对于表结构创建选用哪种数据类型做到胸有成竹等等。 熟悉常用的配置,知道如何调优。 熟练配置主从。 NOSQL掌握Redis使用:对于常用数据结构的经典使用场景非常熟悉;了解Redis的事务、RDB、AOF等机制。 掌握memcache的使用,知道与redis的区别。 了解一下MongoDB。 Linux熟悉常用文本命令:例如wc、awk、split、diff、grep、sed等。 熟悉sort、uniq的使用。 熟练掌握ps、netstat、top等命令使用。 熟练使用Supervisor。 熟悉如何编写shell脚本。 能够理解Nginx的配置的含义。 第三阶段PHP该阶段PHP已经非常熟悉了,拥有快速开发项目、快速解决BUG的能力。代码遵循psr规范、稳定性很高。 熟悉消息队列使用,在很多场景合适的选择消息队列进行异步解耦。 熟悉如何使用 Elasticsearch 代替MYSQL的全文搜索功能。 熟悉多进程编程。 熟悉socket编程,对于网络IO模型有一定的认知,熟悉多路复用(select/poll/epoll)技术。 熟悉swoole框架,能应用于项目上。 不限制于框架本身,任何框架一天内快速入手。 对php的工作机制熟悉,熟悉php-fpm生命周期。 能够知道PHP相对于c等强类型语言性能为什么会慢。 对于PHP内部的实现原理有一定的认知,例如变量的实现、zend引擎的了解。 对于PHP的扩展有一定的认知,可以编写简单的扩展。 ...

June 4, 2019 · 1 min · jiezi

聊聊Elasticsearch的ConcurrentMapLong

序本文主要研究一下Elasticsearch的ConcurrentMapLong ConcurrentMapLongelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/ConcurrentMapLong.java public interface ConcurrentMapLong<T> extends ConcurrentMap<Long, T> { T get(long key); T remove(long key); T put(long key, T value); T putIfAbsent(long key, T value);}ConcurrentMapLong继承了ConcurrentMap接口,并指定key类型为LongConcurrentHashMapLongelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/ConcurrentHashMapLong.java public class ConcurrentHashMapLong<T> implements ConcurrentMapLong<T> { private final ConcurrentMap<Long, T> map; public ConcurrentHashMapLong(ConcurrentMap<Long, T> map) { this.map = map; } @Override public T get(long key) { return map.get(key); } @Override public T remove(long key) { return map.remove(key); } @Override public T put(long key, T value) { return map.put(key, value); } @Override public T putIfAbsent(long key, T value) { return map.putIfAbsent(key, value); } // MAP DELEGATION @Override public boolean isEmpty() { return map.isEmpty(); } @Override public int size() { return map.size(); } @Override public T get(Object key) { return map.get(key); } @Override public boolean containsKey(Object key) { return map.containsKey(key); } @Override public boolean containsValue(Object value) { return map.containsValue(value); } @Override public T put(Long key, T value) { return map.put(key, value); } @Override public T putIfAbsent(Long key, T value) { return map.putIfAbsent(key, value); } @Override public void putAll(Map<? extends Long, ? extends T> m) { map.putAll(m); } @Override public T remove(Object key) { return map.remove(key); } @Override public boolean remove(Object key, Object value) { return map.remove(key, value); } @Override public boolean replace(Long key, T oldValue, T newValue) { return map.replace(key, oldValue, newValue); } @Override public T replace(Long key, T value) { return map.replace(key, value); } @Override public void clear() { map.clear(); } @Override public Set<Long> keySet() { return map.keySet(); } @Override public Collection<T> values() { return map.values(); } @Override public Set<Entry<Long, T>> entrySet() { return map.entrySet(); } @Override public boolean equals(Object o) { return map.equals(o); } @Override public int hashCode() { return map.hashCode(); } @Override public String toString() { return map.toString(); }}ConcurrentHashMapLong实现了ConcurrentMapLong接口,它内部使用ConcurrentMap实现ConcurrentCollectionselasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/ConcurrentCollections.java ...

June 3, 2019 · 2 min · jiezi

基于Elasticsearch的地理位置简单搜索

汇总一些简单用法 因为公司需要使用一个需求, 通过用户的当前地理位置消息搜索出周边的一些数据, 如果使用php进行大数据计算的话,非常消耗性能,所以采用es相关文档学习包的使用: https://packagist.org/package... https://www.cnblogs.com/codeA... 地理位置的查询:http://cwiki.apachecn.org/pag... 查询语法:https://doc.yonyoucloud.com/d... 经纬度查询实例:https://cloud.tencent.com/inf... ES - PHPhttps://www.elastic.co/guide/... 创建索引PUT http://localhost:9200/show 创建索引字段PUT http://localhost:9200/show/store/_mapping { "store": { "_all":{ "enabled":false }, "properties": { "id": { "type": "integer" }, "name": { "type": "text", "analyzer": "ik_max_word" }, "type": { "type": "integer" }, "position": { "properties": { "location": { "type": "geo_point" } } } } } }创建索引文档PUT http://localhost:9200/show/test/2{ "id" : 1, "name" : "建升大厦", "type" : 1, "position":{ "location" : { "lat" : 22.6482057076, "lon" : 114.1250142233 } }}PUT http://localhost:9200/show/test/1{ "id" : 2, "name" : "深圳市第三人民医院", "type" : 1, "position":{ "location" : { "lat" : 22.6352587415, "lon" : 114.1289020619 } }}PUT http://localhost:9200/show/test/3{ "id" : 3, "name" : "深圳百合医院", "type" : 1, "position":{ "location" : { "lat" : 22.6164455768, "lon" : 114.1395956293 } }}开始查询查询所有 ...

June 3, 2019 · 2 min · jiezi

elasticsearch学习笔记高级篇九多shard场景下相关度分数不准确问题

场景分析:在某个shard中,有很多个document包含了title中有java这个关键字,比如说10个doc的title中包含了java。当一个搜索title包含java的请求到这个shard的时候,应该会这么计算relevance score相关度分数。TF/IDF算法:(1)在一个document的title中java出现了几次(2)在所有的document的title中,java出现了几次(3)这个document的title的长度由于shard只是一部分的document,默认情况下就在shard本地计算IDF。当有多个shard的时候,比如在一个shard中,只有一个document title包含java,此时计算shard local IDF就会分数很高,导致相关度分数很高。这就有可能导致出现的搜索结果,似乎不太是你想要的结果。也许相关度很高的doc排在了后面,分数不高,而相关度很低的doc排在了前面,分数很高。 如何解决该问题(1)在生产环境下,数据量大,尽可能实现均匀分配数据量很大的话,在概率学的背景下,elasticsearch都是在多个shard中均匀路由数据的,路由的时候根据_id,实现负载均衡。比如说有10个document,title都包含java,一共有5个shard,那么在概率学的背景下,如果负载均衡的话,其实每个shard都应该有2个doc,title包含java。如果说数据分布均匀的话,其实就没有因为IDF不准确导致相关度分数不准确的问题。(2)测试环境下,将索引的primary shard设置为1个如果说只有一个shard,那么当然所有的document都在这个shard里面,也就没有没有因为IDF不准确导致相关度分数不准确的问题。(3)测试环境下,搜索附带search_type=dfs_query_then_fetch参数带上search_type=dfs_query_then_fetch参数,就会将local IDF取出来计算global IDF。也就是在计算一个doc的相关度分数的时候,就会将所有shard对local IDF计算一下,获取出来在本地进行global IDF分数的计算,此时会将所有shard的doc作为上下文来进行计算,可以保证准确性,但是生产环境下,不推荐这个参数,因为性能很差。

June 2, 2019 · 1 min · jiezi

聊聊Elasticsearch的EsThreadPoolExecutor

序本文主要研究一下Elasticsearch的EsThreadPoolExecutor EsThreadPoolExecutorelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/EsThreadPoolExecutor.java public class EsThreadPoolExecutor extends ThreadPoolExecutor { private final ThreadContext contextHolder; private volatile ShutdownListener listener; private final Object monitor = new Object(); /** * Name used in error reporting. */ private final String name; final String getName() { return name; } EsThreadPoolExecutor(String name, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, ThreadContext contextHolder) { this(name, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new EsAbortPolicy(), contextHolder); } @SuppressForbidden(reason = "properly rethrowing errors, see EsExecutors.rethrowErrors") EsThreadPoolExecutor(String name, int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, XRejectedExecutionHandler handler, ThreadContext contextHolder) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); this.name = name; this.contextHolder = contextHolder; } public void shutdown(ShutdownListener listener) { synchronized (monitor) { if (this.listener != null) { throw new IllegalStateException("Shutdown was already called on this thread pool"); } if (isTerminated()) { listener.onTerminated(); } else { this.listener = listener; } } shutdown(); } @Override protected synchronized void terminated() { super.terminated(); synchronized (monitor) { if (listener != null) { try { listener.onTerminated(); } finally { listener = null; } } } } public interface ShutdownListener { void onTerminated(); } @Override public void execute(Runnable command) { command = wrapRunnable(command); try { super.execute(command); } catch (EsRejectedExecutionException ex) { if (command instanceof AbstractRunnable) { // If we are an abstract runnable we can handle the rejection // directly and don't need to rethrow it. try { ((AbstractRunnable) command).onRejection(ex); } finally { ((AbstractRunnable) command).onAfter(); } } else { throw ex; } } } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); EsExecutors.rethrowErrors(unwrap(r)); assert assertDefaultContext(r); } private boolean assertDefaultContext(Runnable r) { try { assert contextHolder.isDefaultContext() : "the thread context is not the default context and the thread [" + Thread.currentThread().getName() + "] is being returned to the pool after executing [" + r + "]"; } catch (IllegalStateException ex) { // sometimes we execute on a closed context and isDefaultContext doen't bypass the ensureOpen checks // this must not trigger an exception here since we only assert if the default is restored and // we don't really care if we are closed if (contextHolder.isClosed() == false) { throw ex; } } return true; } /** * Returns a stream of all pending tasks. This is similar to {@link #getQueue()} but will expose the originally submitted * {@link Runnable} instances rather than potentially wrapped ones. */ public Stream<Runnable> getTasks() { return this.getQueue().stream().map(this::unwrap); } @Override public final String toString() { StringBuilder b = new StringBuilder(); b.append(getClass().getSimpleName()).append('['); b.append("name = ").append(name).append(", "); if (getQueue() instanceof SizeBlockingQueue) { @SuppressWarnings("rawtypes") SizeBlockingQueue queue = (SizeBlockingQueue) getQueue(); b.append("queue capacity = ").append(queue.capacity()).append(", "); } appendThreadPoolExecutorDetails(b); /* * ThreadPoolExecutor has some nice information in its toString but we * can't get at it easily without just getting the toString. */ b.append(super.toString()).append(']'); return b.toString(); } /** * Append details about this thread pool to the specified {@link StringBuilder}. All details should be appended as key/value pairs in * the form "%s = %s, " * * @param sb the {@link StringBuilder} to append to */ protected void appendThreadPoolExecutorDetails(final StringBuilder sb) { } protected Runnable wrapRunnable(Runnable command) { return contextHolder.preserveContext(command); } protected Runnable unwrap(Runnable runnable) { return contextHolder.unwrap(runnable); }}EsThreadPoolExecutor继承了ThreadPoolExecutor,它提供了两个构造器,它们要求RejectedExecutionHandler为XRejectedExecutionHandler类型,其中一个构造器默认为EsAbortPolicy,它们还要求传入ThreadContext它覆盖了terminated、execute、afterExecute方法,其中terminated方法会回调listener.onTerminated();execute方法会捕获EsRejectedExecutionException异常,在command为AbstractRunnable类型时回调其onRejection及onAfter方法;afterExecute方法会执行EsExecutors.rethrowErrors(unwrap(r))方法它提供了wrapRunnable及unwrap方法,分别会调用contextHolder.preserveContext及contextHolder.unwrap方法XRejectedExecutionHandlerelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/XRejectedExecutionHandler.java ...

June 2, 2019 · 5 min · jiezi

elasticsearch学习笔记高级篇八基于boost的细粒度搜索条件权重控制

需求:搜索标题中包含java的帖子,同时如果标题中包含hadoop和elasticsearch就优先搜索出来,同时,如果一个帖子包含java hadoop,一个帖子包含java elasticsearch,包含hadoop的帖子要比elasticsearch优先搜索出来。 搜索条件的权重,boost,可以将某个搜索条件的权重加大,此时当匹配这个搜索条件和匹配另一个搜索条件的document,计算relevance score时,匹配权重更大的搜索条件的document, relevance score会更高,当然也就会优先被返回回来。 在默认的情况下,搜索条件的权重都是一样的,都是1 GET /forum/_search{ "query": { "bool": { "must": [ { "match": { "title": "java" } } ], "should": [ { "match": { "title": "hadoop" } }, { "match": { "title": "elasticsearch" } }, { "match": { "title": "spark" } } ] } }}这个测试不好复现,在出现的结果中如果不是我们想要的,我们可以根据上面的需求调节一下boost,来让最后的返回结果达到我们的预期。 GET /forum/_search{ "query": { "bool": { "must": [ { "match": { "title": "java" } } ], "should": [ { "match": { "title": { "query": "hadoop", "boost": 5 } } }, { "match": { "title": { "query": "elasticsearch", "boost": 3 } } }, { "match": { "title": { "query": "spark", "boost": 1 } } } ] } }}

June 1, 2019 · 1 min · jiezi

elasticsearch学习笔记高级篇七基于termbool搜索底层原理剖析

在上一讲我们可以发现,对于multi-value的搜索方式,实现起来可以有多种方式。这里就说明一下,实现的方式虽然很多,但是elasticsearch在查询的时候底层都会转换为bool + term的形式 1、普通的match如何转换为term+should{ "match": { "title": "java elasticsearch" }}使用类似上面的match query进行多值搜索的时候,elasticsearch会在底层自动将这个match query转换为bool的语法 { "bool": { "should": [ { "term": { "title": "java" } }, { "term: { "title": "elasticsearch" } } ] }}2、and match 如何转换为term+must{ "match": { "title": { "query": "java elasticsearch", "operator": "and" } }}转换为: { "bool": { "must": [ { "term": { "title": "java" } }, { "term": { "title": "elasticsearch" } } ] }}3、minimum_should_match如何转换{ "match": { "title": { "query": "java elasticsearch spark hadoop", "minimum_should_match": 3 } }}转换为: ...

June 1, 2019 · 1 min · jiezi

elasticsearch学习笔记高级篇六在案例中如果通过手动控制全文检索结果的精准度

准备数据:POST /forum/_bulk{ "index": { "_id": 1 }}{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 2 }}{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2017-01-02" }{ "index": { "_id": 3 }}{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 4 }}{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2017-01-02" }1、为帖子数据增加标题字段POST /forum/_bulk{ "update": { "_id": "1"} }{ "doc" : {"title" : "this is java and elasticsearch blog"} }{ "update": { "_id": "2"} }{ "doc" : {"title" : "this is java blog"} }{ "update": { "_id": "3"} }{ "doc" : {"title" : "this is elasticsearch blog"} }{ "update": { "_id": "4"} }{ "doc" : {"title" : "this is java, elasticsearch, hadoop blog"} }{ "update": { "_id": "5"} }{ "doc" : {"title" : "this is spark blog"} }2、搜索标题中包含java或elasticsearch的blog这个就跟之前的那个term filter/query不一样了。不是搜索exact value,而是进行full text全文搜索。match query是负责进行全文检索的。当然如果要检索的field是not_analyzed类型的,那么match query也相当于term query ...

June 1, 2019 · 5 min · jiezi

elasticsearch学习笔记高级篇五在案例中实战基于range-filter来进行范围过滤

格式: "range": { "FIELD": { "gte": 10, "lte": 20 }类似于SQL中的between、大于等于、小于等于之类的范围筛选 准备数据:POST /forum/_bulk{ "index": { "_id": 1 }}{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 2 }}{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2017-01-02" }{ "index": { "_id": 3 }}{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 4 }}{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2017-01-02" }1、为帖子数据增加浏览量的字段POST /forum/_bulk{ "update": { "_id": "1"} }{ "doc" : {"view_cnt" : 30} }{ "update": { "_id": "2"} }{ "doc" : {"view_cnt" : 50} }{ "update": { "_id": "3"} }{ "doc" : {"view_cnt" : 100} }{ "update": { "_id": "4"} }{ "doc" : {"view_cnt" : 80} }2、搜索浏览量在30~60之间的帖子GET /forum/_search{ "query": { "constant_score": { "filter": { "range": { "view_cnt": { "gte": 30, "lte": 60 } } } } }}{ "took" : 561, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ { "_index" : "forum", "_type" : "_doc", "_id" : "1", "_score" : 1.0, "_source" : { "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden" : false, "postDate" : "2017-01-01", "tag" : [ "java", "hadoop" ], "tag_cnt" : 2, "view_cnt" : 30 } }, { "_index" : "forum", "_type" : "_doc", "_id" : "2", "_score" : 1.0, "_source" : { "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden" : false, "postDate" : "2017-01-02", "tag" : [ "java" ], "tag_cnt" : 1, "view_cnt" : 50 } } ] }}3、搜索发帖日期在最近1个月的帖子POST /forum/_bulk{ "index": { "_id": 5 }}{ "articleID" : "DHJK-B-1395-#Ky5", "userID" : 3, "hidden": false, "postDate": "2019-05-30", "tag": ["elasticsearch"], "tag_cnt": 1, "view_cnt": 10 }准备一条数据,之前时间比较老了 ...

June 1, 2019 · 2 min · jiezi

elasticsearch学习笔记高级篇四在案例中实战使用terms搜索多个值以及多值搜索结果优化

格式描述:term格式: "term": { "FIELD": { "value": "VALUE" } terms格式: "terms": { "FIELD": [ "VALUE1", "VALUE2" ] }对于terms,如果和SQL语句联系起来的话,那么就相当于in 准备数据:POST /forum/_bulk{ "index": { "_id": 1 }}{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 2 }}{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2017-01-02" }{ "index": { "_id": 3 }}{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 4 }}{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2017-01-02" }1、为帖子字段增加tag字段POST /forum/_bulk{ "update": { "_id": "1"} }{ "doc" : {"tag" : ["java", "hadoop"]} }{ "update": { "_id": "2"} }{ "doc" : {"tag" : ["java"]} }{ "update": { "_id": "3"} }{ "doc" : {"tag" : ["hadoop"]} }{ "update": { "_id": "4"} }{ "doc" : {"tag" : ["java", "elasticsearch"]} }2、 搜索articleID为KDKE-B-9947-#kL5或QQPX-R-3956-#aD8的帖子GET /forum/_search{ "query": { "constant_score": { "filter": { "terms": { "articleID": [ "KDKE-B-9947-#kL5", "QQPX-R-3956-#aD8" ] } } } }}输出: ...

June 1, 2019 · 3 min · jiezi

elasticsearch学习笔记高级篇三在案例中实战基于bool组合多个filter条件搜索

准备数据:POST /forum/_bulk{ "index": { "_id": 1 }}{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 2 }}{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2017-01-02" }{ "index": { "_id": 3 }}{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 4 }}{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2017-01-02" }需求1、搜索发帖日期为2017-01-01,或者帖子ID为XHDK-A-1293-#fJ3的帖子,同时要求帖子的发帖日期不能为2017-01-02select * from forumwhere (post_date = '2017-01-01' or article_id = 'XHDK-A-1293-#fJ3')and post_date != '2017-01-02'GET /forum/_search{ "query": { "constant_score": { "filter": { "bool": { "should": [ { "term": { "postDate": "2017-01-01" } }, { "term": { "articleID": "XHDK-A-1293-#fJ3" } } ], "must_not": [ { "term": { "postDate": "2017-01-02" } } ] } } } }}输出: ...

June 1, 2019 · 2 min · jiezi

聊聊Elasticsearch的SizeBlockingQueue

序本文主要研究一下Elasticsearch的SizeBlockingQueue SizeBlockingQueueelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/SizeBlockingQueue.java public class SizeBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E> { private final BlockingQueue<E> queue; private final int capacity; private final AtomicInteger size = new AtomicInteger(); public SizeBlockingQueue(BlockingQueue<E> queue, int capacity) { assert capacity >= 0; this.queue = queue; this.capacity = capacity; } @Override public int size() { return size.get(); } public int capacity() { return this.capacity; } @Override public Iterator<E> iterator() { final Iterator<E> it = queue.iterator(); return new Iterator<E>() { E current; @Override public boolean hasNext() { return it.hasNext(); } @Override public E next() { current = it.next(); return current; } @Override public void remove() { // note, we can't call #remove on the iterator because we need to know // if it was removed or not if (queue.remove(current)) { size.decrementAndGet(); } } }; } @Override public E peek() { return queue.peek(); } @Override public E poll() { E e = queue.poll(); if (e != null) { size.decrementAndGet(); } return e; } @Override public E poll(long timeout, TimeUnit unit) throws InterruptedException { E e = queue.poll(timeout, unit); if (e != null) { size.decrementAndGet(); } return e; } @Override public boolean remove(Object o) { boolean v = queue.remove(o); if (v) { size.decrementAndGet(); } return v; } /** * Forces adding an element to the queue, without doing size checks. */ public void forcePut(E e) throws InterruptedException { size.incrementAndGet(); try { queue.put(e); } catch (InterruptedException ie) { size.decrementAndGet(); throw ie; } } @Override public boolean offer(E e) { while (true) { final int current = size.get(); if (current >= capacity()) { return false; } if (size.compareAndSet(current, 1 + current)) { break; } } boolean offered = queue.offer(e); if (!offered) { size.decrementAndGet(); } return offered; } @Override public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { // note, not used in ThreadPoolExecutor throw new IllegalStateException("offer with timeout not allowed on size queue"); } @Override public void put(E e) throws InterruptedException { // note, not used in ThreadPoolExecutor throw new IllegalStateException("put not allowed on size queue"); } @Override public E take() throws InterruptedException { E e; try { e = queue.take(); size.decrementAndGet(); } catch (InterruptedException ie) { throw ie; } return e; } @Override public int remainingCapacity() { return capacity() - size.get(); } @Override public int drainTo(Collection<? super E> c) { int v = queue.drainTo(c); size.addAndGet(-v); return v; } @Override public int drainTo(Collection<? super E> c, int maxElements) { int v = queue.drainTo(c, maxElements); size.addAndGet(-v); return v; } @Override public Object[] toArray() { return queue.toArray(); } @Override public <T> T[] toArray(T[] a) { return (T[]) queue.toArray(a); } @Override public boolean contains(Object o) { return queue.contains(o); } @Override public boolean containsAll(Collection<?> c) { return queue.containsAll(c); }}SizeBlockingQueue继承了AbstractQueue,同时实现了BlockingQueue接口;它的构造器要求输入blockingQueue及capacity参数SizeBlockingQueue有个AtomicInteger类型的size参数用于记录queue的大小,它在poll、remove、offer、take等方法都会维护这个size参数其中offer方法会判断当前size是否大于等于capacity,如果大于等于则直接返回false;而put方法则直接抛出IllegalStateExceptionResizableBlockingQueueelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/util/concurrent/ResizableBlockingQueue.java ...

June 1, 2019 · 4 min · jiezi

elasticsearch学习笔记高级篇二filter执行原理深度剖析

下面详细讲一下为什么filter的性能很高,filter的底层原理究竟是什么?通过一个搜索的场景来深入剖析一下,当一个filter搜索请求打到Elasticsearch的时候,ES会进行下面的操作: (1)在倒排索引中查找搜索串,获取document list以date来举例: word doc1 doc2 doc32019-01-01 * *2019-02-02 * *2019-03-03 * * *filter: 2019-02-02到倒排索引中一找,发现2019-02-02对应的document list是doc2,doc3 (2)为每个在倒排索引中搜索到的结果,构建一个bitset这一步是非常重要的,使用找到的doc list,构建一个bitset,就是一个二进制的数组,数组的每个元素都是0或1,用来标识一个doc对一个filter条件是否匹配,如果匹配的话值就是1,不匹配值就是0。所以上面的filter的bitset的结果就是: [0,1,1]doc1:不匹配这个filter的doc2和doc3:匹配这个filter的注意:这样做的好处就是尽可能用简单的数据结构去实现复杂的功能,可以节省内存空间,提升性能。 (3)遍历每个过滤条件对应的bitset,优先从最稀疏的开始搜索,查找满足所有条件的document由于一次性可以在一个search请求中发出多个filter条件,那么就会产生多个bitset,遍历每个filter条件对应的bitset优先从最稀疏的开始遍历 [0,0,0,0,0,0,0,1] 比较稀疏的bitset[1,0,1,1,0,1,0,1]这里主要是因为先遍历比较稀疏的bitset,就可以先过滤掉尽可能多的数据 (4)caching bitsetcaching bitset会跟踪query,在最近256个query中超过一定次数的过滤条件,缓存其bitset。对于小segment(<1000 或<3%),不缓存bitset。这样下次如果在有这个条件过来的时候,就不用重新扫描倒排索引,反复生成bitset,可以大幅度提升性能。 说明:1、在最近的256个filter中,有某个filter超过了一定次数,这个次数不固定,那么elasticsearch就会缓存这个filter对应的bitset2、filter针对小的segment获取到的结果,是可以不缓存的,segment记录数小于1000,或者segment大小小于index总大小的3%。因为此时segment数据量很小,哪怕是扫描也是很快的;segment会在后台自动合并,小segment很快会跟其它小segment合并成大segment,此时缓存就没有什么意思了,segment很快会消失。 filter比query好的原因除了不计算相关度分数以外还有这个caching bitset。所以filter性能会很高。(5)filter大部分的情况下,是在query之前执行的,可以尽可能过滤掉多的数据query: 会计算每个doc的相关度分数,还会根据这个相关度分数去做排序filter: 只是简单过滤出想要的数据,不计算相关度分数,也不排序 (6)如果document有新增和修改,那么caching bitset会被自动更新这个过程是ES内部做的,比如之前的bitset是[0,0,0,1]。那么现在插入一条数据或是更新了一条数据doc5,而且doc5也在缓存的bitset[0,0,0,1]的filter查询条件中,那么ES会自动更新这个bitset,变为[0,0,0,1,1] (7)以后只要有相同的filter条件的查询请求打过来,就会直接使用这个过滤条件对应的bitset这样查询性能就会很高,一些热的filter查询,就会被cache住。

June 1, 2019 · 1 min · jiezi

聊聊Elasticsearch的RoundRobinSupplier

序本文主要研究一下Elasticsearch的RoundRobinSupplier RoundRobinSupplierelasticsearch-7.0.1/libs/nio/src/main/java/org/elasticsearch/nio/RoundRobinSupplier.java final class RoundRobinSupplier<S> implements Supplier<S> { private final AtomicBoolean selectorsSet = new AtomicBoolean(false); private volatile S[] selectors; private AtomicInteger counter = new AtomicInteger(0); RoundRobinSupplier() { this.selectors = null; } RoundRobinSupplier(S[] selectors) { this.selectors = selectors; this.selectorsSet.set(true); } @Override public S get() { S[] selectors = this.selectors; return selectors[counter.getAndIncrement() % selectors.length]; } void setSelectors(S[] selectors) { if (selectorsSet.compareAndSet(false, true)) { this.selectors = selectors; } else { throw new AssertionError("Selectors already set. Should only be set once."); } } int count() { return selectors.length; }}RoundRobinSupplier实现了Supplier接口,其get方法使用counter.getAndIncrement() % selectors.length来选择selectors数组的下标,然后返回该下标的值NioSelectorGroupelasticsearch-7.0.1/libs/nio/src/main/java/org/elasticsearch/nio/NioSelectorGroup.java ...

May 31, 2019 · 2 min · jiezi

Elasticsearch如何做到亿级数据查询毫秒级返回

本文来源 | https://zhuanlan.zhihu.com/p/... 导读:如果面试的时候碰到这样一个面试题:ES 在数据量很大的情况下(数十亿级别)如何提高查询效率? 这个问题,其实就是在看你是否用过 ES,其实 ES 性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条数据的时候,可能你会懵逼的发现,跑个搜索怎么一下 5~10s。 第一次搜索的时候,是5~10s,后面反而就快了,可能就几百毫秒。 你就很懵,每个用户第一次访问都会比较慢,比较卡么?所以你要是没玩儿过 ES,或者就是自己玩玩儿 Demo,被问到这个问题容易懵逼,显示出你对 ES 确实玩的不怎么样。 说实话,ES 性能优化是没有银弹的,不要期待着随手调一个参数,就可以万能的应对所有性能慢的场景。也许有的场景你换个参数,或者调整一下语法,就可以搞定,但绝对不是所有场景都可以这样。 性能优化的杀手锏:Filesystem Cache 你向 ES 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 Filesystem Cache 里面去。 ES 的搜索引擎严重依赖于底层的 Filesystem Cache,如果给 Filesystem Cache 更多的内存,尽量让内存可以容纳所有的 IDX Segment File 索引数据文件,那么你在搜索的时候基本都会走内存且性能会非常高。 性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1 秒、5 秒、10 秒。但如果走 Filesystem Cache,则是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。 这里有个真实的案例:某个公司 ES 节点有 3 台机器,每台机器看起来内存很多(64G),总内存就是 64 * 3 = 192G。 每台机器给 ES JVM Heap 是 32G,那么剩下来留给 Filesystem Cache 的就是每台机器才 32G,总共集群里给 Filesystem Cache 的就是 32 * 3 = 96G 内存。 ...

May 30, 2019 · 2 min · jiezi

聊聊Elasticsearch的TaskScheduler

序本文主要研究一下Elasticsearch的TaskScheduler TaskSchedulerelasticsearch-7.0.1/libs/nio/src/main/java/org/elasticsearch/nio/TaskScheduler.java public class TaskScheduler { private final PriorityQueue<DelayedTask> tasks = new PriorityQueue<>(Comparator.comparingLong(DelayedTask::getDeadline)); /** * Schedule a task at the defined relative nanotime. When {@link #pollTask(long)} is called with a * relative nanotime after the scheduled time, the task will be returned. This method returns a * {@link Runnable} that can be run to cancel the scheduled task. * * @param task to schedule * @param relativeNanos defining when to execute the task * @return runnable that will cancel the task */ public Runnable scheduleAtRelativeTime(Runnable task, long relativeNanos) { DelayedTask delayedTask = new DelayedTask(relativeNanos, task); tasks.offer(delayedTask); return delayedTask; } Runnable pollTask(long relativeNanos) { DelayedTask task; while ((task = tasks.peek()) != null) { if (relativeNanos - task.deadline >= 0) { tasks.remove(); if (task.cancelled == false) { return task.runnable; } } else { return null; } } return null; } long nanosUntilNextTask(long relativeNanos) { DelayedTask nextTask = tasks.peek(); if (nextTask == null) { return Long.MAX_VALUE; } else { return Math.max(nextTask.deadline - relativeNanos, 0); } } private static class DelayedTask implements Runnable { private final long deadline; private final Runnable runnable; private boolean cancelled = false; private DelayedTask(long deadline, Runnable runnable) { this.deadline = deadline; this.runnable = runnable; } private long getDeadline() { return deadline; } @Override public void run() { cancelled = true; } }}TaskScheduler定义了DelayedTask,它实现了Runnable接口,它包含deadline、runnable、cancelled三个属性TaskScheduler定义了DelayedTask类型的PriorityQueue,其comparator为Comparator.comparingLong(DelayedTask::getDeadline)scheduleAtRelativeTime方法将runnable包装为delayedTask,然后offer到priorityQueue中;pollTask则peek出来task,如果不为null则判断relativeNanos是否大于等于task.deadline,条件成立的话则将其从tasks中移除,然后在cancelled为false的时候返回task.runnableSSLChannelContextelasticsearch-7.0.1/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/nio/SSLChannelContext.java ...

May 30, 2019 · 3 min · jiezi

elasticsearch学习笔记高级篇一在案例中实战使用term-filter来搜索数据

下面以IT技术论坛为案例背景 1、根据用户ID、是否隐藏、帖子ID、发帖日期来搜索帖子(1)插入一些测试帖子的数据POST /forum/_bulk{"index": {"_id": 1}}{ "articleID" : "XHDK-A-1293-#fJ3", "userID" : 1, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 2 }}{ "articleID" : "KDKE-B-9947-#kL5", "userID" : 1, "hidden": false, "postDate": "2017-01-02" }{"index": {"_id": 3}}{ "articleID" : "JODL-X-1937-#pV7", "userID" : 2, "hidden": false, "postDate": "2017-01-01" }{ "index": { "_id": 4 }}{ "articleID" : "QQPX-R-3956-#aD8", "userID" : 2, "hidden": true, "postDate": "2017-01-02" }输出: { "took" : 269, "errors" : false, "items" : [ { "index" : { "_index" : "forum", "_type" : "_doc", "_id" : "1", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 0, "_primary_term" : 1, "status" : 201 } }, { "index" : { "_index" : "forum", "_type" : "_doc", "_id" : "2", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 1, "_primary_term" : 1, "status" : 201 } }, { "index" : { "_index" : "forum", "_type" : "_doc", "_id" : "3", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 2, "_primary_term" : 1, "status" : 201 } }, { "index" : { "_index" : "forum", "_type" : "_doc", "_id" : "4", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 3, "_primary_term" : 1, "status" : 201 } } ]}注意:初步来讲,先搞4个字段,因为整个es是支持json document格式的,所以说扩展性和灵活性非常之好。如果后续随着业务需求的增加,要在document中增加更多的field,那么我们可以很方便的随时添加field。但是如果是在关系型数据库中,比如mysql,我们建立了一个表,现在要给表中新增一些column,那么就很坑爹了,必须用复杂的修改表结构的语法去执行。而且可能对系统代码还有一定的影响。 ...

May 29, 2019 · 5 min · jiezi

聊聊ElasticsearchUncaughtExceptionHandler

序本文主要研究一下ElasticsearchUncaughtExceptionHandler ElasticsearchUncaughtExceptionHandlerclass ElasticsearchUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { private static final Logger logger = LogManager.getLogger(ElasticsearchUncaughtExceptionHandler.class); @Override public void uncaughtException(Thread t, Throwable e) { if (isFatalUncaught(e)) { try { onFatalUncaught(t.getName(), e); } finally { // we use specific error codes in case the above notification failed, at least we // will have some indication of the error bringing us down if (e instanceof InternalError) { halt(128); } else if (e instanceof OutOfMemoryError) { halt(127); } else if (e instanceof StackOverflowError) { halt(126); } else if (e instanceof UnknownError) { halt(125); } else if (e instanceof IOError) { halt(124); } else { halt(1); } } } else { onNonFatalUncaught(t.getName(), e); } } static boolean isFatalUncaught(Throwable e) { return e instanceof Error; } void onFatalUncaught(final String threadName, final Throwable t) { logger.error(() -> new ParameterizedMessage("fatal error in thread [{}], exiting", threadName), t); } void onNonFatalUncaught(final String threadName, final Throwable t) { logger.warn(() -> new ParameterizedMessage("uncaught exception in thread [{}]", threadName), t); } void halt(int status) { AccessController.doPrivileged(new PrivilegedHaltAction(status)); } static class PrivilegedHaltAction implements PrivilegedAction<Void> { private final int status; private PrivilegedHaltAction(final int status) { this.status = status; } @SuppressForbidden(reason = "halt") @Override public Void run() { // we halt to prevent shutdown hooks from running Runtime.getRuntime().halt(status); return null; } }}ElasticsearchUncaughtExceptionHandler实现了Thread.UncaughtExceptionHandler接口uncaughtException方法首先判断throwable是否是Error类型,是的话则执行onFatalUncaught(logger.error),然后执行halt方法,不是则执行onNonFatalUncaught(logger.warn)halt方法AccessController.doPrivileged来执行对应status的PrivilegedHaltAction,该action执行的是Runtime.getRuntime().halt(status)Bootstrap.initelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java ...

May 29, 2019 · 3 min · jiezi

12Laravel全文搜索Elasticsearch-三

使用Elasticsearch搜索引擎,配置ik中文分词,与Laravel模型关联,然后实现搜索的业务逻辑。本篇是结束篇,使用Laravel的Scout扩展包完成搜索功能续上篇,已经安装和配置好了Scout和支持Elastic的扩展包 编辑Article模型,将LaravelScoutSearchable 这个 trait加到你想要做检索的模型,这个trait会注册一个模型观察者来保持模型同步到检索服务的驱动: <?phpnamespace App;use Illuminate\Database\Eloquent\Model;use Laravel\Scout\Searchable;class Article extends Model{ // 引入这个trait,这个trait会注册一个模型观察者来保持模型同步到检索服务的驱动 use Searchable;//... // 定义索引里面的type(类型)-- es中类型相当于mysql中的表 public function searchableAs() { return 'article'; } // 定义有哪些字段需要搜索 public function toSearchableArray() { return [ 'id' => $this->id, 'title' => $this->title, 'content' => $this->content ]; }//...}使用aritsan命令,从mysql导入现有数据到ElasticSearch php artisan scout:import查看一下ElasticSearch中是否存在配置的索引,和导入的数据大小 curl 'localhost:9200/_cat/indices?v'ElasticSearch的一些RESTful api调用方式,可以用来测试数据 查看索引的配置 curl -XGET "http://localhost:9200/mi360?pretty=true"查看文档列表 curl -XGET "http://localhost:9200/mi360/_search?pretty=true"查看指定id=10的文档 curl -XGET "http://localhost:9200/mi360/article/10?pretty=true"ok!导入成功后,开始写搜索业务逻辑了 添加路由 Route::get('/search', 'WelcomeController@search');编辑视图文件中的form表单,提交到路由的地址,并且input表单的name=query <form action="{{ url('/search') }}" class="search fr"> <input type="text" name="query" placeholder="客官,想搜点啥?"> <button type="submit">搜索</button></form>编写控制器 ...

May 29, 2019 · 2 min · jiezi

聊聊Elasticsearch的BootstrapCheck

序本文主要研究一下Elasticsearch的BootstrapCheck BootstrapCheckelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/bootstrap/BootstrapCheck.java public interface BootstrapCheck { /** * Encapsulate the result of a bootstrap check. */ final class BootstrapCheckResult { private final String message; private static final BootstrapCheckResult SUCCESS = new BootstrapCheckResult(null); public static BootstrapCheckResult success() { return SUCCESS; } public static BootstrapCheckResult failure(final String message) { Objects.requireNonNull(message); return new BootstrapCheckResult(message); } private BootstrapCheckResult(final String message) { this.message = message; } public boolean isSuccess() { return this == SUCCESS; } public boolean isFailure() { return !isSuccess(); } public String getMessage() { assert isFailure(); assert message != null; return message; } } /** * Test if the node fails the check. * * @param context the bootstrap context * @return the result of the bootstrap check */ BootstrapCheckResult check(BootstrapContext context); default boolean alwaysEnforce() { return false; }}BootstrapCheck接口定义了check方法,该方法返回BootstrapCheckResult,另外还定义了一个default方法alwaysEnforce,默认返回falseBootstrapCheckselasticsearch-7.0.1/server/src/main/java/org/elasticsearch/bootstrap/BootstrapChecks.java ...

May 29, 2019 · 3 min · jiezi

聊聊Elasticsearch的DiscoveryPlugin

序本文主要研究一下Elasticsearch的DiscoveryPlugin DiscoveryPluginelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/plugins/DiscoveryPlugin.java public interface DiscoveryPlugin { /** * Override to add additional {@link NetworkService.CustomNameResolver}s. * This can be handy if you want to provide your own Network interface name like _mycard_ * and implement by yourself the logic to get an actual IP address/hostname based on this * name. * * For example: you could call a third party service (an API) to resolve _mycard_. * Then you could define in elasticsearch.yml settings like: * * <pre>{@code * network.host: _mycard_ * }</pre> */ default NetworkService.CustomNameResolver getCustomNameResolver(Settings settings) { return null; } /** * Returns providers of seed hosts for discovery. * * The key of the returned map is the name of the host provider * (see {@link org.elasticsearch.discovery.DiscoveryModule#DISCOVERY_SEED_PROVIDERS_SETTING}), and * the value is a supplier to construct the host provider when it is selected for use. * * @param transportService Use to form the {@link org.elasticsearch.common.transport.TransportAddress} portion * of a {@link org.elasticsearch.cluster.node.DiscoveryNode} * @param networkService Use to find the publish host address of the current node */ default Map<String, Supplier<SeedHostsProvider>> getSeedHostProviders(TransportService transportService, NetworkService networkService) { return Collections.emptyMap(); } /** * Returns a consumer that validate the initial join cluster state. The validator, unless <code>null</code> is called exactly once per * join attempt but might be called multiple times during the lifetime of a node. Validators are expected to throw a * {@link IllegalStateException} if the node and the cluster-state are incompatible. */ default BiConsumer<DiscoveryNode,ClusterState> getJoinValidator() { return null; }}DiscoveryPlugin定义了getCustomNameResolver、getSeedHostProviders、getJoinValidator三个default方法GceDiscoveryPluginelasticsearch-7.0.1/plugins/discovery-gce/src/main/java/org/elasticsearch/plugin/discovery/gce/GceDiscoveryPlugin.java ...

May 28, 2019 · 5 min · jiezi

es优化

1 查看es状态信息的常用命令#查询集群状态GET _cluster/health#查询每个节点堆设置的大小GET _cat/nodes?h=heap.max#查询某个索引的分片、段以及所内存GET _cat/segments/logstash-info-2019.05.22?v&h=shard,segment,size,size.momery#查询node上所有segment占用的memory的总和GET /_cat/nodes?v&h=name,port,sm减少数据节点上segment内存占用的方式删除不用的索引关闭索引(文件仍然存在于磁盘,只是释放掉内存),需要时可以打开。定期对不再更新的索引force merge。ES的heap是如何被瓜分掉的?segment memoryfilter cachefield data cachebulk queueindexing bufferstate buffer超大搜索聚合结果集的fetch对高cardinality字段做terms aggregationgc overhead导致数据节点脱离集群笔者线上的heap设置了32G,导致gc的时间过长,从而使节点脱离了集群。如果把heap size设置的过小,GC太过频繁,会影响ES入库和搜索的效率 。通过增加ping_timeout的时间,和增加ping_retries的次数来防止节点错误的脱离集群,可以使节点有充足的时间进行full GC。可以通过调整参数来优化。 discovery.zen.fd.ping_timeout: 1000sdiscovery.zen.fd.ping_retries: 10当然,最好的优化方式,是改进垃圾回收方式,改用G1GC。 参考文章:https://www.cnblogs.com/bonel...https://www.elastic.co/cn/blo...https://blog.csdn.net/Nonoroy...

May 27, 2019 · 1 min · jiezi

聊聊Elasticsearch的MonitorService

序本文主要研究一下Elasticsearch的MonitorService MonitorServiceelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/MonitorService.java public class MonitorService extends AbstractLifecycleComponent { private final JvmGcMonitorService jvmGcMonitorService; private final OsService osService; private final ProcessService processService; private final JvmService jvmService; private final FsService fsService; public MonitorService(Settings settings, NodeEnvironment nodeEnvironment, ThreadPool threadPool, ClusterInfoService clusterInfoService) throws IOException { this.jvmGcMonitorService = new JvmGcMonitorService(settings, threadPool); this.osService = new OsService(settings); this.processService = new ProcessService(settings); this.jvmService = new JvmService(settings); this.fsService = new FsService(settings, nodeEnvironment, clusterInfoService); } public OsService osService() { return this.osService; } public ProcessService processService() { return this.processService; } public JvmService jvmService() { return this.jvmService; } public FsService fsService() { return this.fsService; } @Override protected void doStart() { jvmGcMonitorService.start(); } @Override protected void doStop() { jvmGcMonitorService.stop(); } @Override protected void doClose() { jvmGcMonitorService.close(); }}MonitorService的构造器创建了jvmGcMonitorService、osService、processService、jvmService、fsService;其doStart、doStop、doClose分别调用了jvmGcMonitorService的start、stop、close方法JvmGcMonitorServiceelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/jvm/JvmGcMonitorService.java ...

May 26, 2019 · 5 min · jiezi

聊聊Elasticsearch的JvmStats

序本文主要研究一下Elasticsearch的JvmStats JvmStatselasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/jvm/JvmStats.java public class JvmStats implements Writeable, ToXContentFragment { private static final RuntimeMXBean runtimeMXBean; private static final MemoryMXBean memoryMXBean; private static final ThreadMXBean threadMXBean; private static final ClassLoadingMXBean classLoadingMXBean; static { runtimeMXBean = ManagementFactory.getRuntimeMXBean(); memoryMXBean = ManagementFactory.getMemoryMXBean(); threadMXBean = ManagementFactory.getThreadMXBean(); classLoadingMXBean = ManagementFactory.getClassLoadingMXBean(); } public static JvmStats jvmStats() { MemoryUsage memUsage = memoryMXBean.getHeapMemoryUsage(); long heapUsed = memUsage.getUsed() < 0 ? 0 : memUsage.getUsed(); long heapCommitted = memUsage.getCommitted() < 0 ? 0 : memUsage.getCommitted(); long heapMax = memUsage.getMax() < 0 ? 0 : memUsage.getMax(); memUsage = memoryMXBean.getNonHeapMemoryUsage(); long nonHeapUsed = memUsage.getUsed() < 0 ? 0 : memUsage.getUsed(); long nonHeapCommitted = memUsage.getCommitted() < 0 ? 0 : memUsage.getCommitted(); List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans(); List<MemoryPool> pools = new ArrayList<>(); for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans) { try { MemoryUsage usage = memoryPoolMXBean.getUsage(); MemoryUsage peakUsage = memoryPoolMXBean.getPeakUsage(); String name = GcNames.getByMemoryPoolName(memoryPoolMXBean.getName(), null); if (name == null) { // if we can't resolve it, its not interesting.... (Per Gen, Code Cache) continue; } pools.add(new MemoryPool(name, usage.getUsed() < 0 ? 0 : usage.getUsed(), usage.getMax() < 0 ? 0 : usage.getMax(), peakUsage.getUsed() < 0 ? 0 : peakUsage.getUsed(), peakUsage.getMax() < 0 ? 0 : peakUsage.getMax() )); } catch (final Exception ignored) { } } Mem mem = new Mem(heapCommitted, heapUsed, heapMax, nonHeapCommitted, nonHeapUsed, Collections.unmodifiableList(pools)); Threads threads = new Threads(threadMXBean.getThreadCount(), threadMXBean.getPeakThreadCount()); List<GarbageCollectorMXBean> gcMxBeans = ManagementFactory.getGarbageCollectorMXBeans(); GarbageCollector[] collectors = new GarbageCollector[gcMxBeans.size()]; for (int i = 0; i < collectors.length; i++) { GarbageCollectorMXBean gcMxBean = gcMxBeans.get(i); collectors[i] = new GarbageCollector(GcNames.getByGcName(gcMxBean.getName(), gcMxBean.getName()), gcMxBean.getCollectionCount(), gcMxBean.getCollectionTime()); } GarbageCollectors garbageCollectors = new GarbageCollectors(collectors); List<BufferPool> bufferPoolsList = Collections.emptyList(); try { List<BufferPoolMXBean> bufferPools = ManagementFactory.getPlatformMXBeans(BufferPoolMXBean.class); bufferPoolsList = new ArrayList<>(bufferPools.size()); for (BufferPoolMXBean bufferPool : bufferPools) { bufferPoolsList.add(new BufferPool(bufferPool.getName(), bufferPool.getCount(), bufferPool.getTotalCapacity(), bufferPool.getMemoryUsed())); } } catch (Exception e) { // buffer pools are not available } Classes classes = new Classes(classLoadingMXBean.getLoadedClassCount(), classLoadingMXBean.getTotalLoadedClassCount(), classLoadingMXBean.getUnloadedClassCount()); return new JvmStats(System.currentTimeMillis(), runtimeMXBean.getUptime(), mem, threads, garbageCollectors, bufferPoolsList, classes); } private final long timestamp; private final long uptime; private final Mem mem; private final Threads threads; private final GarbageCollectors gc; private final List<BufferPool> bufferPools; private final Classes classes; public JvmStats(long timestamp, long uptime, Mem mem, Threads threads, GarbageCollectors gc, List<BufferPool> bufferPools, Classes classes) { this.timestamp = timestamp; this.uptime = uptime; this.mem = mem; this.threads = threads; this.gc = gc; this.bufferPools = bufferPools; this.classes = classes; } public JvmStats(StreamInput in) throws IOException { timestamp = in.readVLong(); uptime = in.readVLong(); mem = new Mem(in); threads = new Threads(in); gc = new GarbageCollectors(in); bufferPools = in.readList(BufferPool::new); classes = new Classes(in); } @Override public void writeTo(StreamOutput out) throws IOException { out.writeVLong(timestamp); out.writeVLong(uptime); mem.writeTo(out); threads.writeTo(out); gc.writeTo(out); out.writeList(bufferPools); classes.writeTo(out); } public long getTimestamp() { return timestamp; } public TimeValue getUptime() { return new TimeValue(uptime); } public Mem getMem() { return this.mem; } public Threads getThreads() { return threads; } public GarbageCollectors getGc() { return gc; } public List<BufferPool> getBufferPools() { return bufferPools; } public Classes getClasses() { return classes; } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(Fields.JVM); builder.field(Fields.TIMESTAMP, timestamp); builder.humanReadableField(Fields.UPTIME_IN_MILLIS, Fields.UPTIME, new TimeValue(uptime)); builder.startObject(Fields.MEM); builder.humanReadableField(Fields.HEAP_USED_IN_BYTES, Fields.HEAP_USED, new ByteSizeValue(mem.heapUsed)); if (mem.getHeapUsedPercent() >= 0) { builder.field(Fields.HEAP_USED_PERCENT, mem.getHeapUsedPercent()); } builder.humanReadableField(Fields.HEAP_COMMITTED_IN_BYTES, Fields.HEAP_COMMITTED, new ByteSizeValue(mem.heapCommitted)); builder.humanReadableField(Fields.HEAP_MAX_IN_BYTES, Fields.HEAP_MAX, new ByteSizeValue(mem.heapMax)); builder.humanReadableField(Fields.NON_HEAP_USED_IN_BYTES, Fields.NON_HEAP_USED, new ByteSizeValue(mem.nonHeapUsed)); builder.humanReadableField(Fields.NON_HEAP_COMMITTED_IN_BYTES, Fields.NON_HEAP_COMMITTED, new ByteSizeValue(mem.nonHeapCommitted)); builder.startObject(Fields.POOLS); for (MemoryPool pool : mem) { builder.startObject(pool.getName()); builder.humanReadableField(Fields.USED_IN_BYTES, Fields.USED, new ByteSizeValue(pool.used)); builder.humanReadableField(Fields.MAX_IN_BYTES, Fields.MAX, new ByteSizeValue(pool.max)); builder.humanReadableField(Fields.PEAK_USED_IN_BYTES, Fields.PEAK_USED, new ByteSizeValue(pool.peakUsed)); builder.humanReadableField(Fields.PEAK_MAX_IN_BYTES, Fields.PEAK_MAX, new ByteSizeValue(pool.peakMax)); builder.endObject(); } builder.endObject(); builder.endObject(); builder.startObject(Fields.THREADS); builder.field(Fields.COUNT, threads.getCount()); builder.field(Fields.PEAK_COUNT, threads.getPeakCount()); builder.endObject(); builder.startObject(Fields.GC); builder.startObject(Fields.COLLECTORS); for (GarbageCollector collector : gc) { builder.startObject(collector.getName()); builder.field(Fields.COLLECTION_COUNT, collector.getCollectionCount()); builder.humanReadableField(Fields.COLLECTION_TIME_IN_MILLIS, Fields.COLLECTION_TIME, new TimeValue(collector.collectionTime)); builder.endObject(); } builder.endObject(); builder.endObject(); if (bufferPools != null) { builder.startObject(Fields.BUFFER_POOLS); for (BufferPool bufferPool : bufferPools) { builder.startObject(bufferPool.getName()); builder.field(Fields.COUNT, bufferPool.getCount()); builder.humanReadableField(Fields.USED_IN_BYTES, Fields.USED, new ByteSizeValue(bufferPool.used)); builder.humanReadableField(Fields.TOTAL_CAPACITY_IN_BYTES, Fields.TOTAL_CAPACITY, new ByteSizeValue(bufferPool.totalCapacity)); builder.endObject(); } builder.endObject(); } builder.startObject(Fields.CLASSES); builder.field(Fields.CURRENT_LOADED_COUNT, classes.getLoadedClassCount()); builder.field(Fields.TOTAL_LOADED_COUNT, classes.getTotalLoadedClassCount()); builder.field(Fields.TOTAL_UNLOADED_COUNT, classes.getUnloadedClassCount()); builder.endObject(); builder.endObject(); return builder; } //......}JvmStats定义了timestamp、uptime、mem、threads、gc、bufferPools、classes属性jvmStats方法返回JvmStats对象,它从runtimeMXBean读取uptime,从memoryMXBean读取memUsage信息,从memoryPoolMXBeans读取memoryPool信息,从threadMXBean读取threads信息,从classLoadingMXBean读取classes信息JvmStats定义了Fields、GarbageCollectors、GarbageCollector、Threads、MemoryPool、Mem、BufferPool、Classes静态类Fieldselasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/jvm/JvmStats.java ...

May 25, 2019 · 8 min · jiezi

聊聊Elasticsearch的FsProbe

序本文主要研究一下Elasticsearch的FsProbe FsProbeelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/fs/FsProbe.java public class FsProbe { private static final Logger logger = LogManager.getLogger(FsProbe.class); private final NodeEnvironment nodeEnv; public FsProbe(NodeEnvironment nodeEnv) { this.nodeEnv = nodeEnv; } public FsInfo stats(FsInfo previous, @Nullable ClusterInfo clusterInfo) throws IOException { if (!nodeEnv.hasNodeFile()) { return new FsInfo(System.currentTimeMillis(), null, new FsInfo.Path[0]); } NodePath[] dataLocations = nodeEnv.nodePaths(); FsInfo.Path[] paths = new FsInfo.Path[dataLocations.length]; for (int i = 0; i < dataLocations.length; i++) { paths[i] = getFSInfo(dataLocations[i]); } FsInfo.IoStats ioStats = null; if (Constants.LINUX) { Set<Tuple<Integer, Integer>> devicesNumbers = new HashSet<>(); for (int i = 0; i < dataLocations.length; i++) { if (dataLocations[i].majorDeviceNumber != -1 && dataLocations[i].minorDeviceNumber != -1) { devicesNumbers.add(Tuple.tuple(dataLocations[i].majorDeviceNumber, dataLocations[i].minorDeviceNumber)); } } ioStats = ioStats(devicesNumbers, previous); } DiskUsage leastDiskEstimate = null; DiskUsage mostDiskEstimate = null; if (clusterInfo != null) { leastDiskEstimate = clusterInfo.getNodeLeastAvailableDiskUsages().get(nodeEnv.nodeId()); mostDiskEstimate = clusterInfo.getNodeMostAvailableDiskUsages().get(nodeEnv.nodeId()); } return new FsInfo(System.currentTimeMillis(), ioStats, paths, leastDiskEstimate, mostDiskEstimate); } final FsInfo.IoStats ioStats(final Set<Tuple<Integer, Integer>> devicesNumbers, final FsInfo previous) { try { final Map<Tuple<Integer, Integer>, FsInfo.DeviceStats> deviceMap = new HashMap<>(); if (previous != null && previous.getIoStats() != null && previous.getIoStats().devicesStats != null) { for (int i = 0; i < previous.getIoStats().devicesStats.length; i++) { FsInfo.DeviceStats deviceStats = previous.getIoStats().devicesStats[i]; deviceMap.put(Tuple.tuple(deviceStats.majorDeviceNumber, deviceStats.minorDeviceNumber), deviceStats); } } List<FsInfo.DeviceStats> devicesStats = new ArrayList<>(); List<String> lines = readProcDiskStats(); if (!lines.isEmpty()) { for (String line : lines) { String fields[] = line.trim().split("\\s+"); final int majorDeviceNumber = Integer.parseInt(fields[0]); final int minorDeviceNumber = Integer.parseInt(fields[1]); if (!devicesNumbers.contains(Tuple.tuple(majorDeviceNumber, minorDeviceNumber))) { continue; } final String deviceName = fields[2]; final long readsCompleted = Long.parseLong(fields[3]); final long sectorsRead = Long.parseLong(fields[5]); final long writesCompleted = Long.parseLong(fields[7]); final long sectorsWritten = Long.parseLong(fields[9]); final FsInfo.DeviceStats deviceStats = new FsInfo.DeviceStats( majorDeviceNumber, minorDeviceNumber, deviceName, readsCompleted, sectorsRead, writesCompleted, sectorsWritten, deviceMap.get(Tuple.tuple(majorDeviceNumber, minorDeviceNumber))); devicesStats.add(deviceStats); } } return new FsInfo.IoStats(devicesStats.toArray(new FsInfo.DeviceStats[devicesStats.size()])); } catch (Exception e) { // do not fail Elasticsearch if something unexpected // happens here logger.debug(() -> new ParameterizedMessage( "unexpected exception processing /proc/diskstats for devices {}", devicesNumbers), e); return null; } } @SuppressForbidden(reason = "read /proc/diskstats") List<String> readProcDiskStats() throws IOException { return Files.readAllLines(PathUtils.get("/proc/diskstats")); } /* See: https://bugs.openjdk.java.net/browse/JDK-8162520 */ /** * Take a large value intended to be positive, and if it has overflowed, * return {@code Long.MAX_VALUE} instead of a negative number. */ static long adjustForHugeFilesystems(long bytes) { if (bytes < 0) { return Long.MAX_VALUE; } return bytes; } public static FsInfo.Path getFSInfo(NodePath nodePath) throws IOException { FsInfo.Path fsPath = new FsInfo.Path(); fsPath.path = nodePath.path.toAbsolutePath().toString(); // NOTE: we use already cached (on node startup) FileStore and spins // since recomputing these once per second (default) could be costly, // and they should not change: fsPath.total = adjustForHugeFilesystems(nodePath.fileStore.getTotalSpace()); fsPath.free = adjustForHugeFilesystems(nodePath.fileStore.getUnallocatedSpace()); fsPath.available = adjustForHugeFilesystems(nodePath.fileStore.getUsableSpace()); fsPath.type = nodePath.fileStore.type(); fsPath.mount = nodePath.fileStore.toString(); return fsPath; }}FsProbe提供了stats、ioStats、readProcDiskStats等方法;其中readProcDiskStats方法主要是读取/proc/diskstats的数据stats方法返回FsInfo,它包含了ioStats、leastDiskEstimate、mostDiskEstimate,其中ioStats是通过ioStats方法获取,而leastDiskEstimate及mostDiskEstimate则是通过clusterInfo.getNodeLeastAvailableDiskUsages()及clusterInfo.getNodeMostAvailableDiskUsages()获取ioStats方法则通过readProcDiskStats读取diskstats信息,构造FsInfo.DeviceStats,从而构造FsInfo.IoStats返回FsInfoelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/fs/FsInfo.java ...

May 24, 2019 · 9 min · jiezi

聊聊elasticsearch的DeadlockAnalyzer

序本文主要研究一下elasticsearch的DeadlockAnalyzer DeadlockAnalyzerelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/jvm/DeadlockAnalyzer.java public class DeadlockAnalyzer { private static final Deadlock NULL_RESULT[] = new Deadlock[0]; private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); private static DeadlockAnalyzer INSTANCE = new DeadlockAnalyzer(); public static DeadlockAnalyzer deadlockAnalyzer() { return INSTANCE; } private DeadlockAnalyzer() { } public Deadlock[] findDeadlocks() { long deadlockedThreads[] = threadBean.findMonitorDeadlockedThreads(); if (deadlockedThreads == null || deadlockedThreads.length == 0) { return NULL_RESULT; } Map<Long, ThreadInfo> threadInfoMap = createThreadInfoMap(deadlockedThreads); Set<LinkedHashSet<ThreadInfo>> cycles = calculateCycles(threadInfoMap); Set<LinkedHashSet<ThreadInfo>> chains = calculateCycleDeadlockChains(threadInfoMap, cycles); cycles.addAll(chains); return createDeadlockDescriptions(cycles); } private Deadlock[] createDeadlockDescriptions(Set<LinkedHashSet<ThreadInfo>> cycles) { Deadlock result[] = new Deadlock[cycles.size()]; int count = 0; for (LinkedHashSet<ThreadInfo> cycle : cycles) { ThreadInfo asArray[] = cycle.toArray(new ThreadInfo[cycle.size()]); Deadlock d = new Deadlock(asArray); result[count++] = d; } return result; } private Set<LinkedHashSet<ThreadInfo>> calculateCycles(Map<Long, ThreadInfo> threadInfoMap) { Set<LinkedHashSet<ThreadInfo>> cycles = new HashSet<>(); for (Map.Entry<Long, ThreadInfo> entry : threadInfoMap.entrySet()) { LinkedHashSet<ThreadInfo> cycle = new LinkedHashSet<>(); for (ThreadInfo t = entry.getValue(); !cycle.contains(t); t = threadInfoMap.get(Long.valueOf(t.getLockOwnerId()))) { cycle.add(t); } if (!cycles.contains(cycle)) { cycles.add(cycle); } } return cycles; } private Set<LinkedHashSet<ThreadInfo>> calculateCycleDeadlockChains(Map<Long, ThreadInfo> threadInfoMap, Set<LinkedHashSet<ThreadInfo>> cycles) { ThreadInfo allThreads[] = threadBean.getThreadInfo(threadBean.getAllThreadIds()); Set<LinkedHashSet<ThreadInfo>> deadlockChain = new HashSet<>(); Set<Long> knownDeadlockedThreads = threadInfoMap.keySet(); for (ThreadInfo threadInfo : allThreads) { Thread.State state = threadInfo.getThreadState(); if (state == Thread.State.BLOCKED && !knownDeadlockedThreads.contains(threadInfo.getThreadId())) { for (LinkedHashSet<ThreadInfo> cycle : cycles) { if (cycle.contains(threadInfoMap.get(Long.valueOf(threadInfo.getLockOwnerId())))) { LinkedHashSet<ThreadInfo> chain = new LinkedHashSet<>(); ThreadInfo node = threadInfo; while (!chain.contains(node)) { chain.add(node); node = threadInfoMap.get(Long.valueOf(node.getLockOwnerId())); } deadlockChain.add(chain); } } } } return deadlockChain; } private Map<Long, ThreadInfo> createThreadInfoMap(long threadIds[]) { ThreadInfo threadInfos[] = threadBean.getThreadInfo(threadIds); Map<Long, ThreadInfo> threadInfoMap = new HashMap<>(); for (ThreadInfo threadInfo : threadInfos) { threadInfoMap.put(threadInfo.getThreadId(), threadInfo); } return unmodifiableMap(threadInfoMap); } //......}DeadlockAnalyzer提供了findDeadlocks方法用于返回死锁线程的信息,该方法通过ThreadMXBean的findMonitorDeadlockedThreads方法获取deadlockedThreads数组,如果该数组为null或空,则返回NULL_RESULT,否则往下计算createThreadInfoMap方法根据threadIds从ThreadMXBean获取对应的threadInfo信息,然后组装成threadId与threadInfo的map;calculateCycles方法则是遍历该map,然后根据threadInfo的getLockOwnerId()构建cyclescalculateCycleDeadlockChains方法则根据threadInfoMap及cycles构建cycleDeadlockChains,添加到cycles中,最后通过createDeadlockDescriptions方法构建Deadlock数组Deadlockelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/jvm/DeadlockAnalyzer.java ...

May 23, 2019 · 3 min · jiezi

Elasticsearch搜索相关性排序算法详解

前言说明:本文章使用的ES版本是:6.2.4 在上一篇文章Elasticsearch搜索过程详解中,介绍了ES的搜索过程。 接下来我们具体的看一下ES搜索时,是如何计算文档相关性得分并用于排序的。 TF-IDF在介绍ES计算文档得分之前,先来看一下TF-IDF算法。 TF-IDF(Term Frequency–Inverse Document Frequency)是一种用于信息检索与文本挖掘的常用加权算法。它是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。 TF-IDF算法原理TF-IDF实际上是两个算法TF和IDF的乘积。 词频(Term Frequency,TF)词频的所在对象是一个具体的文档,是指一个文档中出现某个单词(Term)的频率(Frequency)。这里用的是频率而不是次数,是为了防止文档内容过长从而导致某些单词出现过多。为了正确评价一个单词在一个文档中的重要程度,需要将出现次数归一化,其算法如下: $$tf_i=\frac{n_i}{\sum\nolimits_{k=1}^nn_k}$$ 上面式子中n_i是该词在文件中的出现次数,而分母则是在文件中所有字词的出现次数之和。 逆向文件频率(Inverse Document Frequency,IDF)逆向文件频率描述的对象是一个文档集合中,包含某个单词的文档数量。它表示的是一个单词在一个文档集合中的普遍重要程度。将其归一化的算法入下: $${idf_{i}} =\lg {\frac {|D|}{1+|\{j:t_{i}\in d_{j}\}|}}$$ 其中 |D|:表示文档集合中的文件总数分母:包含词语t_i的文件数目(即n_i不等于0的文件数目)如果词语不在数据中,就导致分母为零,因此一般情况下使用分母加了一个1最后 $$tfidf_i=tf_i\times idf_i$$ 某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的tf-idf。因此,tf-idf倾向于过滤掉常见的词语,保留重要的词语。 TF—IDF总结TF表示的是一个单词在一段文本中的重要程度,随着单词的增加而增加IDF表示的是一个单词在一个文档集合中的重要程度,越稀有权重越高,所以它随着单词的增加而降低TF-IDF算法举例用上面的公式,计算一个例子。 假如一篇文件的总词语数是100个,而词语“学校”出现了5次,那么“学校”一词在该文件中的词频(tf)就是 $$tf_i=5/100=0.05$$ “学校”一词在1,000份文件出现过,而文件总数是1,000,000份的话,其逆向文件频率就是 $$idf_i = lg(1,000,000 / 1,000)=3$$ 最后的tf-idf的分数为 $$tfidf_i = tf_i\times idf_i = 0.05 \times 3= 0.15$$ OKapi BM25算法原理BM25(Best Match25)是在信息检索系统中根据提出的query对document进行评分的算法。 TF-IDF算法是一个可用的算法,但并不太完美。它给出了一个基于统计学的相关分数算法,而BM25算法则是在此之上做出改进之后的算法。(为什么要改进呢?TF-IDF不完美的地方在哪里?) 当两篇描述“人工智能”的文档A和B,其中A出现“人工智能”100次,B出现“人工智能”200次。两篇文章的单词数量都是10000,那么按照TF-IDF算法,A的tf得分是:0.01,B的tf得分是0.02。得分上B比A多了一倍,但是两篇文章都是再说人工智能,tf分数不应该相差这么多。可见单纯统计的tf算法在文本内容多的时候是不可靠的多篇文档内容的长度长短不同,对tf算法的结果也影响很大,所以需要将文本的长度也考虑到算法当中去基于上面两点,BM25算法做出了改进,最终该算法公式如下: $${\displaystyle {\text{score}}(D,Q)=\sum _{i=1}^{n}{\text{IDF}}(q_{i})\cdot {\frac {f(q_{i},D)\cdot (k_{1}+1)}{f(q_{i},D)+k_{1}\cdot \left(1-b+b\cdot {\frac {|D|}{\text{avgdl}}}\right)}}}$$ 其中: Q:文档集合D:具体的文档IDF(q_i):就是TF-IDF中的IDF,表示单词q_i在文档集合Q的IDF值f(q_i,D):就是TF-IDF中的TF,表示单词q_i在文档D中的TF值k_1:词语频率饱和度(term frequency saturation)它用于调节饱和度变化的速率。它的值一般介于 1.2 到 2.0 之间。数值越低则饱和的过程越快速。(意味着两个上面A、B两个文档有相同的分数,因为他们都包含大量的“人工智能”这个词语)。在ES应用中为1.2b:字段长度归约,将文档的长度归约化到全部文档的平均长度,它的值在 0 和 1 之间,1 意味着全部归约化,0 则不进行归约化。在ES的应用中为0.75|D|:文本长度avgdl:所有的文档集合中,被搜索的文本的平均长度Lucene相关性算法注:ES版本6.2.4所用的Lucene jar包版本是:7.2.1在了解了TF-IDF算法之后,再来了解Lucene中的相关性算法就很好理解了。 ...

May 23, 2019 · 1 min · jiezi

聊聊Elasticsearch的OsProbe

序本文主要研究一下Elasticsearch的OsProbe OsProbeelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/os/OsProbe.java public class OsProbe { private static final OperatingSystemMXBean osMxBean = ManagementFactory.getOperatingSystemMXBean(); private static final Method getFreePhysicalMemorySize; private static final Method getTotalPhysicalMemorySize; private static final Method getFreeSwapSpaceSize; private static final Method getTotalSwapSpaceSize; private static final Method getSystemLoadAverage; private static final Method getSystemCpuLoad; static { getFreePhysicalMemorySize = getMethod("getFreePhysicalMemorySize"); getTotalPhysicalMemorySize = getMethod("getTotalPhysicalMemorySize"); getFreeSwapSpaceSize = getMethod("getFreeSwapSpaceSize"); getTotalSwapSpaceSize = getMethod("getTotalSwapSpaceSize"); getSystemLoadAverage = getMethod("getSystemLoadAverage"); getSystemCpuLoad = getMethod("getSystemCpuLoad"); } /** * Returns the amount of free physical memory in bytes. */ public long getFreePhysicalMemorySize() { if (getFreePhysicalMemorySize == null) { return -1; } try { return (long) getFreePhysicalMemorySize.invoke(osMxBean); } catch (Exception e) { return -1; } } /** * Returns the total amount of physical memory in bytes. */ public long getTotalPhysicalMemorySize() { if (getTotalPhysicalMemorySize == null) { return -1; } try { return (long) getTotalPhysicalMemorySize.invoke(osMxBean); } catch (Exception e) { return -1; } } /** * Returns the amount of free swap space in bytes. */ public long getFreeSwapSpaceSize() { if (getFreeSwapSpaceSize == null) { return -1; } try { return (long) getFreeSwapSpaceSize.invoke(osMxBean); } catch (Exception e) { return -1; } } /** * Returns the total amount of swap space in bytes. */ public long getTotalSwapSpaceSize() { if (getTotalSwapSpaceSize == null) { return -1; } try { return (long) getTotalSwapSpaceSize.invoke(osMxBean); } catch (Exception e) { return -1; } } /** * The system load averages as an array. * * On Windows, this method returns {@code null}. * * On Linux, this method returns the 1, 5, and 15-minute load averages. * * On macOS, this method should return the 1-minute load average. * * @return the available system load averages or {@code null} */ final double[] getSystemLoadAverage() { if (Constants.WINDOWS) { return null; } else if (Constants.LINUX) { try { final String procLoadAvg = readProcLoadavg(); assert procLoadAvg.matches("(\\d+\\.\\d+\\s+){3}\\d+/\\d+\\s+\\d+"); final String[] fields = procLoadAvg.split("\\s+"); return new double[]{Double.parseDouble(fields[0]), Double.parseDouble(fields[1]), Double.parseDouble(fields[2])}; } catch (final IOException e) { if (logger.isDebugEnabled()) { logger.debug("error reading /proc/loadavg", e); } return null; } } else { assert Constants.MAC_OS_X; if (getSystemLoadAverage == null) { return null; } try { final double oneMinuteLoadAverage = (double) getSystemLoadAverage.invoke(osMxBean); return new double[]{oneMinuteLoadAverage >= 0 ? oneMinuteLoadAverage : -1, -1, -1}; } catch (IllegalAccessException | InvocationTargetException e) { if (logger.isDebugEnabled()) { logger.debug("error reading one minute load average from operating system", e); } return null; } } } public short getSystemCpuPercent() { return Probes.getLoadAndScaleToPercent(getSystemCpuLoad, osMxBean); } public OsStats osStats() { final OsStats.Cpu cpu = new OsStats.Cpu(getSystemCpuPercent(), getSystemLoadAverage()); final OsStats.Mem mem = new OsStats.Mem(getTotalPhysicalMemorySize(), getFreePhysicalMemorySize()); final OsStats.Swap swap = new OsStats.Swap(getTotalSwapSpaceSize(), getFreeSwapSpaceSize()); final OsStats.Cgroup cgroup = Constants.LINUX ? getCgroup() : null; return new OsStats(System.currentTimeMillis(), cpu, mem, swap, cgroup); } /** * Returns a given method of the OperatingSystemMXBean, or null if the method is not found or unavailable. */ private static Method getMethod(String methodName) { try { return Class.forName("com.sun.management.OperatingSystemMXBean").getMethod(methodName); } catch (Exception e) { // not available return null; } } //......}OsProbe使用static代码块反射获取了getFreePhysicalMemorySize、getTotalPhysicalMemorySize、getFreeSwapSpaceSize、getTotalSwapSpaceSize、getSystemLoadAverage、getSystemCpuLoad这几个method;它们是从com.sun.management.OperatingSystemMXBean获取的;osStats方法返回OsStats,它由OsStats.Cpu、OsStats.Mem、OsStats.Swap、OsStats.Cgroup这几部分组成OsProbe.getCgroupelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/os/OsProbe.java ...

May 22, 2019 · 6 min · jiezi

聊聊Elasticsearch的ProcessProbe

序本文主要研究一下Elasticsearch的ProcessProbe ProcessProbeelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/monitor/process/ProcessProbe.java public class ProcessProbe { private static final OperatingSystemMXBean osMxBean = ManagementFactory.getOperatingSystemMXBean(); private static final Method getMaxFileDescriptorCountField; private static final Method getOpenFileDescriptorCountField; private static final Method getProcessCpuLoad; private static final Method getProcessCpuTime; private static final Method getCommittedVirtualMemorySize; static { getMaxFileDescriptorCountField = getUnixMethod("getMaxFileDescriptorCount"); getOpenFileDescriptorCountField = getUnixMethod("getOpenFileDescriptorCount"); getProcessCpuLoad = getMethod("getProcessCpuLoad"); getProcessCpuTime = getMethod("getProcessCpuTime"); getCommittedVirtualMemorySize = getMethod("getCommittedVirtualMemorySize"); } private static class ProcessProbeHolder { private static final ProcessProbe INSTANCE = new ProcessProbe(); } public static ProcessProbe getInstance() { return ProcessProbeHolder.INSTANCE; } private ProcessProbe() { } /** * Returns the maximum number of file descriptors allowed on the system, or -1 if not supported. */ public long getMaxFileDescriptorCount() { if (getMaxFileDescriptorCountField == null) { return -1; } try { return (Long) getMaxFileDescriptorCountField.invoke(osMxBean); } catch (Exception t) { return -1; } } /** * Returns the number of opened file descriptors associated with the current process, or -1 if not supported. */ public long getOpenFileDescriptorCount() { if (getOpenFileDescriptorCountField == null) { return -1; } try { return (Long) getOpenFileDescriptorCountField.invoke(osMxBean); } catch (Exception t) { return -1; } } /** * Returns the process CPU usage in percent */ public short getProcessCpuPercent() { return Probes.getLoadAndScaleToPercent(getProcessCpuLoad, osMxBean); } /** * Returns the CPU time (in milliseconds) used by the process on which the Java virtual machine is running, or -1 if not supported. */ public long getProcessCpuTotalTime() { if (getProcessCpuTime != null) { try { long time = (long) getProcessCpuTime.invoke(osMxBean); if (time >= 0) { return (time / 1_000_000L); } } catch (Exception t) { return -1; } } return -1; } /** * Returns the size (in bytes) of virtual memory that is guaranteed to be available to the running process */ public long getTotalVirtualMemorySize() { if (getCommittedVirtualMemorySize != null) { try { long virtual = (long) getCommittedVirtualMemorySize.invoke(osMxBean); if (virtual >= 0) { return virtual; } } catch (Exception t) { return -1; } } return -1; } public ProcessInfo processInfo(long refreshInterval) { return new ProcessInfo(jvmInfo().pid(), BootstrapInfo.isMemoryLocked(), refreshInterval); } public ProcessStats processStats() { ProcessStats.Cpu cpu = new ProcessStats.Cpu(getProcessCpuPercent(), getProcessCpuTotalTime()); ProcessStats.Mem mem = new ProcessStats.Mem(getTotalVirtualMemorySize()); return new ProcessStats(System.currentTimeMillis(), getOpenFileDescriptorCount(), getMaxFileDescriptorCount(), cpu, mem); } /** * Returns a given method of the OperatingSystemMXBean, * or null if the method is not found or unavailable. */ private static Method getMethod(String methodName) { try { return Class.forName("com.sun.management.OperatingSystemMXBean").getMethod(methodName); } catch (Exception t) { // not available return null; } } /** * Returns a given method of the UnixOperatingSystemMXBean, * or null if the method is not found or unavailable. */ private static Method getUnixMethod(String methodName) { try { return Class.forName("com.sun.management.UnixOperatingSystemMXBean").getMethod(methodName); } catch (Exception t) { // not available return null; } }}ProcessProbe使用static代码块反射获取了getMaxFileDescriptorCountField、getOpenFileDescriptorCountField、getProcessCpuLoad、getProcessCpuTime、getCommittedVirtualMemorySize这几个methodgetMaxFileDescriptorCountField、getOpenFileDescriptorCountField使用getUnixMethod方法,从com.sun.management.UnixOperatingSystemMXBean获取getProcessCpuLoad、getProcessCpuTime、getCommittedVirtualMemorySize使用getMethod方法,从com.sun.management.OperatingSystemMXBean获取getMaxFileDescriptorCount、getOpenFileDescriptorCount、getProcessCpuTotalTime、getTotalVirtualMemorySize方法首先都判断对应的method是否为null,如果为null则返回-1,否则则反射获取对应的值,出现异常的话,返回-1processStats方法返回ProcessStats,它由ProcessStats.Cpu、ProcessStats.Mem、getOpenFileDescriptorCount()、getMaxFileDescriptorCount()构成小结ProcessProbe使用static代码块反射获取了getMaxFileDescriptorCountField、getOpenFileDescriptorCountField、getProcessCpuLoad、getProcessCpuTime、getCommittedVirtualMemorySize这几个methodgetMaxFileDescriptorCount、getOpenFileDescriptorCount、getProcessCpuTotalTime、getTotalVirtualMemorySize方法首先都判断对应的method是否为null,如果为null则返回-1,否则则反射获取对应的值,出现异常的话,返回-1processStats方法返回ProcessStats,它由ProcessStats.Cpu、ProcessStats.Mem、getOpenFileDescriptorCount()、getMaxFileDescriptorCount()构成docProcessProbe

May 22, 2019 · 3 min · jiezi

聊聊Elasticsearch的CircuitBreaker

序本文主要研究一下Elasticsearch的CircuitBreaker CircuitBreakerelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/breaker/CircuitBreaker.java /** * Interface for an object that can be incremented, breaking after some * configured limit has been reached. */public interface CircuitBreaker { /** * The parent breaker is a sum of all the following breakers combined. With * this we allow a single breaker to have a significant amount of memory * available while still having a "total" limit for all breakers. Note that * it's not a "real" breaker in that it cannot be added to or subtracted * from by itself. */ String PARENT = "parent"; /** * The fielddata breaker tracks data used for fielddata (on fields) as well * as the id cached used for parent/child queries. */ String FIELDDATA = "fielddata"; /** * The request breaker tracks memory used for particular requests. This * includes allocations for things like the cardinality aggregation, and * accounting for the number of buckets used in an aggregation request. * Generally the amounts added to this breaker are released after a request * is finished. */ String REQUEST = "request"; /** * The in-flight request breaker tracks bytes allocated for reading and * writing requests on the network layer. */ String IN_FLIGHT_REQUESTS = "in_flight_requests"; /** * The accounting breaker tracks things held in memory that is independent * of the request lifecycle. This includes memory used by Lucene for * segments. */ String ACCOUNTING = "accounting"; enum Type { // A regular or ChildMemoryCircuitBreaker MEMORY, // A special parent-type for the hierarchy breaker service PARENT, // A breaker where every action is a noop, it never breaks NOOP; public static Type parseValue(String value) { switch(value.toLowerCase(Locale.ROOT)) { case "noop": return Type.NOOP; case "parent": return Type.PARENT; case "memory": return Type.MEMORY; default: throw new IllegalArgumentException("No CircuitBreaker with type: " + value); } } } enum Durability { // The condition that tripped the circuit breaker fixes itself eventually. TRANSIENT, // The condition that tripped the circuit breaker requires manual intervention. PERMANENT } /** * Trip the circuit breaker * @param fieldName name of the field responsible for tripping the breaker * @param bytesNeeded bytes asked for but unable to be allocated */ void circuitBreak(String fieldName, long bytesNeeded); /** * add bytes to the breaker and maybe trip * @param bytes number of bytes to add * @param label string label describing the bytes being added * @return the number of "used" bytes for the circuit breaker */ double addEstimateBytesAndMaybeBreak(long bytes, String label) throws CircuitBreakingException; /** * Adjust the circuit breaker without tripping */ long addWithoutBreaking(long bytes); /** * @return the currently used bytes the breaker is tracking */ long getUsed(); /** * @return maximum number of bytes the circuit breaker can track before tripping */ long getLimit(); /** * @return overhead of circuit breaker */ double getOverhead(); /** * @return the number of times the circuit breaker has been tripped */ long getTrippedCount(); /** * @return the name of the breaker */ String getName(); /** * @return whether a tripped circuit breaker will reset itself (transient) or requires manual intervention (permanent). */ Durability getDurability();}CircuitBreaker定义了Type、Durability枚举;它还定义了circuitBreak、addEstimateBytesAndMaybeBreak、addWithoutBreaking、getUsed、getLimit、getOverhead、getTrippedCount等方法;它有两个实现类分别是NoopCircuitBreaker、ChildMemoryCircuitBreakerNoopCircuitBreakerelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/common/breaker/NoopCircuitBreaker.java ...

May 21, 2019 · 8 min · jiezi

聊聊Elasticsearch的NodesSniffer

序本文主要研究一下Elasticsearch的NodesSniffer NodesSnifferelasticsearch-7.0.1/client/sniffer/src/main/java/org/elasticsearch/client/sniff/NodesSniffer.java /** * Responsible for sniffing the http hosts */public interface NodesSniffer { /** * Returns the sniffed Elasticsearch nodes. */ List<Node> sniff() throws IOException;}NodesSniffer接口定义了sniff方法用于获取sniffed Elasticsearch nodes,它有一个实现类为ElasticsearchNodesSnifferElasticsearchNodesSnifferelasticsearch-7.0.1/client/sniffer/src/main/java/org/elasticsearch/client/sniff/ElasticsearchNodesSniffer.java public final class ElasticsearchNodesSniffer implements NodesSniffer { private static final Log logger = LogFactory.getLog(ElasticsearchNodesSniffer.class); public static final long DEFAULT_SNIFF_REQUEST_TIMEOUT = TimeUnit.SECONDS.toMillis(1); private final RestClient restClient; private final Request request; private final Scheme scheme; private final JsonFactory jsonFactory = new JsonFactory(); public ElasticsearchNodesSniffer(RestClient restClient) { this(restClient, DEFAULT_SNIFF_REQUEST_TIMEOUT, ElasticsearchNodesSniffer.Scheme.HTTP); } public ElasticsearchNodesSniffer(RestClient restClient, long sniffRequestTimeoutMillis, Scheme scheme) { this.restClient = Objects.requireNonNull(restClient, "restClient cannot be null"); if (sniffRequestTimeoutMillis < 0) { throw new IllegalArgumentException("sniffRequestTimeoutMillis must be greater than 0"); } this.request = new Request("GET", "/_nodes/http"); request.addParameter("timeout", sniffRequestTimeoutMillis + "ms"); this.scheme = Objects.requireNonNull(scheme, "scheme cannot be null"); } /** * Calls the elasticsearch nodes info api, parses the response and returns all the found http hosts */ @Override public List<Node> sniff() throws IOException { Response response = restClient.performRequest(request); return readHosts(response.getEntity(), scheme, jsonFactory); } static List<Node> readHosts(HttpEntity entity, Scheme scheme, JsonFactory jsonFactory) throws IOException { try (InputStream inputStream = entity.getContent()) { JsonParser parser = jsonFactory.createParser(inputStream); if (parser.nextToken() != JsonToken.START_OBJECT) { throw new IOException("expected data to start with an object"); } List<Node> nodes = new ArrayList<>(); while (parser.nextToken() != JsonToken.END_OBJECT) { if (parser.getCurrentToken() == JsonToken.START_OBJECT) { if ("nodes".equals(parser.getCurrentName())) { while (parser.nextToken() != JsonToken.END_OBJECT) { JsonToken token = parser.nextToken(); assert token == JsonToken.START_OBJECT; String nodeId = parser.getCurrentName(); Node node = readNode(nodeId, parser, scheme); if (node != null) { nodes.add(node); } } } else { parser.skipChildren(); } } } return nodes; } } //...... public enum Scheme { HTTP("http"), HTTPS("https"); private final String name; Scheme(String name) { this.name = name; } @Override public String toString() { return name; } }}ElasticsearchNodesSniffer的构造器需要restClient、sniffRequestTimeoutMillis、scheme三个参数,其中sniffRequestTimeoutMillis默认为1秒,scheme默认为HTTP;它的构造器创建了GET /_nodes/http的request;sniff方法使用restClient.performRequest来执行这个GET /_nodes/http的request,之后调用readHosts来解析response,其中调用了readNode方法来解析nodes部分GET /_nodes/http实例{ "_nodes" : { "total" : 1, "successful" : 1, "failed" : 0 }, "cluster_name" : "docker-cluster", "nodes" : { "d7w2wdw7Q7SqERe5_fxZYA" : { "name" : "d7w2wdw", "transport_address" : "172.17.0.2:9300", "host" : "172.17.0.2", "ip" : "172.17.0.2", "version" : "6.6.2", "build_flavor" : "oss", "build_type" : "tar", "build_hash" : "3bd3e59", "roles" : [ "master", "data", "ingest" ], "http" : { "bound_address" : [ "0.0.0.0:9200" ], "publish_address" : "192.168.99.100:9200", "max_content_length_in_bytes" : 104857600 } } }}这里http部分有publish_address信息readNodeelasticsearch-7.0.1/client/sniffer/src/main/java/org/elasticsearch/client/sniff/ElasticsearchNodesSniffer.java ...

May 18, 2019 · 5 min · jiezi

聊聊Elasticsearch-RestClient的DeadHostState

序本文主要研究一下Elasticsearch RestClient的DeadHostState DeadHostStateelasticsearch-7.0.1/client/rest/src/main/java/org/elasticsearch/client/DeadHostState.java /** * Holds the state of a dead connection to a host. Keeps track of how many failed attempts were performed and * when the host should be retried (based on number of previous failed attempts). * Class is immutable, a new copy of it should be created each time the state has to be changed. */final class DeadHostState implements Comparable<DeadHostState> { private static final long MIN_CONNECTION_TIMEOUT_NANOS = TimeUnit.MINUTES.toNanos(1); static final long MAX_CONNECTION_TIMEOUT_NANOS = TimeUnit.MINUTES.toNanos(30); private final int failedAttempts; private final long deadUntilNanos; private final TimeSupplier timeSupplier; /** * Build the initial dead state of a host. Useful when a working host stops functioning * and needs to be marked dead after its first failure. In such case the host will be retried after a minute or so. * * @param timeSupplier a way to supply the current time and allow for unit testing */ DeadHostState(TimeSupplier timeSupplier) { this.failedAttempts = 1; this.deadUntilNanos = timeSupplier.nanoTime() + MIN_CONNECTION_TIMEOUT_NANOS; this.timeSupplier = timeSupplier; } /** * Build the dead state of a host given its previous dead state. Useful when a host has been failing before, hence * it already failed for one or more consecutive times. The more failed attempts we register the longer we wait * to retry that same host again. Minimum is 1 minute (for a node the only failed once created * through {@link #DeadHostState(TimeSupplier)}), maximum is 30 minutes (for a node that failed more than 10 consecutive times) * * @param previousDeadHostState the previous state of the host which allows us to increase the wait till the next retry attempt */ DeadHostState(DeadHostState previousDeadHostState) { long timeoutNanos = (long)Math.min(MIN_CONNECTION_TIMEOUT_NANOS * 2 * Math.pow(2, previousDeadHostState.failedAttempts * 0.5 - 1), MAX_CONNECTION_TIMEOUT_NANOS); this.deadUntilNanos = previousDeadHostState.timeSupplier.nanoTime() + timeoutNanos; this.failedAttempts = previousDeadHostState.failedAttempts + 1; this.timeSupplier = previousDeadHostState.timeSupplier; } /** * Indicates whether it's time to retry to failed host or not. * * @return true if the host should be retried, false otherwise */ boolean shallBeRetried() { return timeSupplier.nanoTime() - deadUntilNanos > 0; } /** * Returns the timestamp (nanos) till the host is supposed to stay dead without being retried. * After that the host should be retried. */ long getDeadUntilNanos() { return deadUntilNanos; } int getFailedAttempts() { return failedAttempts; } @Override public int compareTo(DeadHostState other) { if (timeSupplier != other.timeSupplier) { throw new IllegalArgumentException("can't compare DeadHostStates with different clocks [" + timeSupplier + " != " + other.timeSupplier + "]"); } return Long.compare(deadUntilNanos, other.deadUntilNanos); } @Override public String toString() { return "DeadHostState{" + "failedAttempts=" + failedAttempts + ", deadUntilNanos=" + deadUntilNanos + ", timeSupplier=" + timeSupplier + '}'; } /** * Time supplier that makes timing aspects pluggable to ease testing */ interface TimeSupplier { TimeSupplier DEFAULT = new TimeSupplier() { @Override public long nanoTime() { return System.nanoTime(); } @Override public String toString() { return "nanoTime"; } }; long nanoTime(); }}DeadHostState有failedAttempts、deadUntilNanos、timeSupplier三个属性,它提供了两类构造器,一类是根据timeSupplier来构造,一类是根据previousDeadHostState来构造根据previousDeadHostState来构造时会重新计算deadUntilNanos,递增failedAttempts;其中deadUntilNanos为timeSupplier.nanoTime() + timeoutNanos,而timeoutNanos的计算公式为(long)Math.min(MIN_CONNECTION_TIMEOUT_NANOS * 2 * Math.pow(2, previousDeadHostState.failedAttempts * 0.5 - 1)DeadHostState提供了shallBeRetried方法,其判断逻辑为timeSupplier.nanoTime() - deadUntilNanos > 0;DeadHostState实现了Comparable的compareTo方法,它是根据deadUntilNanos来进行比较RestClientelasticsearch-7.0.1/client/rest/src/main/java/org/elasticsearch/client/RestClient.java ...

May 18, 2019 · 5 min · jiezi

聊聊Elasticsearch-RestClient的NodeSelector

序本文主要研究一下Elasticsearch RestClient的NodeSelector NodeSelectorelasticsearch-7.0.1/client/rest/src/main/java/org/elasticsearch/client/NodeSelector.java public interface NodeSelector { /** * Select the {@link Node}s to which to send requests. This is called with * a mutable {@link Iterable} of {@linkplain Node}s in the order that the * rest client would prefer to use them and implementers should remove * nodes from the that should not receive the request. Implementers may * iterate the nodes as many times as they need. * <p> * This may be called twice per request: first for "living" nodes that * have not been blacklisted by previous errors. If the selector removes * all nodes from the list or if there aren't any living nodes then the * {@link RestClient} will call this method with a list of "dead" nodes. * <p> * Implementers should not rely on the ordering of the nodes. */ void select(Iterable<Node> nodes); /* * We were fairly careful with our choice of Iterable here. The caller has * a List but reordering the list is likely to break round robin. Luckily * Iterable doesn't allow any reordering. */ /** * Selector that matches any node. */ NodeSelector ANY = new NodeSelector() { @Override public void select(Iterable<Node> nodes) { // Intentionally does nothing } @Override public String toString() { return "ANY"; } }; /** * Selector that matches any node that has metadata and doesn't * have the {@code master} role OR it has the data {@code data} * role. */ NodeSelector SKIP_DEDICATED_MASTERS = new NodeSelector() { @Override public void select(Iterable<Node> nodes) { for (Iterator<Node> itr = nodes.iterator(); itr.hasNext();) { Node node = itr.next(); if (node.getRoles() == null) continue; if (node.getRoles().isMasterEligible() && false == node.getRoles().isData() && false == node.getRoles().isIngest()) { itr.remove(); } } } @Override public String toString() { return "SKIP_DEDICATED_MASTERS"; } };}NodeSelector接口定义了select方法,它接收mutable的Iterable,然后具体实现会选择性删除node;它定义了ANY、SKIP_DEDICATED_MASTERS两个匿名实现类,其中ANY的select方法不做任何操作;SKIP_DEDICATED_MASTERS的select方法会删除date为false、ingest为false的master node;除此之外还有HasAttributeNodeSelector、PreferHasAttributeNodeSelector两个实现类HasAttributeNodeSelectorelasticsearch-7.0.1/client/rest/src/main/java/org/elasticsearch/client/HasAttributeNodeSelector.java ...

May 16, 2019 · 3 min · jiezi

聊聊elasticsearch的RoutingService

序本文主要研究一下elasticsearch的RoutingService RoutingServiceelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/cluster/routing/RoutingService.java public class RoutingService extends AbstractLifecycleComponent { private static final Logger logger = LogManager.getLogger(RoutingService.class); private static final String CLUSTER_UPDATE_TASK_SOURCE = "cluster_reroute"; private final ClusterService clusterService; private final AllocationService allocationService; private AtomicBoolean rerouting = new AtomicBoolean(); @Inject public RoutingService(ClusterService clusterService, AllocationService allocationService) { this.clusterService = clusterService; this.allocationService = allocationService; } @Override protected void doStart() { } @Override protected void doStop() { } @Override protected void doClose() { } /** * Initiates a reroute. */ public final void reroute(String reason) { try { if (lifecycle.stopped()) { return; } if (rerouting.compareAndSet(false, true) == false) { logger.trace("already has pending reroute, ignoring {}", reason); return; } logger.trace("rerouting {}", reason); clusterService.submitStateUpdateTask(CLUSTER_UPDATE_TASK_SOURCE + "(" + reason + ")", new ClusterStateUpdateTask(Priority.HIGH) { @Override public ClusterState execute(ClusterState currentState) { rerouting.set(false); return allocationService.reroute(currentState, reason); } @Override public void onNoLongerMaster(String source) { rerouting.set(false); // no biggie } @Override public void onFailure(String source, Exception e) { rerouting.set(false); ClusterState state = clusterService.state(); if (logger.isTraceEnabled()) { logger.error(() -> new ParameterizedMessage("unexpected failure during [{}], current state:\n{}", source, state), e); } else { logger.error(() -> new ParameterizedMessage("unexpected failure during [{}], current state version [{}]", source, state.version()), e); } } }); } catch (Exception e) { rerouting.set(false); ClusterState state = clusterService.state(); logger.warn(() -> new ParameterizedMessage("failed to reroute routing table, current state:\n{}", state), e); } }}RoutingService的构造器要求输入clusterService及allocationService;其reroute方法主要是向clusterService提交ClusterStateUpdateTask,其execute方法是委托给allocationService.rerouteAllocationService.rerouteelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/cluster/routing/allocation/AllocationService.java ...

May 14, 2019 · 3 min · jiezi

Elasticsearch搜索过程详解

前言说明:本文章使用的ES版本是:6.7.0 在上一篇文章Elasticsearch如何创建索引?中,介绍了ES写入文档的过程。 接下来我们具体的看一下ES中,搜索过程是怎样的 在ES中搜索按照前面几篇文章的步骤,我们直接开始debug搜索的过程。上一篇文章中我们写入了如下的数据 { "id":6, "title": "我是文件标题,可被搜索到66", "text": "文本内容,ES时如何索引一个文档的66", "date": "2014/01/06"}'现在执行如下请求,对ES服务器发起搜索请求: curl -X GET 'localhost:9200/index_name/type_name/_search?pretty&q=title:66' -H 'Content-Type: application/json'搜索可以接收下面的形式的请求: 客户端根据上图的路由表,选取处理请求的Action,这里是RestSearchAction接收并开始处理请求RestSearchAction解析并验证搜索参数,并将其封装成SearchRequest,并指定服务端要处理该请求的Action:indices:data/read/search服务端(master节点)根据SearchRequest的index构造相应的ShardsIterator(分片迭代器),shardIterators由localShardsIterator(当前节点分片迭代器(默认一个节点上,一个索引有5个分片))和remoteShardIterators(其他节点分片迭代器)合并而成,根据搜索条件,构建搜索策略。然后遍历所有的shard。 搜索策略 最多遍历分片数量LONG最大值2^63-1如果只有一个分片,搜索类型只能是:QUERY_THEN_FETCH是否查询缓存遍历分片的最大并发数Math.min(256, Math.max(节点数, 1)*节点分片数),节点默认分片数:5构造异步请求Action,将请求转发到各个节点,等待回调遍历所有节点,构造节点查询参数ShardSearchTransportRequest对象,对每个节点执行查询操作执行查询阶段,首先在cache里面判断是否有缓存,如果有则执行缓存查询;如果cache里面没有,执行QueryPhase类的execute()方法,他调用lucene的searcher.search对索引进行查询,查询成功回调onShardResult方法并返回docIds,查询失败回调onShardFailure(计数失败情况,并尝试在副本分片上进行查询)查询阶段会计算文档的相关性得分用于排序:Fetch阶段:master接收到各个节点返回的docIds后,发起数据Fetch请求,通过docId和其分片ID到对应分片抓取数据,后合并数据返回给客户端大致的查询时序逻辑: 搜索总结Query阶段可以知道,一个搜索会遍历这个索引下的所有分片,每个分片都会执行一次搜索,并返回相同数量的文档ID。比如搜索条件要查询5条数据,有5个分片,则最终会查询25条数据,排序后取前面5条数据查询和计算权重得分在Lucene完成,聚合是在ES中实现的搜索会遍历所有的分片,所以分片的数量影响着搜索的性能,而分片的数量也决定了ES能承载的最大数据量。所以在具体的应用中,需要在二者之间选择平衡计算文档权重得分,每搜索一次,都会根据搜索条件重新计算一次,对搜索性能影响很大后面的文章中将会详细分析ES中的排序得分是如何计算的。 系列文章搜索引擎ElasticSearch源码编译和Debug环境搭建搜索引擎ElasticSearch的启动过程Elasticsearch创建索引流程Elasticsearch搜索过程详解Elasticsearch搜索相关性排序算法详解Elasticsearch中的倒排索引

May 14, 2019 · 1 min · jiezi

聊聊elasticsearch的LagDetector

序本文主要研究一下elasticsearch的LagDetector LagDetectorelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/cluster/coordination/LagDetector.java /** * A publication can succeed and complete before all nodes have applied the published state and acknowledged it; however we need every node * eventually either to apply the published state (or a later state) or be removed from the cluster. This component achieves this by * removing any lagging nodes from the cluster after a timeout. */public class LagDetector { private static final Logger logger = LogManager.getLogger(LagDetector.class); // the timeout for each node to apply a cluster state update after the leader has applied it, before being removed from the cluster public static final Setting<TimeValue> CLUSTER_FOLLOWER_LAG_TIMEOUT_SETTING = Setting.timeSetting("cluster.follower_lag.timeout", TimeValue.timeValueMillis(90000), TimeValue.timeValueMillis(1), Setting.Property.NodeScope); private final TimeValue clusterStateApplicationTimeout; private final Consumer<DiscoveryNode> onLagDetected; private final Supplier<DiscoveryNode> localNodeSupplier; private final ThreadPool threadPool; private final Map<DiscoveryNode, NodeAppliedStateTracker> appliedStateTrackersByNode = newConcurrentMap(); public LagDetector(final Settings settings, final ThreadPool threadPool, final Consumer<DiscoveryNode> onLagDetected, final Supplier<DiscoveryNode> localNodeSupplier) { this.threadPool = threadPool; this.clusterStateApplicationTimeout = CLUSTER_FOLLOWER_LAG_TIMEOUT_SETTING.get(settings); this.onLagDetected = onLagDetected; this.localNodeSupplier = localNodeSupplier; } public void setTrackedNodes(final Iterable<DiscoveryNode> discoveryNodes) { final Set<DiscoveryNode> discoveryNodeSet = new HashSet<>(); discoveryNodes.forEach(discoveryNodeSet::add); discoveryNodeSet.remove(localNodeSupplier.get()); appliedStateTrackersByNode.keySet().retainAll(discoveryNodeSet); discoveryNodeSet.forEach(node -> appliedStateTrackersByNode.putIfAbsent(node, new NodeAppliedStateTracker(node))); } public void clearTrackedNodes() { appliedStateTrackersByNode.clear(); } public void setAppliedVersion(final DiscoveryNode discoveryNode, final long appliedVersion) { final NodeAppliedStateTracker nodeAppliedStateTracker = appliedStateTrackersByNode.get(discoveryNode); if (nodeAppliedStateTracker == null) { // Received an ack from a node that a later publication has removed (or we are no longer master). No big deal. logger.trace("node {} applied version {} but this node's version is not being tracked", discoveryNode, appliedVersion); } else { nodeAppliedStateTracker.increaseAppliedVersion(appliedVersion); } } public void startLagDetector(final long version) { final List<NodeAppliedStateTracker> laggingTrackers = appliedStateTrackersByNode.values().stream().filter(t -> t.appliedVersionLessThan(version)).collect(Collectors.toList()); if (laggingTrackers.isEmpty()) { logger.trace("lag detection for version {} is unnecessary: {}", version, appliedStateTrackersByNode.values()); } else { logger.debug("starting lag detector for version {}: {}", version, laggingTrackers); threadPool.scheduleUnlessShuttingDown(clusterStateApplicationTimeout, Names.GENERIC, new Runnable() { @Override public void run() { laggingTrackers.forEach(t -> t.checkForLag(version)); } @Override public String toString() { return "lag detector for version " + version + " on " + laggingTrackers; } }); } } @Override public String toString() { return "LagDetector{" + "clusterStateApplicationTimeout=" + clusterStateApplicationTimeout + ", appliedStateTrackersByNode=" + appliedStateTrackersByNode.values() + '}'; } // for assertions Set<DiscoveryNode> getTrackedNodes() { return Collections.unmodifiableSet(appliedStateTrackersByNode.keySet()); } private class NodeAppliedStateTracker { private final DiscoveryNode discoveryNode; private final AtomicLong appliedVersion = new AtomicLong(); NodeAppliedStateTracker(final DiscoveryNode discoveryNode) { this.discoveryNode = discoveryNode; } void increaseAppliedVersion(long appliedVersion) { long maxAppliedVersion = this.appliedVersion.updateAndGet(v -> Math.max(v, appliedVersion)); logger.trace("{} applied version {}, max now {}", this, appliedVersion, maxAppliedVersion); } boolean appliedVersionLessThan(final long version) { return appliedVersion.get() < version; } @Override public String toString() { return "NodeAppliedStateTracker{" + "discoveryNode=" + discoveryNode + ", appliedVersion=" + appliedVersion + '}'; } void checkForLag(final long version) { if (appliedStateTrackersByNode.get(discoveryNode) != this) { logger.trace("{} no longer active when checking version {}", this, version); return; } long appliedVersion = this.appliedVersion.get(); if (version <= appliedVersion) { logger.trace("{} satisfied when checking version {}, node applied version {}", this, version, appliedVersion); return; } logger.debug("{}, detected lag at version {}, node has only applied version {}", this, version, appliedVersion); onLagDetected.accept(discoveryNode); } }}LagDetector用于检测并移除lagging nodes,其构造器读取cluster.follower_lag.timeout配置,默认为90000ms,最小值为1msstartLagDetector首先获取从appliedStateTrackersByNode中过滤出appliedVersion小于指定version的laggingTrackers,之后延时clusterStateApplicationTimeout执行检测,其run方法会遍历laggingTrackers,挨个执行器NodeAppliedStateTracker.checkForLag方法NodeAppliedStateTracker的checkForLag方法首先进行version判断,最后调用onLagDetected.accept(discoveryNode)Coordinatorelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/cluster/coordination/Coordinator.java ...

May 14, 2019 · 4 min · jiezi

Elasticsearch创建索引流程

前言说明:本文章使用的ES版本是:6.7.0 在上一篇文章搜索引擎ElasticSearch的启动过程中,介绍了ES的启动过程。 由此可知,在ES启动过程中,创建Node对象(new Node(environment))时,初始化了RestHandler,由其名字可以知道这是用来处理Rest请求的。 在ES源码中,RestHandlerAction如下图: 其中: admin cluster:处理集群相关请求indices:处理索引相关请求cat:日常查询document:文档处理ingest:pipeline处理。pipeline?干嘛的search:搜索接下来我们具体的看一下ES是如何创建索引的:org.elasticsearch.rest.action.document.RestIndexAction 数据概念和结构一个完整的ES集群由以下几个基本元素组成 名称概念对应关系型数据库概念说明Cluster集群 一个或多个节点的集合,通过启动时指定名字作为唯一标识,默认cluster-statenode节点 启动的ES的单个实例,保存数据并具有索引和搜索的能力,通过名字唯一标识,默认node-nindex索引Database具有相似特点的文档的集合,可以对应为关系型数据库中的数据库,通过名字在集群内唯一标识type文档类别Table索引内部的逻辑分类,可以对应为Mysql中的表,ES 6.x 版本中,一个索引只允许一个type,不再支持多个type。7.x版本中,type将废弃。document文档Row构成索引的最小单元,属于一个索引的某个类别,从属关系为: Index -> Type -> Document,通过id 在Type 内唯一标识field字段Column构成文档的单元mapping索引映射(约束)Schema用来约束文档字段的类型,可以理解为索引内部结构shard分片 将索引分为多个块,每块叫做一个分片。索引定义时需要指定分片数且不能更改,默认一个索引有5个分片,每个分片都是一个功能完整的Index,分片带来规模上(数据水平切分)和性能上(并行执行)的提升,是ES数据存储的最小单位replicas分片的备份 每个分片默认一个备份分片,它可以提升节点的可用性,同时能够提升搜索时的并发性能(搜索可以在全部分片上并行执行)一个ES集群的结构如下: 每个节点默认有5个分片,每个分片有一个备分片。 6.x版本之前的索引的内部结构: 说明:ES 6.x 版本中,相同索引只允许一个type,不再支持多个type。7.x版本中,type将废弃。 所以,6.x版本的索引结构如下: 7.x版本的索引结构如下: 索引一个文档启动ES实例后,发送如下请求: curl -X PUT 'localhost:9200/index_name/type_name/1' -H 'Content-Type: application/json' -d '{ "title": "我是文件标题,可被搜索到", "text": "文本内容,ES时如何索引一个文档的", "date": "2019/01/01"}'其中: index_name:表示索引名称type_name:类别名称1:文档IDES执行流程:客户端:BaseRestHandler#handleRequest:处理请求RestIndexAction#prepareRequest:封装request,识别行为,允许的行为如下,默认INDEX enum OpType { /** * Index the source. If there an existing document with the id, it will */ INDEX(0), /** * Creates the resource. Simply adds it to the index, if there is an existing * document with the id, then it won't be removed. */ CREATE(1), /** Updates a document */ UPDATE(2), /** Deletes a document */ DELETE(3); ...}```参数检查,查看是否有关键字,并获取相关关键字的值 ...

May 13, 2019 · 1 min · jiezi

聊聊elasticsearch的ElectMasterService

序本文主要研究一下elasticsearch的ElectMasterService ElectMasterServiceelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/discovery/zen/ElectMasterService.java public class ElectMasterService { private static final Logger logger = LogManager.getLogger(ElectMasterService.class); public static final Setting<Integer> DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING = Setting.intSetting("discovery.zen.minimum_master_nodes", -1, Property.Dynamic, Property.NodeScope, Property.Deprecated); private volatile int minimumMasterNodes; /** * a class to encapsulate all the information about a candidate in a master election * that is needed to decided which of the candidates should win */ public static class MasterCandidate { public static final long UNRECOVERED_CLUSTER_VERSION = -1; final DiscoveryNode node; final long clusterStateVersion; public MasterCandidate(DiscoveryNode node, long clusterStateVersion) { Objects.requireNonNull(node); assert clusterStateVersion >= -1 : "got: " + clusterStateVersion; assert node.isMasterNode(); this.node = node; this.clusterStateVersion = clusterStateVersion; } public DiscoveryNode getNode() { return node; } public long getClusterStateVersion() { return clusterStateVersion; } @Override public String toString() { return "Candidate{" + "node=" + node + ", clusterStateVersion=" + clusterStateVersion + '}'; } /** * compares two candidates to indicate which the a better master. * A higher cluster state version is better * * @return -1 if c1 is a batter candidate, 1 if c2. */ public static int compare(MasterCandidate c1, MasterCandidate c2) { // we explicitly swap c1 and c2 here. the code expects "better" is lower in a sorted // list, so if c2 has a higher cluster state version, it needs to come first. int ret = Long.compare(c2.clusterStateVersion, c1.clusterStateVersion); if (ret == 0) { ret = compareNodes(c1.getNode(), c2.getNode()); } return ret; } } public ElectMasterService(Settings settings) { this.minimumMasterNodes = DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.get(settings); logger.debug("using minimum_master_nodes [{}]", minimumMasterNodes); } public void minimumMasterNodes(int minimumMasterNodes) { this.minimumMasterNodes = minimumMasterNodes; } public int minimumMasterNodes() { return minimumMasterNodes; } public int countMasterNodes(Iterable<DiscoveryNode> nodes) { int count = 0; for (DiscoveryNode node : nodes) { if (node.isMasterNode()) { count++; } } return count; } public boolean hasEnoughCandidates(Collection<MasterCandidate> candidates) { if (candidates.isEmpty()) { return false; } if (minimumMasterNodes < 1) { return true; } assert candidates.stream().map(MasterCandidate::getNode).collect(Collectors.toSet()).size() == candidates.size() : "duplicates ahead: " + candidates; return candidates.size() >= minimumMasterNodes; } /** * Elects a new master out of the possible nodes, returning it. Returns {@code null} * if no master has been elected. */ public MasterCandidate electMaster(Collection<MasterCandidate> candidates) { assert hasEnoughCandidates(candidates); List<MasterCandidate> sortedCandidates = new ArrayList<>(candidates); sortedCandidates.sort(MasterCandidate::compare); return sortedCandidates.get(0); } /** selects the best active master to join, where multiple are discovered */ public DiscoveryNode tieBreakActiveMasters(Collection<DiscoveryNode> activeMasters) { return activeMasters.stream().min(ElectMasterService::compareNodes).get(); } public boolean hasEnoughMasterNodes(Iterable<DiscoveryNode> nodes) { final int count = countMasterNodes(nodes); return count > 0 && (minimumMasterNodes < 0 || count >= minimumMasterNodes); } public boolean hasTooManyMasterNodes(Iterable<DiscoveryNode> nodes) { final int count = countMasterNodes(nodes); return count > 1 && minimumMasterNodes <= count / 2; } public void logMinimumMasterNodesWarningIfNecessary(ClusterState oldState, ClusterState newState) { // check if min_master_nodes setting is too low and log warning if (hasTooManyMasterNodes(oldState.nodes()) == false && hasTooManyMasterNodes(newState.nodes())) { logger.warn("value for setting \"{}\" is too low. This can result in data loss! Please set it to at least a quorum of master-" + "eligible nodes (current value: [{}], total number of master-eligible nodes used for publishing in this round: [{}])", ElectMasterService.DISCOVERY_ZEN_MINIMUM_MASTER_NODES_SETTING.getKey(), minimumMasterNodes(), newState.getNodes().getMasterNodes().size()); } } /** * Returns the given nodes sorted by likelihood of being elected as master, most likely first. * Non-master nodes are not removed but are rather put in the end */ static List<DiscoveryNode> sortByMasterLikelihood(Iterable<DiscoveryNode> nodes) { ArrayList<DiscoveryNode> sortedNodes = CollectionUtils.iterableAsArrayList(nodes); CollectionUtil.introSort(sortedNodes, ElectMasterService::compareNodes); return sortedNodes; } /** * Returns a list of the next possible masters. */ public DiscoveryNode[] nextPossibleMasters(ObjectContainer<DiscoveryNode> nodes, int numberOfPossibleMasters) { List<DiscoveryNode> sortedNodes = sortedMasterNodes(Arrays.asList(nodes.toArray(DiscoveryNode.class))); if (sortedNodes == null) { return new DiscoveryNode[0]; } List<DiscoveryNode> nextPossibleMasters = new ArrayList<>(numberOfPossibleMasters); int counter = 0; for (DiscoveryNode nextPossibleMaster : sortedNodes) { if (++counter >= numberOfPossibleMasters) { break; } nextPossibleMasters.add(nextPossibleMaster); } return nextPossibleMasters.toArray(new DiscoveryNode[nextPossibleMasters.size()]); } private List<DiscoveryNode> sortedMasterNodes(Iterable<DiscoveryNode> nodes) { List<DiscoveryNode> possibleNodes = CollectionUtils.iterableAsArrayList(nodes); if (possibleNodes.isEmpty()) { return null; } // clean non master nodes possibleNodes.removeIf(node -> !node.isMasterNode()); CollectionUtil.introSort(possibleNodes, ElectMasterService::compareNodes); return possibleNodes; } /** master nodes go before other nodes, with a secondary sort by id **/ private static int compareNodes(DiscoveryNode o1, DiscoveryNode o2) { if (o1.isMasterNode() && !o2.isMasterNode()) { return -1; } if (!o1.isMasterNode() && o2.isMasterNode()) { return 1; } return o1.getId().compareTo(o2.getId()); }}ElectMasterService的构造器读取discovery.zen.minimum_master_nodes配置到变量minimumMasterNodesElectMasterService定义了静态类MasterCandidate,它提供了compare静态方法用于比较两个MasterCandidate,它首先对比clusterStateVersion,如果该值相同再进行compareNodes,compareNodes会先判断下是否masterNode,都不是则对比他们各自的node的idelectMaster方法首先通过hasEnoughCandidates来确定是否有足够的candidates,足够的话则对他们进行排序,最后取第一个作为master返回ZenDiscovery.findMasterelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/discovery/zen/ZenDiscovery.java ...

May 12, 2019 · 5 min · jiezi

聊聊elasticsearch的MasterFaultDetection

序本文主要研究一下elasticsearch的MasterFaultDetection FaultDetectionelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/discovery/zen/FaultDetection.java /** * A base class for {@link MasterFaultDetection} &amp; {@link NodesFaultDetection}, * making sure both use the same setting. */public abstract class FaultDetection implements Closeable { private static final Logger logger = LogManager.getLogger(FaultDetection.class); public static final Setting<Boolean> CONNECT_ON_NETWORK_DISCONNECT_SETTING = Setting.boolSetting("discovery.zen.fd.connect_on_network_disconnect", false, Property.NodeScope, Property.Deprecated); public static final Setting<TimeValue> PING_INTERVAL_SETTING = Setting.positiveTimeSetting("discovery.zen.fd.ping_interval", timeValueSeconds(1), Property.NodeScope, Property.Deprecated); public static final Setting<TimeValue> PING_TIMEOUT_SETTING = Setting.timeSetting("discovery.zen.fd.ping_timeout", timeValueSeconds(30), Property.NodeScope, Property.Deprecated); public static final Setting<Integer> PING_RETRIES_SETTING = Setting.intSetting("discovery.zen.fd.ping_retries", 3, Property.NodeScope, Property.Deprecated); public static final Setting<Boolean> REGISTER_CONNECTION_LISTENER_SETTING = Setting.boolSetting("discovery.zen.fd.register_connection_listener", true, Property.NodeScope, Property.Deprecated); protected final ThreadPool threadPool; protected final ClusterName clusterName; protected final TransportService transportService; // used mainly for testing, should always be true protected final boolean registerConnectionListener; protected final FDConnectionListener connectionListener; protected final boolean connectOnNetworkDisconnect; protected final TimeValue pingInterval; protected final TimeValue pingRetryTimeout; protected final int pingRetryCount; public FaultDetection(Settings settings, ThreadPool threadPool, TransportService transportService, ClusterName clusterName) { this.threadPool = threadPool; this.transportService = transportService; this.clusterName = clusterName; this.connectOnNetworkDisconnect = CONNECT_ON_NETWORK_DISCONNECT_SETTING.get(settings); this.pingInterval = PING_INTERVAL_SETTING.get(settings); this.pingRetryTimeout = PING_TIMEOUT_SETTING.get(settings); this.pingRetryCount = PING_RETRIES_SETTING.get(settings); this.registerConnectionListener = REGISTER_CONNECTION_LISTENER_SETTING.get(settings); this.connectionListener = new FDConnectionListener(); if (registerConnectionListener) { transportService.addConnectionListener(connectionListener); } } @Override public void close() { transportService.removeConnectionListener(connectionListener); } /** * This method will be called when the {@link org.elasticsearch.transport.TransportService} raised a node disconnected event */ abstract void handleTransportDisconnect(DiscoveryNode node); private class FDConnectionListener implements TransportConnectionListener { @Override public void onNodeDisconnected(DiscoveryNode node) { AbstractRunnable runnable = new AbstractRunnable() { @Override public void onFailure(Exception e) { logger.warn("failed to handle transport disconnect for node: {}", node); } @Override protected void doRun() { handleTransportDisconnect(node); } }; threadPool.generic().execute(runnable); } }}FaultDetection实现了Closeable接口,它定义了FDConnectionListener,其构造器在registerConnectionListener为true的情况下会给transportService添加FDConnectionListener,而close方法则是将FDConnectionListener从transportService中移除;FaultDetection还定义了抽象方法handleTransportDisconnectMasterFaultDetectionelasticsearch-7.0.1/server/src/main/java/org/elasticsearch/discovery/zen/MasterFaultDetection.java ...

May 11, 2019 · 12 min · jiezi

聊聊elasticsearch的NodesFaultDetection

序本文主要研究一下elasticsearch的NodesFaultDetection NodesFaultDetectionelasticsearch-0.90.0/src/main/java/org/elasticsearch/discovery/zen/fd/NodesFaultDetection.java public class NodesFaultDetection extends AbstractComponent { public static interface Listener { void onNodeFailure(DiscoveryNode node, String reason); } private final ThreadPool threadPool; private final TransportService transportService; private final boolean connectOnNetworkDisconnect; private final TimeValue pingInterval; private final TimeValue pingRetryTimeout; private final int pingRetryCount; // used mainly for testing, should always be true private final boolean registerConnectionListener; private final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<Listener>(); private final ConcurrentMap<DiscoveryNode, NodeFD> nodesFD = newConcurrentMap(); private final FDConnectionListener connectionListener; private volatile DiscoveryNodes latestNodes = EMPTY_NODES; private volatile boolean running = false; public NodesFaultDetection(Settings settings, ThreadPool threadPool, TransportService transportService) { super(settings); this.threadPool = threadPool; this.transportService = transportService; this.connectOnNetworkDisconnect = componentSettings.getAsBoolean("connect_on_network_disconnect", true); this.pingInterval = componentSettings.getAsTime("ping_interval", timeValueSeconds(1)); this.pingRetryTimeout = componentSettings.getAsTime("ping_timeout", timeValueSeconds(30)); this.pingRetryCount = componentSettings.getAsInt("ping_retries", 3); this.registerConnectionListener = componentSettings.getAsBoolean("register_connection_listener", true); logger.debug("[node ] uses ping_interval [{}], ping_timeout [{}], ping_retries [{}]", pingInterval, pingRetryTimeout, pingRetryCount); transportService.registerHandler(PingRequestHandler.ACTION, new PingRequestHandler()); this.connectionListener = new FDConnectionListener(); if (registerConnectionListener) { transportService.addConnectionListener(connectionListener); } } public NodesFaultDetection start() { if (running) { return this; } running = true; return this; } public NodesFaultDetection stop() { if (!running) { return this; } running = false; return this; } public void close() { stop(); transportService.removeHandler(PingRequestHandler.ACTION); transportService.removeConnectionListener(connectionListener); } //......}NodesFaultDetection继承了AbstractComponent,它定义了一个CopyOnWriteArrayList类型的listeners,一个ConcurrentMap的nodesFD,connectionListener、latestNodes、running等属性其构造器读取connect_on_network_disconnect(默认true)、ping_interval(默认1s)、ping_timeout(默认30s)、ping_retries(默认为3)、register_connection_listener(默认true)配置,然后给transportService注册了PingRequestHandler.ACTION的PingRequestHandler,添加了FDConnectionListenerstart方法用于设置running为true;stop用于设置running为false;close方法先执行stop,然后从transportService移除PingRequestHandler.ACTION的handler,并移除connectionListenerPingRequestHandlerelasticsearch-0.90.0/src/main/java/org/elasticsearch/discovery/zen/fd/NodesFaultDetection.java ...

May 10, 2019 · 6 min · jiezi

增量同步mysql-数据到elasticsearch-canal-adapter方式binlog实现从零到一超级详细全面

下面是我在单机上面从零到一实现增量同步mysql 数据到elasticsearch canal adapter方式(binlog)实现实现步骤(1)安装mysql(2)开启mysql binlog row模式,并启动mysql(3)安装jdk(4)安装Elasticsearch并启动(我安装的是6.4.0,主要目前canal adapter1.1.3还不支持7.0.0的版本)(5)安装kibana并启动(6)安装并启动canal-server(7)安装并启动canal-adapter 我使用的操作系统是centos71、通过yum安装mysql(1)去官网查看最新的安装包https://dev.mysql.com/downloa... (2)下载mysql源安装包wget http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm目前版本已经很高了,但是我使用的是57安装mysql源 yum -y install mysql57-community-release-el7-11.noarch.rpm查看效果: yum repolist enabled | grep mysql.* (3)安装mysql服务器yum install mysql-community-server(4)启动mysql服务systemctl start mysqld.service查看mysql服务的状态: systemctl status mysqld.service (5)查看初始化密码grep "password" /var/log/mysqld.log登录: mysql -u root -p (6)数据库授权(切记这一步一定要做,我为了方便后面使用的都是root账号,没有说新建一个canal账号)数据库没有授权,只支持localhost本地访问 GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION;FLUSH PRIVILEGES;用户名:root密码:123456指向ip:%代表所有Ip,此处也可以输入Ip来指定Ip 2、开启mysql binlog模式找到my.cnf文件,我本地目录是/etc/my.cnf添加即可 log-bin=mysql-binbinlog-format=ROWserver-id=1然后重启mysql,检查一下binlog是否正确启动 show variables like '%log_bin%'; 3、安装jdk我装的是jdk版本是1.8.0_202下载网址:https://www.oracle.com/techne...(1)将jdk-8u202-linux-x64.tar.gz放入/usr/local目录(2)解压缩等一系列处理 tar -xzvf jdk-8u202-linux-x64.tar.gzmv jdk-8u202-linux-x64 jdkrm -rf jdk-8u202-linux-x64.tar.gz命令执行完成之后在/usr/local目录下就会生成一个jdk目录(3)配置环境变量 vi /etc/profile增加:export JAVA_HOME=/usr/local/jdkexport CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jarexport PATH=$JAVA_HOME/bin:$PATH(4)检查JDK是否安装成功 ...

May 4, 2019 · 9 min · jiezi

elasticsearch学习基础知识总结

对elasticsearch基础知识部分做一个比较系统的总结和归纳,感觉已经很全了,在实际中遇到的很多问题,都可以找到参照点,然后针对的去解决问题。郑重说明,可能很多知识自己没有讲得很清楚,希望大家批评指正,我会学习和修改的,谢谢~~后面会继续更新实战篇~~ 自己的心得和经历我是属于一个ES小白,为了解决部门内部的一些瓶颈问题,学习ES然后去实践的。举三个场景吧,当然场景应该是很多的 第一个场景如果mysql表过大之后主要千万级以上,这个时候会出现各种各样的问题,比如非常常见的就是慢查询,这个主要是一开始业务量不大,然后突然业务猛增,慢查询问题就来了。然后还有就是导出数据问题,需要支持各种灵活的筛选条件来大批量的下载导出数据。实际场景就是对账,还有数据分析。当然解决上面这些问题,应该有很多种方法,我是采用将数据同步到ES,包括全量和增量同步,然后写一个查询服务,前端直接调用我的查询接口就OK了。现在已经投入使用,几十万的数据一般不超过3分钟,表数据都是千万级的或是更多。 第二个场景目前如果做指标监控,自己去开发一套会非常的复杂,但是如果使用ES就不一样的,可以轻松的做到这件事情。我们就是用ES做一套行为日志收集,以及利用现有的mysql数据到ES的开源工具logstash、canal、go-mysql-elasticsearch,将数据近实时的放在ES上面,然后通过grafana或是kibana,进行分析和展示,可以近实时的监控我们做的部分业务的效果,即使的做出调整,主要也是业务需要,很多的业务可能不需要,只是我的这些业务是非常需要这个,因为直接会影响收入。 第三个场景直接用ES作为数据存储,也就是数据不存mysql了。我是做了一个,不过不是全部数据都是用ES的,还有一些数据是存在mysql数据库中的。如果想用ES存储的话,一定要保证你的业务是一些实时性要求没有那么高的。 下面是做的笔记,类似起到一个目录的作用elasticsearch学习笔记(一)——大白话告诉你什么是elasticsearchhttps://segmentfault.com/a/11... elasticsearch学习笔记(二)——elasticsearch的功能、适用场景以及特点介绍https://segmentfault.com/a/11... elasticsearch学习笔记(三)——Elasticsearch的核心概念https://segmentfault.com/a/11... elasticsearch学习笔记(四)——在windows上安装和启动Elasticsearchhttps://segmentfault.com/a/11... elasticsearch学习笔记(五)——快速入门案例实战电商网站商品管理:集群健康检查,文档的CRUDhttps://segmentfault.com/a/11... elasticsearch学习笔记(六)——快速入门案例实战之电商网站商品管理:多种搜索方式https://segmentfault.com/a/11... elasticsearch学习笔记(七)——快速入门案例实战之电商网站商品管理:嵌套聚合,下钻分析,聚合分析https://segmentfault.com/a/11... elasticsearch学习笔记(八)——剖析Elasticsearch的基础分布式架构https://segmentfault.com/a/11... elasticsearch学习笔记(九)——shard&replica机制以及ES集群节点的问题https://segmentfault.com/a/11... elasticsearch学习笔记(十)——Elasticsearch横向扩容过程与容错机制https://segmentfault.com/a/11... elasticsearch学习笔记(十一)——document的核心元数据、操作以及原理https://segmentfault.com/a/11... elasticsearch学习笔记(十二)——Elasticsearch并发冲突问题以及锁机制https://segmentfault.com/a/11... elasticsearch学习笔记(十三)——Elasticsearch乐观锁并发控制实战https://segmentfault.com/a/11... elasticsearch学习笔记(十四)——Elasticsearch partial update实现原理和实践https://segmentfault.com/a/11... elasticsearch学习笔记(十五)——Elasticsearch partial update内置乐观锁并发控制https://segmentfault.com/a/11... elasticsearch学习笔记(十六)——Elasticsearch mget批量查询api实战https://segmentfault.com/a/11... elasticsearch学习笔记(十七)——Elasticsearch document数据路由原理以及主分片的不可变https://segmentfault.com/a/11... elasticsearch学习笔记(十八)——Elasticsearch document增删改内部原理,写一致性机制https://segmentfault.com/a/11... elasticsearch学习笔记(十九)——Elasticsearch document查询内部原理https://segmentfault.com/a/11... elasticsearch学习笔记(二十)——Elasticsearch bulk api的奇特json格式与底层性能优化关系https://segmentfault.com/a/11... elasticsearch学习笔记(二十一)——Elasticsearch _search结果的含义以及timeout机制https://segmentfault.com/a/11... elasticsearch学习笔记(二十二)——Elasticsearch multi-index搜索模式以及搜索原理https://segmentfault.com/a/11... elasticsearch学习笔记(二十三)——Elasticsearch 分页搜索以及深分页性能问题https://segmentfault.com/a/11... elasticsearch学习笔记(二十四)——Elasticsearch query string语法以及_all元数据原理https://segmentfault.com/a/11... elasticsearch学习笔记(二十五)——Elasticsearch mapping详解以及索引内部原理https://segmentfault.com/a/11... elasticsearch学习笔记(二十六)——Elasticsearch query DSL搜索实战https://segmentfault.com/a/11... elasticsearch学习笔记(二十七)——Elasticsearch filter与queryhttps://segmentfault.com/a/11... elasticsearch学习笔记(二十八)——Elasticsearch 实战各种query搜索https://segmentfault.com/a/11... elasticsearch学习笔记(二十九)——Elasticsearch 将一个field索引两次来解决字符串排序问题https://segmentfault.com/a/11... elasticsearch学习笔记(三十)——Elasticsearch 相关度评分 TF&IDF算法https://segmentfault.com/a/11... elasticsearch学习笔记(三十一)——Elasticsearch doc value正排索引https://segmentfault.com/a/11... elasticsearch学习笔记(三十二)——Elasticsearch 解密query、fetch phrase原理https://segmentfault.com/a/11... elasticsearch学习笔记(三十三)——Elasticsearch Bouncing Results问题https://segmentfault.com/a/11... ...

May 2, 2019 · 1 min · jiezi

elasticsearch学习笔记三十六Elasticsearch-内核原理

倒排索引组成结构以及索引不可变原因对于倒排索引是非常适合用来进行搜索的它的结构:(1)包含这个关键词的document list(2)包含这个关键词的所有document的数量:IDF(inverse document frequency)(3)这个关键词在每个document中出现的次数:TF(term frequency)(4)这个关键词在这个document中的次序(5)每个document的长度:length norm(6)包含这个关键词的所有document的平均长度其实本质上主要是为了计算相关度分数 _score = boost * idf * tf 此时boost = 2.2, idf = 0.18232156, tf = 0.5116279idf = log(1 + (N - n + 0.5) / (n + 0.5))此时n = 2 (n, number of documents containing term), N = 2(N, total number of documents with field)tf = freq / (freq + k1 * (1 - b + b * dl / avgdl))此时freq = 1(freq, occurrences of term within document), k1 = 1.2(k1, term saturation parameter), b = 0.75(b, length normalization parameter), d1 = 4 (dl, length of field), avgdl = 5.5(avgdl, average length of field)倒排索引的不可变好处(1)不需要锁,提升了并发能力,避免锁的问题(2)数据不变,一直保存在OS cache中,只要cache内存足够(3)filter cache一直驻留在内存(4)可以压缩,节省cpu和io开销这个对应的就是primary shard的数量不变,不能修改field的属性(将date改成text) ...

May 2, 2019 · 1 min · jiezi

elasticsearch学习笔记三十五Elasticsearch-索引管理

索引的基本操作创建索引PUT /{index}{ "settings": {}, "mappings": { "properties": { } }}创建索引示例: PUT /test_index{ "settings": { "number_of_replicas": 1, "number_of_shards": 5 }, "mappings": { "properties": { "field1": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }, "ctime": { "type": "date" } } }}修改索引PUT /{index}/_settings{ "setttings": {}}PUT /test_index/_settings{ "settings": { "number_of_replicas": 2 }}删除索引DELETE /{index}删除索引API也可以通过使用逗号分隔列表应用于多个索引,或者通过使用_all或*作为索引应用于所有索引(小心!)。 要禁用允许通过通配符删除索引,或者将配置中的_all设置action.destructive_requires_name设置为true。也可以通过群集更新设置api更改此设置。 修改分词器以及定义自己的分词器Elasticsearch附带了各种内置分析器,无需进一步配置即可在任何索引中使用: standard analyzer: 所述standard分析器将文本分为在字边界条件,由Unicode的文本分割算法所定义的。它删除了大多数标点符号,小写术语,并支持删除停用词。Simple analyzer:该simple分析仪将文本分为方面每当遇到一个字符是不是字母。然后全部变为小写whitespace analyzer: whitespace只要遇到任何空格字符 ,分析器就会将文本划分为术语。它不会进行小写转换。stop analyzer: 该stop分析器是像simple,而且还支持去除停止词。keyword analyzer: 所述keyword分析器是一个“空操作”分析器接受任何文本它被赋予并输出完全相同的文本作为一个单一的术语,也就是不会分词,进行精确匹配。pattern analyzer: 所述pattern分析器使用一个正则表达式对文本进行拆分。它支持小写转换和停用字。language analyzer: Elasticsearch提供了许多特定于语言的分析器,如english或 french。fingerprint analyzer: 所述fingerprint分析器是一种专业的指纹分析器,它可以创建一个指纹,用于重复检测。修改分词器的设置启动english停用词token filter ...

May 2, 2019 · 6 min · jiezi

elasticsearch学习笔记三十四Elasticsearch-基于scoll技术滚动搜索大量数据

在实际应用中,通过from+size不可避免会出现深分页的瓶颈,那么通过scoll技术就是一个很好的解决深分页的方法。比如如果我们一次性要查出10万条数据,那么使用from+size很显然性能会非常的差,priority queue会非常的大。此时如果采用scroll滚动查询,就可以一批一批的查,直到所有数据都查询完。 scroll原理scoll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的。而且ES内部是基于_doc进行排序的方式,性能较高。示例: POST /test_index/_search?scroll=1m{ "query": { "match_all": {} }, "sort": [ "_doc" ], "size": 3}{ "_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABu4oWUC1iLVRFdnlRT3lsTXlFY01FaEFwUQ==", "took" : 7, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 10, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "test_index", "_type" : "_doc", "_id" : "1", "_score" : null, "_source" : { "field1" : "one" }, "sort" : [ 0 ] }, { "_index" : "test_index", "_type" : "_doc", "_id" : "2", "_score" : null, "_source" : { "field1" : "two" }, "sort" : [ 1 ] }, { "_index" : "test_index", "_type" : "_doc", "_id" : "3", "_score" : null, "_source" : { "field1" : "three" }, "sort" : [ 2 ] } ] }}POST /_search/scroll{ "scroll": "1m", "scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABu4oWUC1iLVRFdnlRT3lsTXlFY01FaEFwUQ=="}{ "_scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABu4oWUC1iLVRFdnlRT3lsTXlFY01FaEFwUQ==", "took" : 1, "timed_out" : false, "terminated_early" : true, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 10, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "test_index", "_type" : "_doc", "_id" : "4", "_score" : null, "_source" : { "field1" : "four" }, "sort" : [ 3 ] }, { "_index" : "test_index", "_type" : "_doc", "_id" : "5", "_score" : null, "_source" : { "field1" : "five" }, "sort" : [ 4 ] }, { "_index" : "test_index", "_type" : "_doc", "_id" : "6", "_score" : null, "_source" : { "field1" : "six" }, "sort" : [ 5 ] } ] }}

May 1, 2019 · 2 min · jiezi

elasticsearch学习笔记三十三Elasticsearch-Bouncing-Results问题

想象一下有两个文档有同样值的时间戳字段,搜索结果用 timestamp 字段来排序。 由于搜索请求是在所有有效的分片副本间轮询的,那就有可能发生主分片处理请求时,这两个文档是一种顺序, 而副本分片处理请求时又是另一种顺序。 这就是所谓的 bouncing results 问题: 每次用户刷新页面,搜索结果表现是不同的顺序。 让同一个用户始终使用同一个分片,这样可以避免这种问题, 可以设置 preference 参数为一个特定的任意值比如用户会话ID来解决。对于preference详细请查看https://www.elastic.co/guide/... 偏好这个参数 preference 允许 用来控制由哪些分片或节点来处理搜索请求。 它接受像 _primary, _primary_first, _local, _only_node:xyz, _prefer_node:xyz, 和 _shards:2,3 这样的值, 这些值在 search preference 文档页面被详细解释。但是最有用的值是某些随机字符串,它可以避免 bouncing results 问题。例如,使用用户的会话ID xyzabc123,如下所示: GET /test_index/_search?preference=xyzabc123{ "query": { "match": { "test_field": "hello" } }}{ "took" : 1, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : 0.20521778, "hits" : [ { "_index" : "test_index", "_type" : "_doc", "_id" : "2", "_score" : 0.20521778, "_source" : { "test_field" : "hello, how are you" } }, { "_index" : "test_index", "_type" : "_doc", "_id" : "1", "_score" : 0.16402164, "_source" : { "test_field" : "hello you, and world is very good" } } ] }}

May 1, 2019 · 1 min · jiezi

elasticsearch学习笔记三十二Elasticsearch-解密queryfetch-phrase原理

query pharse基本原理:(1)搜索请求发送到某一个coordinate node协调节点,会构建一个priority queue,长度以paging操作from和size为准,默认是10(2)coordinate node将请求转发到所有的shard,每个shard本地搜索,并构建一个本地的priority queue(3)各个shard将自己的priority queue返回给coordinate node,并构建一个全局的priority queue fetch phrase基本原理:(1)coordinate node协调节点构建完priority queue之后,就发送mget请求去所有shard上获取对应的document(2)各个shard将document返回给coordinate node(3)coordinate node将合并后的document结果返回给客户端。 也就是ES的query pharse是根据priority queue去构建搜索结果的示例elasticsearch学习笔记(二十三)——Elasticsearch 分页搜索以及深分页性能问题https://segmentfault.com/a/11...比如总共有60000条数据,三个primary shard,每个shard上分了20000条数据,每页是10条数据,这个时候,你要搜索到第1000页,实际上要拿到的是10001~10010,也就是会构建一个10010大小的priority queue。 注意这里千万不要理解成每个shard都是返回10条数据。这样理解是错误的! 下面做一下详细的分析:请求首先可能是打到一个不包含这个index的shard的node上去,这个node就是一个协调节点coordinate node,那么这个coordinate node就会将搜索请求转发到index的三个shard所在的node上去。比如说我们之前说的情况下,要搜索60000条数据中的第1000页,实际上每个shard都要将内部的20000条数据中的第10001~10010条数据,拿出来,不是才10条,是10010条数据。3个shard的每个shard都返回10010条数据给协调节点coordinate node,coordinate node会收到总共30030条数据,此时会构建一个30030大小的priority queue,然后在这些数据中进行排序,根据_score相关度分数,然后取到10001~10010这10条数据,就是我们要的第1000页的10条数据。如下图所示:

May 1, 2019 · 1 min · jiezi

elasticsearch学习笔记三十一Elasticsearch-doc-value正排索引

在我们搜索的时候,要依靠倒排索引,但是当我们排序的时候,需要依靠正排索引。通过倒排索引锁定文档document之后,看到每个document的每个field,然后进行排序,所谓的正排索引就是doc values。对于ES而言,在建立索引的时候,一方面会建立倒排索引,以供搜索使用;一方面会建立正排索引,也就是doc values,以供排序,聚合,过滤等使用。doc values是被保存在磁盘上的,此时如果内存足够,OS操作系统会自动将其缓存在内存中,性能还是会很高的,如果内存不够用,OS操作系统会将其写入磁盘。下面举个例子描述正排索引和倒排索引假设某个index有两个doc doc1 : hello world you and medoc2 : hi world, how are you建立倒排索引 word doc1 doc2hello *world * *you * *and *me *hi *how *are *假设某个index有两个doc doc1: {"name": "jack", "age": 27}doc2: {"name": "tom", "age": 30}建立正排索引 document name agedoc1 jack 27doc2 tom 30

May 1, 2019 · 1 min · jiezi

elasticsearch学习笔记三十Elasticsearch-相关度评分-TFIDF算法

Elasticsearch的相关度评分(relevance score)算法采用的是term frequency/inverse document frequency算法,简称为TF/IDF算法。 算法介绍relevance score算法,简单来说就是,就是计算出一个索引中的文本,与搜索文本,它们之间的关联匹配程度。TF/IDF算法,分为两个部分,IF 和IDFTerm Frequency(TF): 搜索文本中的各个词条在field文本中出现了多少次,出现的次数越多,就越相关例如:搜索请求:hello worlddoc1: hello you, and world is very gooddoc2: hello, how are you那么此时根据TF算法,doc1的相关度要比doc2的要高 Inverse Document Frequency(IDF): 搜索文本中的各个词条在整个索引的所有文档中出现的次数,出现的次数越多,就越不相关。搜索请求: hello worlddoc1: hello, today is very good.doc2: hi world, how are you.比如在index中有1万条document, hello这个单词在所有的document中,一共出现了1000次,world这个单词在所有的document中一共出现100次。那么根据IDF算法此时doc2的相关度要比doc1要高。 对于ES还有一个Field-length normfield-length norm就是field长度越长,相关度就越弱搜索请求:hello worlddoc1: {"title": "hello article", "content": "1万个单词"}doc2: {"title": "my article", "content": "1万个单词, hi world"}此时hello world在整个index中出现的次数是一样多的。但是根据Field-length norm此时doc1比doc2相关度要高。因为title字段更短。 _score是如何被计算出来的GET /test_index/_search?explain=true{ "query": { "match": { "test_field": "hello" } }}{ "took" : 9, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : 0.20521778, "hits" : [ { "_shard" : "[test_index][0]", "_node" : "P-b-TEvyQOylMyEcMEhApQ", "_index" : "test_index", "_type" : "_doc", "_id" : "2", "_score" : 0.20521778, "_source" : { "test_field" : "hello, how are you" }, "_explanation" : { "value" : 0.20521778, "description" : "weight(test_field:hello in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.20521778, "description" : "score(freq=1.0), product of:", "details" : [ { "value" : 2.2, "description" : "boost", "details" : [ ] }, { "value" : 0.18232156, "description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:", "details" : [ { "value" : 2, "description" : "n, number of documents containing term", "details" : [ ] }, { "value" : 2, "description" : "N, total number of documents with field", "details" : [ ] } ] }, { "value" : 0.5116279, "description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:", "details" : [ { "value" : 1.0, "description" : "freq, occurrences of term within document", "details" : [ ] }, { "value" : 1.2, "description" : "k1, term saturation parameter", "details" : [ ] }, { "value" : 0.75, "description" : "b, length normalization parameter", "details" : [ ] }, { "value" : 4.0, "description" : "dl, length of field", "details" : [ ] }, { "value" : 5.5, "description" : "avgdl, average length of field", "details" : [ ] } ] } ] } ] } }, { "_shard" : "[test_index][0]", "_node" : "P-b-TEvyQOylMyEcMEhApQ", "_index" : "test_index", "_type" : "_doc", "_id" : "1", "_score" : 0.16402164, "_source" : { "test_field" : "hello you, and world is very good" }, "_explanation" : { "value" : 0.16402164, "description" : "weight(test_field:hello in 0) [PerFieldSimilarity], result of:", "details" : [ { "value" : 0.16402164, "description" : "score(freq=1.0), product of:", "details" : [ { "value" : 2.2, "description" : "boost", "details" : [ ] }, { "value" : 0.18232156, "description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:", "details" : [ { "value" : 2, "description" : "n, number of documents containing term", "details" : [ ] }, { "value" : 2, "description" : "N, total number of documents with field", "details" : [ ] } ] }, { "value" : 0.40892193, "description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:", "details" : [ { "value" : 1.0, "description" : "freq, occurrences of term within document", "details" : [ ] }, { "value" : 1.2, "description" : "k1, term saturation parameter", "details" : [ ] }, { "value" : 0.75, "description" : "b, length normalization parameter", "details" : [ ] }, { "value" : 7.0, "description" : "dl, length of field", "details" : [ ] }, { "value" : 5.5, "description" : "avgdl, average length of field", "details" : [ ] } ] } ] } ] } } ] }}匹配的文档有两个,下面直接用一个文档来分析出ES各个算法的公式。从上面可以看出第一个文档的相关度分数是0.20521778 ...

May 1, 2019 · 5 min · jiezi

elasticsearch学习笔记二十九Elasticsearch-将一个field索引两次来解决字符串排序问题

下面描述一个场景如果某个字段的类型是text,在创建索引的时候,针对每个document,对应的这个text字段都会对内容进行分词。由于ES不允许对已经存在的field的类型进行修改,就会导致该字段一直都是会被分词,那么如果之后有需求想对该字段排序,就不行了。具体看下面展示的示例。 PUT /test_index{ "mappings": { "properties": { "field1": { "type": "text" } } }}GET /test_index/_search{ "query": { "match_all": {} }, "sort": [ { "field1": { "order": "desc" } } ]}{ "error": { "root_cause": [ { "type": "illegal_argument_exception", "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [field1] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead." } ], "type": "search_phase_execution_exception", "reason": "all shards failed", "phase": "query", "grouped": true, "failed_shards": [ { "shard": 0, "index": "test_index", "node": "P-b-TEvyQOylMyEcMEhApQ", "reason": { "type": "illegal_argument_exception", "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [field1] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead." } } ], "caused_by": { "type": "illegal_argument_exception", "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [field1] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead.", "caused_by": { "type": "illegal_argument_exception", "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [field1] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead." } } }, "status": 400}这个时候要想解决这个问题,就可以将一个string field建立两次索引,一个分词用来搜索,一个不分词用来进行排序。Alternatively use a keyword field instead.根据错误中的提示,我们重新建立索引 ...

May 1, 2019 · 2 min · jiezi

网络分析利器wireshark命令版4tshark结合ES

tshark是网络分析工具wireshark下的一个工具,主要用于命令行环境进行抓包、分析,尤其对协议深层解析时,tcpdump难以胜任的场景中。本系列文章将整理介绍tshark相关内容。本文将介绍与tshark相关的流量解决方案。 利用tshark,不仅可以对现有的pcap文件进行分析,由于可以输出其他格式,也就可以结合ES的强大搜索能力,达到对数据报文进行记录、分析处理的能力,可以实现回溯分析,结合kibana可视化工具,甚至达到实时可视化监控。 tshark + elastic stackelastic stack全家桶性能一直被诟病,后来另起炉灶,针对采集使用golang构建出一套beats,用于不同的采集场景。其中针对网络流量,开发出packetbeat。 packetbeat的优势是定制了elasticsearch的mapping、kibana一系列可视化图表,可以满足一般对tcp、dns、udp等常规报文的分析。基本达到开箱即用程度。 但packetbeat也有不足,对报文的分析采用会话分析,没有一个个报文单独分析,倾向于应用层,对网络层面分析不足(尤其是故障排查时),此外,支持的协议有限,仅常见协议与tshark的2000多种存在明显差距,当遇到不支持时,需要等待支持或手动写插件,难度极高。 离线导入elasticsearchtshark支持将pcap报文分析后生成json文件导入elasticsearch,同时支持elasticsearch的批量导入接口_bulk的格式,命令如下: tshark -r test_trace.pcap -T ek > test_trace.pcap.json之后可以将json文件通过curl导入。 curl -s -H "Content-Type: application/x-ndjson" -XPOST "localhost:9200/foo/_bulk" --data-binary "@/Users/test-elastic/test_trace.pcap.json"注: 导入时可能存在导入失败,由于_bulk接口对post的文件大小有限制,尽量不要超过15MB,最好在10MB以内。如果超过,建议使用tshark的输出条件生成多个json文件,使用curl依次导入。如果导入失败,可以在curl加-v查看提示信息。默认索引名为类似 packets-2019-04-23(报文记录的日期),可以导入后重新索引可以使用类似以下命令查看导入情况: curl 'http://127.0.0.1:9200/packets-2019-04-23/_search/?size=10&pretty=true'实时监控方案主要思路使用tshark实时抓取报文,并启用过滤策略使用tshark解析捕获的报文,提取指定的字段并写入csv文件中,或者使用json格式在下一步进行ETL writes captured wireless packets as .csv.使用filebeat持续检测csv文件,并发个logstash用于字段过滤之类(如无需过滤可以跳过logstash直接发给elastsearch)logstash对字段进行过滤,格式转化等,之后发到elasticsearch使用kibana进行数据可视化,对报文统计分析简单实现1. tshark部分tshark -a duration:600 -i phy0.mon -t ad -t ad -lT fields -E separator=, -E quote=d -e _ws.col.Time -e wlan.fc.type -e wlan.fc.type_subtype -e radiotap.dbm_antsignal -e frame.len -e radiotap.datarate > tshark.csv2. filebeat简单filebeat.yml配置文件 ...

May 1, 2019 · 2 min · jiezi

ES

搜索引擎和NoSQL数据库功能,用于全文搜索,结构化搜索,近实时分析指南:https://es.xiaoleilu.com/020_...常用:E(存储和索引)L(数据转换和解析Beat采集过来)K(可视化) Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 ElasticsearchLocal FileSystem等。索引信息,集群内信息,mapping,transaction loglucene directory lucene不同存储层服务发现的抽象,FSDirectory,RAWDirectory 控制索引文件的位置discovery Zen 服务发现+选主driver:数据源 racketmq等memcached协议:get/set/delete/quit 高可用和可扩展1。集群发现与故障处理:服务发现+选主,ping,rsp包含节点基本信息和该节点认为的master节点,支持非奇数,信息不对等时会有脑裂,要求ping所有节点。返回其他节点认为的主节点,也返回候选主节点(配置中master!=false)。如果第一个不为空,直接选节点最小的,若为空,候选节点>discovery.zen.minimum_master_nodes则选一个状态+nodeid最小的,否则失败。思想就是有旧的master活着尽量用。主节点是群集中唯一可以更改群集状态的节点。主节点一次处理一个群集状态更新,应用所需的更改并将更新的群集状态发布到群集中的所有其他节点。每个节点接收发布消息,承认它,但它不是还没有应用它。如果主服务器discovery.zen.minimum_master_nodes在一定时间内没有从至少节点收到确认(由discovery.zen.commit_timeout设置控制并默认为30秒),则拒绝集群状态更改,重新选主。无主时默认不能写2.负载均衡:轮询3.分片机制:索引键值hash%分片数量。主分片+副本分片水平扩展(分片数量不能改变,只能改变每个节点有几个分片,每个分片副本分散到哪些节点)单节点存储瓶颈等时扩展:处理瓶颈时扩展: master和节点统一。master只负责索引的创建和删除 数据处理过程节点对等写入和读取。写入只能在P分片然后同步复制到R分片,读写转发到主节点或副本节点1.更新过程: 2.搜索:需要先查询排序再取回排序中想要的数据其中查询:1).node3创建from+size优先队列2).请求转发个每个分片,每个分片自己获取from+size本地优先队列3).返回给node3合并产生全局排序搜索有两种:基于短语的、全文索引1)基于短语的:低级查询,没有分析,精确查找(加not_analyzed)2)match,query_string这种高级查询,会产生短语列表和低级查询结合,得到文档相关度。 Elasticsearch通过下面的步骤执行match查询:检查field类型title字段是一个字符串(analyzed),所以该查询字符串也需要被分析(analyzed)分析查询字符串查询词QUICK!经过标准分析器的分析后变成单词quick。因为我们只有一个查询词,因此match查询可以以一种低级别term查询的方式执行。找到匹配的文档term查询在倒排索引中搜索quick,并且返回包含该词的文档。为每个文档打分term查询综合考虑词频(每篇文档title字段包含quick的次数)、逆文档频率(在全部文档中title字段包含quick的次数)、包含quick的字段长度(长度越短越相关)来计算每篇文档的相关性得分_score。(更多请见相关性介绍)因为match查询需要查询两个关键词:"brown"和"dog",在内部会执行两个term查询并综合二者的结果得到最终的结果。match的实现方式是将两个term查询放入一个bool查询数据组织关系数据库 ⇒ 数据库 ⇒ 表 ⇒ 行 ⇒ 列(Columns)Elasticsearch ⇒ 索引(=》分片=》segment) ⇒ 类型 ⇒ 文档 ⇒ 字段(Fields)索引只是一个用来指向一个或多个分片(shards)的“逻辑命名空间(logical namespace)”.分片就是一个Lucene实例,文档存储在分片中,并且在分片中被索引每个分片上包含此分片的所有数据索引和数据,我们的elk,每天都是一个新库(index),为其建立traceid,urlkey索引的含义只是创建索引,并不对所有词都建立倒排索引,虽然本身es每个词都可搜索 词=》倒排索引,包含每个filed(term)在每个文档中的值写入磁盘的倒排索引是不可变的.不需要加锁,不需要重建任何缓存,可以压缩数据动态索引、近实时索引: Luence per-segment search 索引:段的集合+提交点(包含所有段的文件) 1.当一个文档被索引,它被加入到内存缓存,同时加到事务日志。2.refresh使得分片的进入如下图描述的状态。每秒分片都进行refeash:内存缓冲区的文档写入到段中,但没有fsync。段被打开,使得新的文档可以搜索。缓存被清除3.随着更多的文档加入到缓存区,写入日志,这个过程会继续4.不时地,比如日志很大了,新的日志会创建,会进行一次全提交:内存缓存区的所有文档会写入到新段中。清除缓存一个提交点写入硬盘文件系统缓存通过fsync操作flush到硬盘事务日志被清除

May 1, 2019 · 1 min · jiezi

bulk写es超时问题

背景笔者维护的线上EFK集群,在每天早上8点创建新索引的时候,日志中总会出现如下的日志: failed to process cluster event (cluster_update_settings) within 30s[2019-04-13T08:00:38,213][DEBUG][o.e.a.b.TransportShardBulkAction] [logstash-felice-query-2019.04.13][2] failed to execute bulk item (index) BulkShardRequest [[logstash-felice-query-2019.04.13][2]] containing [7] requestsorg.elasticsearch.cluster.metadata.ProcessClusterEventTimeoutException: failed to process cluster event (put-mapping) within 30s at org.elasticsearch.cluster.service.MasterService$Batcher.lambda$onTimeout$0(MasterService.java:124) ~[elasticsearch-6.3.0.jar:6.3.0] at java.util.ArrayList.forEach(ArrayList.java:1257) ~[?:1.8.0_152] at org.elasticsearch.cluster.service.MasterService$Batcher.lambda$onTimeout$1(MasterService.java:123) ~[elasticsearch-6.3.0.jar:6.3.0] at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:625) ~[elasticsearch-6.3.0.jar:6.3.0] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_152] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_152] at java.lang.Thread.run(Thread.java:748) [?:1.8.0_152]问题原因简言之:es的master处理不过来了,es的segment合并是一个非常耗时的操作。批量处理的超时时间默认设置为30s。可以通过以下命令查看pending的task: curl 'localhost:9200/_cat/pending_tasks?v'网上说法不一,出现这个问题很可能的原因可以总结如下: 分片数过多;单节点的分片数不要超过1000个(经验值);通过写入数据自动创建索引最容易出现这种情况;大批量写入数据refresh时间间隔太短;索引的字段数量太多(几十上百个)解决方案方案一:每天预创建索引虽然普通的EFK日志服务可以通过logstash的默认模板去创建索引,但是当索引个数比较大,并且索引的字段数量太多时,就非常容易出现超时。那么我们一般的做法是提前创建好索引,并且设定好每个索引的mapping。笔者采用了一种偷懒的操作,写了一个定时任务,每天去读取当天的索引的mapping,然后直接用这个mapping去创建新的索引。当然这种解决方案的前提是你自己已经对索引有了一个基本的管理。 方案二:减少每个节点的分片数es默认为每个索引设置的分片数是5。可以通过以下命令查看索引的settings: GET /logstash-sonofelice-2019.04.27可以在创建索引的时候,优化一下分片的个数。可以参考文章:https://www.cnblogs.com/gugul... 一句话:shard越少越好。shard越少,越能减少写入开销。 方案三:集群扩容如果是偶发性的写入超时,可能是流量高峰或者新创建索引的时候创建mapping新字段导致。但是如果经常性的出现超时,并且write_queue里面的数据太多,cpu使用率居高不下,那可能就是es集群出现了瓶颈,这时候就需要进行扩容,没有更好的办法。 参考资料https://github.com/elastic/el...

April 30, 2019 · 1 min · jiezi

Spring-boot-整合-Elasticsearch

1. 概述前面学习了 Elasticsearch 的简单基本操作,例如安装,基本的操作命令等,今天就来看看 es 和 Spring boot 的简单整合,实现增删改查的功能。众所周知,Spring boot 支持多种 NoSql 数据库,例如 redis、mongodb,elasticsearch 也是其中的一种。并且实现了 Spring boot 一贯的自动化配置,使用起来也是十分方便的。 2. 新建项目新建一个 spring boot 项目,在 NoSql 这一栏选中 Elasticsearch 。 然后在配置文件中加上 es 的配置: spring: data: elasticsearch: cluster-nodes: 192.168.66.135:9300 repositories: enabled: true cluster-name: elasticsearch注意这里使用的是集群的 9300 端口,而不是 es 默认的 9200 端口。cluster-name 默认就是 elasticsearch,不写也可以的。 3. 简单操作项目建好之后,可以来试试简单的操作。只要你使用过 Spring Data Jpa,那么理解起来就非常的容易了,因为用法都是类似的。 1.首先需要新建一个实体类,表示这种实体类的数据,做为 es 的文档存放。 @Data@Builder@NoArgsConstructor@AllArgsConstructor@Document(indexName = "product", type = "computer")public class Product { private String id; private String name; private double price; private String brand; private String color;}@Document 注解上面可以指定 index 以及 type 的名称。 ...

April 30, 2019 · 1 min · jiezi

elasticsearch学习笔记二十七Elasticsearch-filter与query

filter和query示例PUT /employee/_doc/1{ "address": { "country": "china", "province": "jiangsu", "city": "nanjing" }, "name": "tom", "age": 30, "join_date": "2016-01-01"}PUT /employee/_doc/2{ "address": { "country": "china", "province": "shanxi", "city": "xian" }, "name": "marry", "age": 35, "join_date": "2015-01-01"}搜索请求:年龄必须大于等于30,同时join_date必须是2016-01-01 GET /employee/_search{ "query": { "bool": { "must": [ { "match": { "join_date": "2016-01-01" } } ], "filter": { "range": { "age": { "gte": 30 } } } } }}{ "took" : 141, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ { "_index" : "employee", "_type" : "_doc", "_id" : "1", "_score" : 1.0, "_source" : { "address" : { "country" : "china", "province" : "jiangsu", "city" : "nanjing" }, "name" : "tom", "age" : 30, "join_date" : "2016-01-01" } } ] }}filter与query对比大揭秘filter:仅仅只是按照搜索条件过滤出需要的数据而已,比计算任何相关度分数,对相关度没有任何影响query: 会计算每个document相对于搜索条件的相关度,并按照相关度进行排序一般来说,如果是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用query。如果只是要根据一些条件筛选出一部分数据,不关注其排序,那么用filter。 ...

April 30, 2019 · 1 min · jiezi

聊聊elasticsearch的PeerFinder

序本文主要研究一下elasticsearch的PeerFinder PeersRequestelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/PeersRequest.java public class PeersRequest extends TransportRequest { private final DiscoveryNode sourceNode; private final List<DiscoveryNode> knownPeers; public PeersRequest(DiscoveryNode sourceNode, List<DiscoveryNode> knownPeers) { assert knownPeers.contains(sourceNode) == false : "local node is not a peer"; this.sourceNode = sourceNode; this.knownPeers = knownPeers; } public PeersRequest(StreamInput in) throws IOException { super(in); sourceNode = new DiscoveryNode(in); knownPeers = in.readList(DiscoveryNode::new); } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); sourceNode.writeTo(out); out.writeList(knownPeers); } public List<DiscoveryNode> getKnownPeers() { return knownPeers; } public DiscoveryNode getSourceNode() { return sourceNode; } @Override public String toString() { return "PeersRequest{" + "sourceNode=" + sourceNode + ", knownPeers=" + knownPeers + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PeersRequest that = (PeersRequest) o; return Objects.equals(sourceNode, that.sourceNode) && Objects.equals(knownPeers, that.knownPeers); } @Override public int hashCode() { return Objects.hash(sourceNode, knownPeers); }}PeersRequest有两个属性,分别是sourceNode以及knownPeersPeersResponseelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/cluster/coordination/PeersResponse.java ...

April 29, 2019 · 8 min · jiezi

elasticsearch分片策略

1.什么是分片Elasticsearch中的数据组织成索引。每一个索引由一个或多个分片组成。每个分片是Luncene索引的一个实例,Luncene实例可以理解成自管理的搜索引擎,用于在Elasticsearch集群中对一部分数据进行索引和处理查询。 2.分片和副本index 包含多个 shard,创建 index 时可以在settings中设置分片数,不设置时默认是5个每个shard 都是一个最小工作单元,承载部分数据;每个 shard 都是一个 lucene 实例,并且具有完整的建立索引和处理能力。primary shard 的数量在创建索引的时候就固定了,不可更改replica shard 是 primary shard 的副本,负责容错,以及承担读请求负载shard一般情况最大为50Gprimary shard 的数量在创建索引的时候就固定了,不可更改;replica shard 的数量可以随时修改

April 29, 2019 · 1 min · jiezi

Elasticsearch-查询和数据同步-记一次技术实践

前言前段时间与同事一起为产品接入了 Elasticsearch 框架技术。从参与方案会议到搭建开发上线过程中有很多讨论点,故产生本文,希望藉此总结和分享一些经验。 1. 业务模型接触已有的业务时,数据模型是最早需要知道的信息。我和同事负责接入 Elasticsearch 的产品是一个业务繁多的通讯录,简化下来就是 3 个关键的模型,如下: 部门(Department)人员(User)标签(Tag)它们的用途和联系,就跟它们的词义一样。由此产生的业务如下: 通过 标签 查询 部门、人员通过 部门 查询 人员 基于以上模型和业务,在典型的关系型数据库下,为了实现关联关系,自然会有额外的关联表: 部门人员关联表:每条记录包含1个部门,1个人员标签对象关联表:每条记录包含1个标签,1个部门或人员2. 需求Elasticsearch 的特点有全文检索、分布式、海量数据下近实时查询。当时为通讯录业务引入 Elasticsearch 的需求和目标如下: 多字段的匹配或模糊查询。这些部门、人员、标签数据原本存储在 MySQL 中,如果要做匹配多个字段的模糊查询就比较吃力了,考虑一个常用功能 “输入姓名/手机号/拼音/首字母来查询人员”。而快速查询此类业务是 Elasticsearch 可以提供的。基础模块能力。其他业务模块也提出了类似全文检索的需求,因此在通讯录业务首次应用 es 时,要定义和提供好 es 的访问和工具方法,供其他模块在未来接入时,能复用一些实现,能保持一致的接口和命名风格等。3. 索引设计从原 MySQL 数据库表,到 Elasticsearch 的索引,数据模型的变化称为异构。Elasticsearch 适合解决在 MySQL 中多条件或连表这样比较慢的查询业务,因此除了原有的信息字段,我们会再附加 3 个模型的关联关系到 es 索引中。 索引 字段原有关联关系部门部门名<br/>完整部门路径名(无)人员姓名<br/>拼音<br/>首字母<br/>手机号父部门Id<br/>所有父级部门Id<br/>标签Id标签标签名部门Id<br/>人员Id(上表略去了一些无关本篇内容的字段,如 SaaS 平台的租户Id、每个对象的信息详情字段) 是否需要添加关联关系的字段,是由业务需求决定的。拿人员索引的 “所有父级部门Id” 举例子,因为有查询部门下所有人员(包括直属、子部门下的)的业务需求,所以会设计这么一个字段。可以使用 Elasticsearch 的分词功能来记录关联关系的字段中。为该字段定义一个分隔模式为竖线 “|” 的分词器,把若干个关联Id存成一个拼接的字符串。4. 版本选择同事是个版本控,在选择版本时了解和考虑了非常多的信息。不过版本选择确实是为平台接入新技术时的一个重要考虑点。我们提出这个方案的当时(2018年4月),对比了主要使用的云服务提供商的几个版本,考虑项可以按优先级概括为: 稳定的案例资料多的时新程度,包括 Elasticsearch版本 和 Lucene版本我们已经使用了某家云服务提供商,会偏向再用其提供的服务几个版本对比我们当时选择了 Elasticsearch 6.2.2 版本。 v5.6.4是 Spring 整合的各个框架中,支持数最多的版本市面使用人数较多,资料较多其依赖的 Lucene 大版本是v6,较旧v6.2.2是当时稳定的版本中最新的,性能比 v5 好v6.2.4是当时最新的版本,修复了许多 bug性能更好,是官方推荐的版本官方的技术文档部分还没更新,得看旧文档市面上找不到相应的人的使用资料版本发展(于2019年4月)在写本篇文章时,我再去了解了和 Elasticsearch版本 相关的变更: ...

April 28, 2019 · 1 min · jiezi

聊聊elasticsearch的ZenPing

序本文主要研究一下elasticsearch的ZenPing ZenPingelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/zen/ZenPing.java public interface ZenPing extends Releasable { void start(); void ping(Consumer<PingCollection> resultsConsumer, TimeValue timeout); class PingResponse implements Writeable { //...... } class PingCollection { //...... }}ZenPing接口继承了Releasable接口,另外它还定义了start、ping方法;除此之外还定义了PingResponse、PingCollection这两个类PingResponseelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/zen/ZenPing.java class PingResponse implements Writeable { /** * An ID of a ping response that was generated on behalf of another node. Needs to be less than all other ping IDs so that fake ping * responses don't override real ones. */ public static long FAKE_PING_ID = -1; private static final AtomicLong idGenerator = new AtomicLong(); // an always increasing unique identifier for this ping response. // lower values means older pings. private final long id; private final ClusterName clusterName; private final DiscoveryNode node; private final DiscoveryNode master; private final long clusterStateVersion; /** * @param node the node which this ping describes * @param master the current master of the node * @param clusterName the cluster name of the node * @param clusterStateVersion the current cluster state version of that node * ({@link ElectMasterService.MasterCandidate#UNRECOVERED_CLUSTER_VERSION} for not recovered) */ public PingResponse(DiscoveryNode node, DiscoveryNode master, ClusterName clusterName, long clusterStateVersion) { this(idGenerator.incrementAndGet(), node, master, clusterName, clusterStateVersion); } /** * @param id the ping's ID * @param node the node which this ping describes * @param master the current master of the node * @param clusterName the cluster name of the node * @param clusterStateVersion the current cluster state version of that node* ({@link ElectMasterService.MasterCandidate#UNRECOVERED_CLUSTER_VERSION} for not recovered) */ public PingResponse(long id, DiscoveryNode node, DiscoveryNode master, ClusterName clusterName, long clusterStateVersion) { this.id = id; this.node = node; this.master = master; this.clusterName = clusterName; this.clusterStateVersion = clusterStateVersion; } public PingResponse(DiscoveryNode node, DiscoveryNode master, ClusterState state) { this(node, master, state.getClusterName(), state.blocks().hasGlobalBlock(STATE_NOT_RECOVERED_BLOCK) ? ElectMasterService.MasterCandidate.UNRECOVERED_CLUSTER_VERSION : state.version()); } PingResponse(StreamInput in) throws IOException { this.clusterName = new ClusterName(in); this.node = new DiscoveryNode(in); this.master = in.readOptionalWriteable(DiscoveryNode::new); this.clusterStateVersion = in.readLong(); this.id = in.readLong(); } @Override public void writeTo(StreamOutput out) throws IOException { clusterName.writeTo(out); node.writeTo(out); out.writeOptionalWriteable(master); out.writeLong(clusterStateVersion); out.writeLong(id); } /** * an always increasing unique identifier for this ping response. * lower values means older pings. */ public long id() { return this.id; } /** * the name of the cluster this node belongs to */ public ClusterName clusterName() { return this.clusterName; } /** the node which this ping describes */ public DiscoveryNode node() { return node; } /** the current master of the node */ public DiscoveryNode master() { return master; } /** * the current cluster state version of that node ({@link ElectMasterService.MasterCandidate#UNRECOVERED_CLUSTER_VERSION} * for not recovered) */ public long getClusterStateVersion() { return clusterStateVersion; } @Override public String toString() { return "ping_response{node [" + node + "], id[" + id + "], master [" + master + "]," + "cluster_state_version [" + clusterStateVersion + "], cluster_name[" + clusterName.value() + "]}"; } }PingResponse实现了Writeable接口,其writeTo方法会依次写入clusterName、node、master、clusterStateVersion、idPingCollectionelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/zen/ZenPing.java ...

April 28, 2019 · 11 min · jiezi

5Elasticsearch-Query-DSL-Match-All-Query

match_all匹配所有文档,返回的分数自然为1.0 GET /bank/_search{ "query": { "match_all": {} }}改变分数 GET /bank/_search{ "query": { "match_all": { "boost": 1.2 } }}match_none不匹配任何文档 GET /bank/_search{ "query": { "match_none":{} }}

April 27, 2019 · 1 min · jiezi

4Elasticsearch-Query-DSL

Query DSL 简介Elasticsearch提供了基于JSON的、完整的Query DSL(Domain Specific Language,领域特定语言)。把Query DSL想象成AST(Abstract Syntax Tree,抽象语法树),这样就有叶子子句和复合(容器)子句。 叶子子句:叶子子句可以单独使用,一般用来查找指定字段的指定值,例如match、term、range。复合子句:组合叶子子句或其他复合子句,一般用来逻辑组合多查询(例如bool)或改变行为(例如constant_score)。Query Context | Filter ContextQuery Context 除了匹配文档,还会计算匹配程度(相关性算分),分数越高匹配程度越高。Filter Context Filter Context只进行过滤(是|否),不计算匹配程度(相关性算分)。ELasticsearch会自动缓存常用的过滤,以提高性能。总之,把需要计算匹配程度的条件放到Query Context下,其余尽量放到Filter Context下。放到Query Context下算分,放到Filter Context缓存,提高性能。

April 27, 2019 · 1 min · jiezi

elasticsearch学习笔i记二十五Elasticsearch-mapping详解以及索引内部原理

下面先简单描述一下mapping是什么?当我们插入几条数据,让ES自动为我们建立一个索引 PUT /website/_doc/1{ "post_date": "2017-01-01", "title": "my first article", "content": "this is my first article in this website", "author_id": 11400}PUT /website/_doc/2{ "post_date": "2017-01-02", "title": "my second article", "content": "this is my second article in this website", "author_id": 11400}PUT /website/_doc/3{ "post_date": "2017-01-03", "title": "my third article", "content": "this is my third article in this website", "author_id": 11400}查看mapping GET /website/_mapping{ "website" : { "mappings" : { "properties" : { "author_id" : { "type" : "long" }, "content" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "post_date" : { "type" : "date" }, "title" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } }}上面是插入数据自动生成的mapping,还有手动生成的mapping。这种自动或手动为index中的type建立的一种数据结构和相关配置,称为mapping。下面是手动创建的mapping。 ...

April 27, 2019 · 5 min · jiezi

elasticsearch学习笔i记二十六Elasticsearch-query-DSL搜索实战

下面先解释一下ES的GET+request body这种模式一般我们知道HTTP协议一般是不允许get请求带上request body,但是因为get更加适合描述查询数据的操作,因此还是这么用了。碰巧当前很多浏览器或是服务器也都支持GET+request body模式如果遇到不支持的场景,也可以用POST+request body模式. 1、 一个例子让你明白什么是query DSLGET /website/_search{ "query": { "match_all": {} }}2、query DSL的基本语法GET /{index}/_search{ "各种条件"}示例: GET /website/_search{ "query": { "match": { "title": "article" } }}{ "took" : 3, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 3, "relation" : "eq" }, "max_score" : 0.13353139, "hits" : [ { "_index" : "website", "_type" : "_doc", "_id" : "1", "_score" : 0.13353139, "_source" : { "post_date" : "2017-01-01", "title" : "my first article", "content" : "this is my first article in this website", "author_id" : 11400 } }, { "_index" : "website", "_type" : "_doc", "_id" : "2", "_score" : 0.13353139, "_source" : { "post_date" : "2017-01-02", "title" : "my second article", "content" : "this is my second article in this website", "author_id" : 11400 } }, { "_index" : "website", "_type" : "_doc", "_id" : "3", "_score" : 0.13353139, "_source" : { "post_date" : "2017-01-03", "title" : "my third article", "content" : "this is my third article in this website", "author_id" : 11400 } } ] }}3、组合多个搜索条件搜索需求:title必须包含first,content可以包含website也可以不包含,author_id必须不为111 ...

April 27, 2019 · 2 min · jiezi

1Elasticsearch-环境搭建

搭建环境说明官方支持的操作系统和JVM支持Oracle JDK和Open JDK。JDK8以上,推荐1.8.0_131或以上。这些文档都是基于Elasticsearch v6.7.1。Elasticsearch 单节点解压安装$ tar -zxvf ~/dev/doc/elasticsearch-6.7.1.tar.gz -C ~/dev/tools/$ ln -s ~/dev/tools/elasticsearch-6.7.1/ ~/dev/tools/elasticsearch$ vim .bash_profile export ES_HOME=/Users/baozi/dev/tools/elasticsearch export PATH=$PATH:$ES_HOME/bin 启动测试$ elasticsearch # 启动$ elasticsearch -d # 后台启动 warning: Falling back to java on path. This behavior is deprecated. Specify JAVA_HOMEGET / # 用Kibana发送{ "name" : "m9Y7FJV", "cluster_name" : "elasticsearch", "cluster_uuid" : "zkpO2cqsSIKCn-SVae0Ohg", "version" : { "number" : "6.7.1", "build_flavor" : "default", "build_type" : "zip", "build_hash" : "2f32220", "build_date" : "2019-04-02T15:59:27.961366Z", "build_snapshot" : false, "lucene_version" : "7.7.0", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search"}Elasticsearch 集群参数方式:自己单机常用这种方式。 ...

April 27, 2019 · 1 min · jiezi

2Elasticsearch-基本概念

Elasticsearch 简介Elasticsearch是一个高可扩展的,全文搜索分析引擎。可以近实时地存储、搜索以及分析海量数据。通常作为底层引擎为应用提供复杂搜索功能。应用: 搜索功能自动提示聚合分析日志等等...Near Realtime(NRT,近实时)从存入到可搜索,只有轻微的延迟(约1秒) Cluster(集群)集群是一个或多个节点(服务器)的集合。整个集群共同保存数据,并提供跨节点的联合索引与搜索功能。<br/>集群由唯一名称标识,Elasticsearch的默认集群名称为“elasticsearch”。 Node(节点)节点作为集群的一个服务器,提供存储数据,集群的索引与搜索功能。<br/>Elasticsearch节点名称默认为一个随机的UUID的前7位。 Index(索引)索引是相似特征的文档的集合。例如,你可以同时拥有用户数据索引、订单数据索引以及商品目录索引。<br/>索引由名称标识(全小写),通过名称引用索引对索引中的文档进行索引、搜索、更新、删除等操作。 Type(类型)已弃用,在将来更高版本中会删除。<br/>类型作为索引的逻辑分区,允许同一个索引中存储不同类型的文档,例如用户类型、博客类型等。 Document(文档)文档是Elasticsearch的基本信息单元,例如,一个文档表示产品,一个文档表示客户... Shards(分片)与Replicas(副本)单节点受硬盘空间的限制,以及查询速度会随着数据增加而变慢。<br/>Elasticsearch将索引拆分为多个分片,每个分片都是一个功能齐全且独立的索引,存储到不同的节点上。有两个好处: 可以水平切割/收缩容量。(针对单节点受硬盘空间的限制允许在多个节点上并行操作,从而提高性能和吞吐量。(针对查询速度会随着数据增加而变慢由于在网络/云环境中,随时可能发生故障:例如分片/节点不知怎么下线了或者因任何原因消失。<br/>Elasticsearch允许将分片的一个或多个副本,制作成副本分片或简称为副本。使用副本有两个理由: 在分片/节点故障时提供高可用。注意,副本分片不会与原分片存放到同一个节点上。可以扩展搜索量/吞吐量,因为可以在所有副本是并行搜索。总而言之,一个索引可以被拆为多个分片,一个分片可以有0到多个副本分片。<br/>Elasticsearch默认5个分片,1份副本,一共10个分片(5个主分片,5个副本分片)。

April 27, 2019 · 1 min · jiezi

3Elasticsearch-快速上手

Elasticsearch REST APIElasticsearch提供了全面强大的REST API: 检查集群、节点、索引的健康、状态以及统计信息。管理你的集群、节点和索引数据、元数据。对索引进行CRUD。执行分页、排序、过滤、脚本编写、聚合以及其他高级搜索。Elasticsearch请求格式:<HTTP Verb> /<Index>/<Type>/<ID> 文档说明学习的是Elasticsearch v6.7.1。示例都是用的Kibana,如果不明白Kibana可以看我的或者官网以及其他人的Kibana教程,只需要看DevTools如何使用暂时就够了,也可以用Postman、RestletClient、curl等,能发送REST请求的都可以。快速入门示例集群信息集群健康值: green:一切正常。(集群功能齐全)yellow:所有数据可用,一些副本尚未分配。(集群功能齐全)red:一些数据由于某种原因不可用。(集群部分功能可用)注意:状态为red时仍然提供搜索服务(在可用的分片中搜索),但你需要尽快的去修复它。 GET /_cat/health?vepoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent1555378987 01:43:07 elasticsearch green 1 1 4 4 0 0 0 0 - 100.0%节点信息列出所有节点。 GET /_cat/nodes?vip heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name127.0.0.1 49 73 11 mdi * m9Y7FJV索引信息列出所有索引。 GET /_cat/indices?vhealth status index uuid pri rep docs.count docs.deleted store.size pri.store.sizegreen open .monitoring-es-6-2019.04.16 IND0TKuCQsyHTH2FDE1zdg 1 0 5478 36 2.7mb 2.7mbgreen open .kibana_1 XTsD7vQ7QbukbJpFlkfLQQ 1 0 4 0 14.4kb 14.4kbgreen open .kibana_task_manager 0G59n4AWQzSxJ6YSHBDPnA 1 0 2 0 12.5kb 12.5kbgreen open .monitoring-kibana-6-2019.04.16 -SZdui1tTw-srkqmUxzQHw 1 0 684 0 309.2kb 309.2kb创建索引创建一个索引:名叫customer,2个分片,0副本。 ...

April 27, 2019 · 4 min · jiezi

聊聊elasticsearch的SeedHostsProvider

序本文主要研究一下elasticsearch的SeedHostsProvider SeedHostsProviderelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/SeedHostsProvider.java /** * A pluggable provider of the list of seed hosts to use for discovery. */public interface SeedHostsProvider { /** * Returns a list of seed hosts to use for discovery. Called repeatedly while discovery is active (i.e. while there is no master) * so that this list may be dynamic. */ List<TransportAddress> getSeedAddresses(HostsResolver hostsResolver); /** * Helper object that allows to resolve a list of hosts to a list of transport addresses. * Each host is resolved into a transport address (or a collection of addresses if the * number of ports is greater than one) */ interface HostsResolver { List<TransportAddress> resolveHosts(List<String> hosts, int limitPortCounts); }}SeedHostsProvider接口定义了getSeedAddresses方法,该方法参数类型为HostsResolver;HostsResolver接口定义了resolveHosts方法;它有几个实现类,分别是SettingsBasedSeedHostsProvider、FileBasedSeedHostsProvider、GceSeedHostsProvider、AwsEc2SeedHostsProvider、AzureSeedHostsProviderSettingsBasedSeedHostsProviderelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/SettingsBasedSeedHostsProvider.java ...

April 27, 2019 · 4 min · jiezi

elasticsearch学习笔记二十三Elasticsearch-分页搜索以及深分页性能问题

在实际应用中,分页是必不可少的,例如,前端页面展示数据给用户往往都是分页进行展示的。 1、ES分页搜索Elasticsearch分页搜索采用的是from+size。from表示查询结果的起始下标,size表示从起始下标开始返回文档的个数。示例: GET /test/_search?from=0&size=1{ "took" : 4, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ { "_index" : "test", "_type" : "_doc", "_id" : "1", "_score" : 1.0, "_source" : { "field1" : "value1", "field2" : "value2" } } ] }}2、深分页性能问题什么将深分页(deep paging)?简单来说,就是搜索的特别深,比如总共有60000条数据,三个primary shard,每个shard上分了20000条数据,每页是10条数据,这个时候,你要搜索到第1000页,实际上要拿到d的是10001~10010。 ...

April 27, 2019 · 1 min · jiezi

elasticsearch学习笔记二十四Elasticsearch-query-string语法以及all元数据原理

1、query string 语法GET /test_index/_search?q=test_field1:updateGET /test_index/_search?q=+test_field1:update{ "took" : 7, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : 0.2876821, "hits" : [ { "_index" : "test_index", "_type" : "_doc", "_id" : "3", "_score" : 0.2876821, "_source" : { "test_field1" : "update test1", "test_field2" : "update test2" } } ] }}GET /test_index/_search?q=-test_field1:update{ "took" : 12, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 7, "relation" : "eq" }, "max_score" : 0.0, "hits" : [ { "_index" : "test_index", "_type" : "_doc", "_id" : "10", "_score" : 0.0, "_source" : { "test_field" : "test10 routing _id" } }, { "_index" : "test_index", "_type" : "_doc", "_id" : "7", "_score" : 0.0, "_routing" : "2", "_source" : { "test_field1" : "test1" } }, { "_index" : "test_index", "_type" : "_doc", "_id" : "2", "_score" : 0.0, "_source" : { "test_field" : "test client 1", "name" : "test1" } }, { "_index" : "test_index", "_type" : "_doc", "_id" : "1", "_score" : 0.0, "_source" : { "test_field" : "test test", "name" : "test1" } }, { "_index" : "test_index", "_type" : "_doc", "_id" : "7", "_score" : 0.0, "_routing" : "1", "_source" : { "test_field1" : "test1" } }, { "_index" : "test_index", "_type" : "_doc", "_id" : "11", "_score" : 0.0, "_routing" : "12", "_source" : { "test_field" : "test routing not _id" }—— }, { "_index" : "test_index", "_type" : "_doc", "_id" : "20", "_score" : 0.0, "_source" : { "test_field" : "test consistency" } } ] }}对于query string只要掌握q=field:search content的语法,以及+和-的含义+:代表包含这个筛选条件结果-:代表不包含这个筛选条件的结果 ...

April 27, 2019 · 2 min · jiezi

elasticsearch学习笔记二十二Elasticsearch-multiindex搜索模式以及搜索原理

先说明一下,低版本的ES一个index是支持多type的,所以就有multi-type这一种搜索模式,这里不做详细讲解,因为和multi-index搜索模式是基本一样的。而且高版本的ES会弃用type。 1、multi-index搜索模式/_search:所有索引下的所有数据都搜索出来 GET /_search/{index}/_search:指定一个index,搜索这个索引下的所有数据 GET /test/_search/index1,index2/_search:同时搜索两个索引下的数据 GET /test_index,test/_search/1,2/_search: 通过通配符匹配多个索引,查询多个索引下的数据 GET /test*/_search/_all/_search: 代表所有的index GET /_all/_search2、搜索原理浅析当客户端发送查询请求到ES时,会把请求打到所有的primary shard上去执行,因为每个shard都包含部分数据,所有每个shard都可能会包含搜索请求的结果,但是如果primary shard有replica shard,那么请求也可以打到replica shard上去。如下图所示:

April 27, 2019 · 1 min · jiezi

聊聊elasticsearch的SeedHostsResolver

序本文主要研究一下elasticsearch的SeedHostsResolver ConfiguredHostsResolverelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/PeerFinder.java public interface ConfiguredHostsResolver { /** * Attempt to resolve the configured unicast hosts list to a list of transport addresses. * * @param consumer Consumer for the resolved list. May not be called if an error occurs or if another resolution attempt is in * progress. */ void resolveConfiguredHosts(Consumer<List<TransportAddress>> consumer); }ConfiguredHostsResolver接口定义了resolveConfiguredHosts方法用于解析配置的transport address列表SeedHostsResolverelasticsearch-7.0.0/server/src/main/java/org/elasticsearch/discovery/SeedHostsResolver.java public class SeedHostsResolver extends AbstractLifecycleComponent implements ConfiguredHostsResolver { public static final Setting<Integer> LEGACY_DISCOVERY_ZEN_PING_UNICAST_CONCURRENT_CONNECTS_SETTING = Setting.intSetting("discovery.zen.ping.unicast.concurrent_connects", 10, 0, Setting.Property.NodeScope, Setting.Property.Deprecated); public static final Setting<TimeValue> LEGACY_DISCOVERY_ZEN_PING_UNICAST_HOSTS_RESOLVE_TIMEOUT = Setting.positiveTimeSetting("discovery.zen.ping.unicast.hosts.resolve_timeout", TimeValue.timeValueSeconds(5), Setting.Property.NodeScope, Setting.Property.Deprecated); public static final Setting<Integer> DISCOVERY_SEED_RESOLVER_MAX_CONCURRENT_RESOLVERS_SETTING = Setting.intSetting("discovery.seed_resolver.max_concurrent_resolvers", 10, 0, Setting.Property.NodeScope); public static final Setting<TimeValue> DISCOVERY_SEED_RESOLVER_TIMEOUT_SETTING = Setting.positiveTimeSetting("discovery.seed_resolver.timeout", TimeValue.timeValueSeconds(5), Setting.Property.NodeScope); private static final Logger logger = LogManager.getLogger(SeedHostsResolver.class); private final Settings settings; private final AtomicBoolean resolveInProgress = new AtomicBoolean(); private final TransportService transportService; private final SeedHostsProvider hostsProvider; private final SetOnce<ExecutorService> executorService = new SetOnce<>(); private final TimeValue resolveTimeout; private final String nodeName; private final int concurrentConnects; public SeedHostsResolver(String nodeName, Settings settings, TransportService transportService, SeedHostsProvider seedProvider) { this.settings = settings; this.nodeName = nodeName; this.transportService = transportService; this.hostsProvider = seedProvider; resolveTimeout = getResolveTimeout(settings); concurrentConnects = getMaxConcurrentResolvers(settings); } public static int getMaxConcurrentResolvers(Settings settings) { if (LEGACY_DISCOVERY_ZEN_PING_UNICAST_CONCURRENT_CONNECTS_SETTING.exists(settings)) { if (DISCOVERY_SEED_RESOLVER_MAX_CONCURRENT_RESOLVERS_SETTING.exists(settings)) { throw new IllegalArgumentException("it is forbidden to set both [" + DISCOVERY_SEED_RESOLVER_MAX_CONCURRENT_RESOLVERS_SETTING.getKey() + "] and [" + LEGACY_DISCOVERY_ZEN_PING_UNICAST_CONCURRENT_CONNECTS_SETTING.getKey() + "]"); } return LEGACY_DISCOVERY_ZEN_PING_UNICAST_CONCURRENT_CONNECTS_SETTING.get(settings); } return DISCOVERY_SEED_RESOLVER_MAX_CONCURRENT_RESOLVERS_SETTING.get(settings); } public static TimeValue getResolveTimeout(Settings settings) { if (LEGACY_DISCOVERY_ZEN_PING_UNICAST_HOSTS_RESOLVE_TIMEOUT.exists(settings)) { if (DISCOVERY_SEED_RESOLVER_TIMEOUT_SETTING.exists(settings)) { throw new IllegalArgumentException("it is forbidden to set both [" + DISCOVERY_SEED_RESOLVER_TIMEOUT_SETTING.getKey() + "] and [" + LEGACY_DISCOVERY_ZEN_PING_UNICAST_HOSTS_RESOLVE_TIMEOUT.getKey() + "]"); } return LEGACY_DISCOVERY_ZEN_PING_UNICAST_HOSTS_RESOLVE_TIMEOUT.get(settings); } return DISCOVERY_SEED_RESOLVER_TIMEOUT_SETTING.get(settings); } /** * Resolves a list of hosts to a list of transport addresses. Each host is resolved into a transport address (or a collection of * addresses if the number of ports is greater than one). Host lookups are done in parallel using specified executor service up * to the specified resolve timeout. * * @param executorService the executor service used to parallelize hostname lookups * @param logger logger used for logging messages regarding hostname lookups * @param hosts the hosts to resolve * @param limitPortCounts the number of ports to resolve (should be 1 for non-local transport) * @param transportService the transport service * @param resolveTimeout the timeout before returning from hostname lookups * @return a list of resolved transport addresses */ public static List<TransportAddress> resolveHostsLists( final ExecutorService executorService, final Logger logger, final List<String> hosts, final int limitPortCounts, final TransportService transportService, final TimeValue resolveTimeout) { Objects.requireNonNull(executorService); Objects.requireNonNull(logger); Objects.requireNonNull(hosts); Objects.requireNonNull(transportService); Objects.requireNonNull(resolveTimeout); if (resolveTimeout.nanos() < 0) { throw new IllegalArgumentException("resolve timeout must be non-negative but was [" + resolveTimeout + "]"); } // create tasks to submit to the executor service; we will wait up to resolveTimeout for these tasks to complete final List<Callable<TransportAddress[]>> callables = hosts .stream() .map(hn -> (Callable<TransportAddress[]>) () -> transportService.addressesFromString(hn, limitPortCounts)) .collect(Collectors.toList()); final List<Future<TransportAddress[]>> futures; try { futures = executorService.invokeAll(callables, resolveTimeout.nanos(), TimeUnit.NANOSECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return Collections.emptyList(); } final List<TransportAddress> transportAddresses = new ArrayList<>(); final Set<TransportAddress> localAddresses = new HashSet<>(); localAddresses.add(transportService.boundAddress().publishAddress()); localAddresses.addAll(Arrays.asList(transportService.boundAddress().boundAddresses())); // ExecutorService#invokeAll guarantees that the futures are returned in the iteration order of the tasks so we can associate the // hostname with the corresponding task by iterating together final Iterator<String> it = hosts.iterator(); for (final Future<TransportAddress[]> future : futures) { final String hostname = it.next(); if (!future.isCancelled()) { assert future.isDone(); try { final TransportAddress[] addresses = future.get(); logger.trace("resolved host [{}] to {}", hostname, addresses); for (int addressId = 0; addressId < addresses.length; addressId++) { final TransportAddress address = addresses[addressId]; // no point in pinging ourselves if (localAddresses.contains(address) == false) { transportAddresses.add(address); } } } catch (final ExecutionException e) { assert e.getCause() != null; final String message = "failed to resolve host [" + hostname + "]"; logger.warn(message, e.getCause()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // ignore } } else { logger.warn("timed out after [{}] resolving host [{}]", resolveTimeout, hostname); } } return Collections.unmodifiableList(transportAddresses); } @Override protected void doStart() { logger.debug("using max_concurrent_resolvers [{}], resolver timeout [{}]", concurrentConnects, resolveTimeout); final ThreadFactory threadFactory = EsExecutors.daemonThreadFactory(settings, "[unicast_configured_hosts_resolver]"); executorService.set(EsExecutors.newScaling(nodeName + "/" + "unicast_configured_hosts_resolver", 0, concurrentConnects, 60, TimeUnit.SECONDS, threadFactory, transportService.getThreadPool().getThreadContext())); } @Override protected void doStop() { ThreadPool.terminate(executorService.get(), 10, TimeUnit.SECONDS); } @Override protected void doClose() { } @Override public void resolveConfiguredHosts(Consumer<List<TransportAddress>> consumer) { if (lifecycle.started() == false) { logger.debug("resolveConfiguredHosts: lifecycle is {}, not proceeding", lifecycle); return; } if (resolveInProgress.compareAndSet(false, true)) { transportService.getThreadPool().generic().execute(new AbstractRunnable() { @Override public void onFailure(Exception e) { logger.debug("failure when resolving unicast hosts list", e); } @Override protected void doRun() { if (lifecycle.started() == false) { logger.debug("resolveConfiguredHosts.doRun: lifecycle is {}, not proceeding", lifecycle); return; } List<TransportAddress> providedAddresses = hostsProvider.getSeedAddresses((hosts, limitPortCounts) -> resolveHostsLists(executorService.get(), logger, hosts, limitPortCounts, transportService, resolveTimeout)); consumer.accept(providedAddresses); } @Override public void onAfter() { resolveInProgress.set(false); } @Override public String toString() { return "SeedHostsResolver resolving unicast hosts list"; } }); } }}SeedHostsResolver继承了AbstractLifecycleComponent,同时实现了ConfiguredHostsResolver接口;它提供了getMaxConcurrentResolvers、getResolveTimeout、resolveHostsLists(使用线程池并发执行transportService.addressesFromString)这几个静态方法doStart方法使用EsExecutors.newScaling创建了EsThreadPoolExecutor;doStop方法则使用ThreadPool.terminate来终止线程池resolveConfiguredHosts方法首先将resolveInProgress从false设置为true,之后通过transportService.getThreadPool()执行hostsProvider.getSeedAddresses,执行完成则设置resolveInProgress为false小结ConfiguredHostsResolver接口定义了resolveConfiguredHosts方法用于解析配置的transport address列表SeedHostsResolver继承了AbstractLifecycleComponent,同时实现了ConfiguredHostsResolver接口;它提供了getMaxConcurrentResolvers、getResolveTimeout、resolveHostsLists(使用线程池并发执行transportService.addressesFromString)这几个静态方法doStart方法使用EsExecutors.newScaling创建了EsThreadPoolExecutor;doStop方法则使用ThreadPool.terminate来终止线程池;resolveConfiguredHosts方法首先将resolveInProgress从false设置为true,之后通过transportService.getThreadPool()执行hostsProvider.getSeedAddresses,执行完成则设置resolveInProgress为falsedocSeedHostsResolver

April 26, 2019 · 4 min · jiezi

聊聊elasticsearch的MembershipAction

序本文主要研究一下elasticsearch的MembershipAction MembershipActionelasticsearch-6.7.1/server/src/main/java/org/elasticsearch/discovery/zen/MembershipAction.java public class MembershipAction { private static final Logger logger = LogManager.getLogger(MembershipAction.class); public static final String DISCOVERY_JOIN_ACTION_NAME = "internal:discovery/zen/join"; public static final String DISCOVERY_JOIN_VALIDATE_ACTION_NAME = "internal:discovery/zen/join/validate"; public static final String DISCOVERY_LEAVE_ACTION_NAME = "internal:discovery/zen/leave"; //...... private final TransportService transportService; private final MembershipListener listener; public MembershipAction(TransportService transportService, MembershipListener listener, Collection<BiConsumer<DiscoveryNode,ClusterState>> joinValidators) { this.transportService = transportService; this.listener = listener; transportService.registerRequestHandler(DISCOVERY_JOIN_ACTION_NAME, JoinRequest::new, ThreadPool.Names.GENERIC, new JoinRequestRequestHandler()); transportService.registerRequestHandler(DISCOVERY_JOIN_VALIDATE_ACTION_NAME, () -> new ValidateJoinRequest(), ThreadPool.Names.GENERIC, new ValidateJoinRequestRequestHandler(transportService::getLocalNode, joinValidators)); transportService.registerRequestHandler(DISCOVERY_LEAVE_ACTION_NAME, LeaveRequest::new, ThreadPool.Names.GENERIC, new LeaveRequestRequestHandler()); } public void sendLeaveRequest(DiscoveryNode masterNode, DiscoveryNode node) { transportService.sendRequest(node, DISCOVERY_LEAVE_ACTION_NAME, new LeaveRequest(masterNode), EmptyTransportResponseHandler.INSTANCE_SAME); } public void sendLeaveRequestBlocking(DiscoveryNode masterNode, DiscoveryNode node, TimeValue timeout) { transportService.submitRequest(masterNode, DISCOVERY_LEAVE_ACTION_NAME, new LeaveRequest(node), EmptyTransportResponseHandler.INSTANCE_SAME).txGet(timeout.millis(), TimeUnit.MILLISECONDS); } public void sendJoinRequestBlocking(DiscoveryNode masterNode, DiscoveryNode node, TimeValue timeout) { transportService.submitRequest(masterNode, DISCOVERY_JOIN_ACTION_NAME, new JoinRequest(node), EmptyTransportResponseHandler.INSTANCE_SAME).txGet(timeout.millis(), TimeUnit.MILLISECONDS); } /** * Validates the join request, throwing a failure if it failed. */ public void sendValidateJoinRequestBlocking(DiscoveryNode node, ClusterState state, TimeValue timeout) { transportService.submitRequest(node, DISCOVERY_JOIN_VALIDATE_ACTION_NAME, new ValidateJoinRequest(state), EmptyTransportResponseHandler.INSTANCE_SAME).txGet(timeout.millis(), TimeUnit.MILLISECONDS); } //......}MembershipAction定义三类请求,分别是LeaveRequest、JoinRequest、ValidateJoinRequest;同时还定义了这些请求的TransportRequestHandler,分别是LeaveRequestRequestHandler、JoinRequestRequestHandler、ValidateJoinRequestRequestHandlerTransportRequestelasticsearch-6.7.1/server/src/main/java/org/elasticsearch/transport/TransportRequest.java ...

April 25, 2019 · 10 min · jiezi

搜索引擎ElasticSearch的启动过程

上一篇文章说了ES的源码编译以及如何在本地编译。这一篇文章主要说明ES的启动过程。 环境准备参考ElasticSearch源码编译和Debug。 说明:本文章使用的ES版本是:6.7.0 启动函数:org.elasticsearch.bootstrap.ElasticSearch 设置如下断点: 启动在上一篇文章中介绍的Debug模式中的一种,这里我用的远程Debug模式。 ElasticSearch的启动过程跟着Debug流程走一遍,可以看出ES启动流程大概分为以下几个阶段: org.elasticsearch.bootstrap.Elasticsearch#main(java.lang.String[]) 解析命令参数,加载配置,权限验证org.elasticsearch.bootstrap.Bootstrap 初始化,资源检查org.elasticsearch.node.Node 启动单机节点,创建keepAlive线程 为创建Node对象做准备,并最终创建Node对象 创建Node对象 如何加载模块和插件创建模块和插件的线程池启动Node实例一、org.elasticsearch.bootstrap.Elasticsearch#main(java.lang.String[])解析命令参数,加载配置,权限验证程序入口代码如下: 如果通过启动命令传入了DNS Cache时间,则重写DNS Cache时间创建 SecurityManager 安全管理器 SecurityManager:安全管理器在Java语言中的作用就是检查操作是否有权限执行,通过则顺序进行,否则抛出一个异常LogConfigurator.registerErrorListener(); 注册错误日志监听器new Elasticsearch(); 创建 Elasticsearch 对象Elasticsearch类继承了EnvironmentAwareCommand、Command,其完整的继承关系如下 所以Elasticsearch也可以解析命令行参数。 elasticsearch.main(args, terminal); 这里的main方法是其父类中的main方法,这里因为继承关系,方法执行的顺序如下: org.elasticsearch.cli.Command#main 注册shutdownHook,当程序异常关闭时打印异常信息org.elasticsearch.cli.Command#mainWithoutErrorHandling 解析命令行参数org.elasticsearch.cli.EnvironmentAwareCommand#execute 加载配置路径:home、data、logsorg.elasticsearch.cli.EnvironmentAwareCommand#createEnv 加载elasticsearch.yaml配置文件,创建command运行的环境org.elasticsearch.bootstrap.Elasticsearch#execute 配置验证,进入Bootstrap.init阶段二、org.elasticsearch.bootstrap.Bootstrap 初始化,资源检查Bootstrap阶段做的事情比较多,主要方法如下: /** * This method is invoked by {@link Elasticsearch#main(String[])} to startup elasticsearch. */ static void init( final boolean foreground, final Path pidFile, final boolean quiet, final Environment initialEnv) throws BootstrapException, NodeValidationException, UserException { // force the class initializer for BootstrapInfo to run before // the security manager is installed BootstrapInfo.init(); INSTANCE = new Bootstrap(); final SecureSettings keystore = loadSecureSettings(initialEnv); final Environment environment = createEnvironment(foreground, pidFile, keystore, initialEnv.settings(), initialEnv.configFile()); if (Node.NODE_NAME_SETTING.exists(environment.settings())) { LogConfigurator.setNodeName(Node.NODE_NAME_SETTING.get(environment.settings())); } try { LogConfigurator.configure(environment); } catch (IOException e) { throw new BootstrapException(e); } if (environment.pidFile() != null) { try { PidFile.create(environment.pidFile(), true); } catch (IOException e) { throw new BootstrapException(e); } } final boolean closeStandardStreams = (foreground == false) || quiet; try { if (closeStandardStreams) { final Logger rootLogger = LogManager.getRootLogger(); final Appender maybeConsoleAppender = Loggers.findAppender(rootLogger, ConsoleAppender.class); if (maybeConsoleAppender != null) { Loggers.removeAppender(rootLogger, maybeConsoleAppender); } closeSystOut(); } // fail if somebody replaced the lucene jars checkLucene(); // install the default uncaught exception handler; must be done before security is // initialized as we do not want to grant the runtime permission // setDefaultUncaughtExceptionHandler Thread.setDefaultUncaughtExceptionHandler(new ElasticsearchUncaughtExceptionHandler()); INSTANCE.setup(true, environment); try { // any secure settings must be read during node construction IOUtils.close(keystore); } catch (IOException e) { throw new BootstrapException(e); } INSTANCE.start(); if (closeStandardStreams) { closeSysError(); } } catch (NodeValidationException | RuntimeException e) { // disable console logging, so user does not see the exception twice (jvm will show it already) final Logger rootLogger = LogManager.getRootLogger(); final Appender maybeConsoleAppender = Loggers.findAppender(rootLogger, ConsoleAppender.class); if (foreground && maybeConsoleAppender != null) { Loggers.removeAppender(rootLogger, maybeConsoleAppender); } Logger logger = LogManager.getLogger(Bootstrap.class); // HACK, it sucks to do this, but we will run users out of disk space otherwise if (e instanceof CreationException) { // guice: log the shortened exc to the log file ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ps = null; try { ps = new PrintStream(os, false, "UTF-8"); } catch (UnsupportedEncodingException uee) { assert false; e.addSuppressed(uee); } new StartupException(e).printStackTrace(ps); ps.flush(); try { logger.error("Guice Exception: {}", os.toString("UTF-8")); } catch (UnsupportedEncodingException uee) { assert false; e.addSuppressed(uee); } } else if (e instanceof NodeValidationException) { logger.error("node validation exception\n{}", e.getMessage()); } else { // full exception logger.error("Exception", e); } // re-enable it if appropriate, so they can see any logging during the shutdown process if (foreground && maybeConsoleAppender != null) { Loggers.addAppender(rootLogger, maybeConsoleAppender); } throw e; } }详细流程如下: ...

April 24, 2019 · 8 min · jiezi

EFK接入kafka消息队列

1 前言在笔者最开始维护的日志服务中,日质量较小,没有接入kafka。随着业务规模扩增,日质量不断增长,接入到日志服务的产品线不断增多,遇到流量高峰,写入到es的性能就会降低,cpu打满,随时都有集群宕机的风险。因此,接入消息队列,进行削峰填谷就迫在眉睫。本文主要介绍在EFK的基础上如何接入kafka,并做到向前兼容。 2 主要内容如何搭建kafka集群原有EFK升级3 搭建kafka集群3.1 搭建zookeeper集群主要参考文章:【zookeeper安装指南】由于是要线上搭建集群,为避免单点故障,就需要部署至少3个节点(取决于多数选举机制)。 3.1.1 下载进入要下载的版本的目录,选择.tar.gz文件下载 3.1.2 安装使用tar解压要安装的目录即可,以3.4.5版本为例这里以解压到/home/work/common,实际安装根据自己的想安装的目录修改(注意如果修改,那后边的命令和配置文件中的路径都要相应修改) tar -zxf zookeeper-3.4.5.tar.gz -C /home/work/common3.1.3 配置在主目录下创建data和logs两个目录用于存储数据和日志: cd /home/work/zookeeper-3.4.5mkdir data mkdir logs在conf目录下新建zoo.cfg文件,写入如下配置: tickTime=2000 dataDir=/home/work/common/zookeeper1/data dataLogDir=/home/work/common/zookeeper1/logs clientPort=2181 initLimit=5 syncLimit=2 server.1=192.168.220.128:2888:3888 server.2=192.168.222.128:2888:3888 server.3=192.168.223.128:2888:3888在zookeeper1的data/myid配置如下: echo '1' > data/myidzookeeper2的data/myid配置如下: echo '2' > data/myidzookeeper2的data/myid配置如下: echo '3' > data/myid3.1.4 启停进入bin目录,启动、停止、重启分和查看当前节点状态(包括集群中是何角色)别执行: ./zkServer.sh start ./zkServer.sh stop ./zkServer.sh restart./zkServer.sh statuszookeeper集群搭建完成之后,根据实际情况开始部署kafka。以部署2个broker为例。 3.2 搭建kafka broker集群3.2.1 安装下载并解压包: curl -L -O http://mirrors.cnnic.cn/apache/kafka/0.9.0.0/kafka_2.10-0.9.0.0.tgz tar zxvf kafka_2.10-0.9.0.0.tgz 3.2.2 配置进入kafka安装工程根目录编辑config/server.properties #不同的broker对应的id不能重复broker.id=1delete.topic.enable=trueinter.broker.protocol.version=0.10.0.1log.message.format.version=0.10.0.1listeners=PLAINTEXT://:9092,SSL://:9093auto.create.topics.enable=falsessl.key.password=testssl.keystore.location=/home/work/certificate/server-keystore.jksssl.keystore.password=testssl.truststore.location=/home/work/certificate/server-truststore.jksssl.truststore.password=testnum.network.threads=3num.io.threads=8socket.send.buffer.bytes=102400socket.receive.buffer.bytes=102400socket.request.max.bytes=104857600log.dirs=/home/work/data/kafka/lognum.partitions=1num.recovery.threads.per.data.dir=1offsets.topic.replication.factor=1transaction.state.log.replication.factor=1transaction.state.log.min.isr=1log.retention.hours=72log.segment.bytes=1073741824log.retention.check.interval.ms=300000zookeeper.connect=192.168.220.128:2181,192.168.222.128:2181,192.168.223.128:2181zookeeper.connection.timeout.ms=6000group.initial.rebalance.delay.ms=03.2.3 启动kafka进入kafka的主目录 nohup sh bin/kafka-server-start.sh config/server.properties > /dev/null 2>&1 &3.2.4 连通性测试首先创建一个topic:topic_1 ...

April 24, 2019 · 2 min · jiezi

elasticsearch学习笔记二十一Elasticsearch-search结果的含义以及timeout机制

我们如果发出一个搜索请求的话,会拿到一堆搜索结果 GET /test/_search{ "took" : 1, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : 1.0, "hits" : [ { "_index" : "test", "_type" : "_doc", "_id" : "1", "_score" : 1.0, "_source" : { "field1" : "value1", "field2" : "value2" } }, { "_index" : "test", "_type" : "_doc", "_id" : "3", "_score" : 1.0, "_source" : { "field1" : "value3" } } ] }}下面主要分析一下返回结果的各种数据的含义和timeout机制 ...

April 23, 2019 · 1 min · jiezi

elasticsearch学习笔记(二十)——Elasticsearch bulk api的奇特json格式与底层性能优化关系

bulk api奇特的json格式{"action": {"meta"}}n{"data"}n{"action": {"meta"}}n{"data"}n... 为什么bulk要采用这种奇特的json格式?由于bulk中的每个操作都可能要转发到不同的node的shard去执行,假设我们不用这种奇特的json格式,采用比较良好的json数组格式,允许任意的换行,整个可读性非常棒,读起来很爽。但是ES拿到这种标准格式的json串之后,要按照下述流程去进行执行处理。格式如下:[{ "action": {},"data": {}}](1)将json数组解析为JSONArray对象,这个时候,整个数据,就会在内存中出现一份一摸一样的拷贝,一份数据是json文本,一份数据是JSONArray对象(2)解析json数组里面的每个json,对每个请求中的document进行路由(3)为路由到同一个shard上的多个请求,创建一个请求数组(4)将这个请求数组序列化(5)将序列化后的请求数组发送到对应的节点上去不难看出这样就会耗费更多的内存,更多的jvm gc开销。 假设一个场景,对于bulk size的大小一般建议在几千条,大小在10MB左右,所以说,可怕的事情来了。假设说现在100个bulk请求发送到了一个节点上去,然后每个请求是10MB,100个请求就是1000MB=1G,然后每个请求的json都copy一份JSONArray对象,此时内存中的占用就会翻倍,就会占用2GB的内存,甚至还不止,因为弄成JSONArray对象之后,还可能会多弄一些其它的数据结构,2GB+的内存占用。占用更多的内存可能就会积压其它请求的内存使用量,比如说最重要的搜索请求,分析请求等等。此时就可能会导致其它请求的性能急速下降,另外的话,占用内存更多,就会导致java虚拟机的垃圾回收次数更多,更加频繁,每次要回收的垃圾对象更多,耗费的时间更多,导致ES的java虚拟机停止工作线程的时间更多。 而使用这个奇特格式的json{"action": {"meta"}}n{"data"}n{"action": {"meta"}}n{"data"}n...(1)不用将其转换为json对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割json(2)对每两个一组的json,读取meta,进行document路由(3)直接将对应的json发送到node上去和标准格式的json相比,最大的优势在于不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间,尽可能的保证性能。 实战: PUT _bulk{"index": {"_index": "test", "_id": "1"}}{"field1": "value1", "field2": "value2"}{"index": {"_index": "test", "_id": "2"}}{"field1": "value1 id2", "field2": "value2 id2"}{"delete": {"_index": "test", "_id": "2"}}{"create": {"_index": "test", "_id": "3"}}{"field1": "value3"}{"update": {"_index": "test", "_id": "1"}}{"doc": {"field2": "value2"}}{ "took" : 68, "errors" : true, "items" : [ { "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1", "_version" : 2, "result" : "updated", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 4, "_primary_term" : 1, "status" : 200 } }, { "index" : { "_index" : "test", "_type" : "_doc", "_id" : "2", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 5, "_primary_term" : 1, "status" : 201 } }, { "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2", "_version" : 2, "result" : "deleted", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 6, "_primary_term" : 1, "status" : 200 } }, { "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3", "status" : 409, "error" : { "type" : "version_conflict_engine_exception", "reason" : "[3]: version conflict, document already exists (current version [1])", "index_uuid" : "rOLJZzIVTDCWtDQcJuei6w", "shard" : "0", "index" : "test" } } }, { "update" : { "_index" : "test", "_type" : "_doc", "_id" : "1", "_version" : 2, "result" : "noop", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "status" : 200 } } ]}

April 22, 2019 · 2 min · jiezi

全文搜索引擎ElasticSearch源码编译和Debug环境搭建

环境准备说明:本文章使用的ES版本是:6.7.0 JDKElastisearch 6.7.0编译需要JDK版本10.0及以上,我直接安装了JDK12.JDK下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index.html Gradlebrew install gradle Elastisearch源码git clone https://github.com/elastic/elasticsearch.gitgit taggit checkout v6.7.0使用IDEA DEBUG 源码将工程Import到IDEA进入Elastisearch根目录,把源码编译为IDEA工程:./gradlew idea 选择Elasticsearch目录进入: 选择Gradle导入后,下一步: 选择如上的选项,点击Finish,导入源码到IDEA完成。 本地Debug代码使用IntelliJ在本地调试ES,有两种方式,一种是直接在IntelliJ上运行ES进行调试,但需要很多繁杂得配置。配置方法:进入IDEA,Run -> Edit Configurations 其中VM options如下: 其中,elasticsearch.policy如下: 最后,运行org.elasticsearch.bootstrap.Elasticsearch::main(java.lang.String[]) 方法就可以调试了。 远程调试另一种是远程调试,先用debug模式,在本地启动ES服务:./gradlew run --debug-jvm 可以看到,debug模式监听的端口是8000 然后在IDE代码中设置断点,点击debug按钮: 同时也可以在浏览器中通过访问:http://127.0.0.1:9200 查看ES状态 http://127.0.0.1:9200/_cat/health?v 下一篇文章将说一下ES的启动过程。 文章首发:搜索引擎ElasticSearch源码编译和Debug环境搭建

April 21, 2019 · 1 min · jiezi

elasticsearch学习笔记(十九)——Elasticsearch document查询内部原理

下面直接点,先描述一下一个查询请求打过来Elasticsearch内部做了什么。 (1)客户端发送请求到任意一个node,这个node就成为了协调节点coordinating node(2)协调节点coordinating node会对document进行路由,将请求转发到包含该document的对应的node上面去,此时会使用round-robin随机轮询算法,在primary shard以及所有的replica shard中随机选择一个,让打过来的读请求实现负载均衡(3)接收请求的node会返回document给协调节点coordinating node(4)协调节点将document数据返回给客户端 对于读取请求,协调节点将在每个请求上选择不同的分片副本以平衡负载; 它循环遍历所有碎片副本。 在索引文档时,文档可能已经存在于主分片上但尚未复制到副本分片。在这种情况下,副本可能会报告文档不存在,而主副本可能会成功返回文档。索引请求将成功返回给用户后,该文档将在主分片和所有副本分片上可用。 最后简单描述一下随机轮询算法:举个例子,比如一个协调节点coordinating接受到一个document的4次请求,就会使用随机轮询算法,循环遍历所有shard,将4次请求均匀的打在所有shard上面,比如有4个shard,就会每个shard各一个请求。

April 21, 2019 · 1 min · jiezi

elasticsearch学习笔记(十八)——Elasticsearch document增删改内部原理,写一致性机制

1、Elasticsearch增删改内部原理下面直接点,先描述一下一个增删改请求打过来Elasticsearch内部做了什么。如图(1)对于客户端首先会选择一个节点node发送请求过去,这个节点node就是协调节点coordinating node(2)协调节点coordinating node会对docuemnt数据进行路由,将请求转发给对应的node(含有primary shard)(3)实际上node的primary shard会处理请求,然后将数据同步到对应的含有replica shard的node(4)协调节点coordinating node如果发现含有primary shard的node和所有的含有replica shard的node都搞定之后,就会返回响应结果给客户端 下面手工画图展示一下上面的过程:假设我们有2个节点,5个primary shard replica=1 1、客户端发送增删改请求给协调节点node22、协调节点node2将请求路由到含有primary shard的node13、node1处理请求,并同步数据到对应的含有replica shard的node24、协调节点node2如果发现含有primary shard的node1以及所有含有replica shard的node2都搞定了,就会返回响应结果给客户端 2、写一致性机制(已经被移除,替换为wait_for_active_shards) consistency默认情况下,主分片需要法定数量或大部分的分片副本(其中分片副本可以是主分片或副本分片)在尝试写入操作之前可用。这是为了防止将数据写入网络分区的 “wrong side”。法定人数定义如下: int((primary + number_of_replicas)/ 2)+ 1允许的值consistency是one(仅主要分片),all (主要和所有副本),或默认quorum或大多数分片副本。 请注意,它number_of_replicas是索引设置中指定的副本数,而不是当前活动的副本数。如果您已指定索引应具有三个副本,则仲裁将如下所示: int( (primary + 3 replicas) / 2 ) + 1 = 4但是,如果仅启动两个节点,则将没有足够的活动分片副本来满足仲裁,并且您将无法索引或删除任何文档。 timeout如果可用的分片副本不足,会发生什么?Elasticsearch等待,希望会出现更多的分片。默认情况下,它将等待最多1分钟。如果需要,可以使用timeout参数让它更快地中止:100是100毫秒,30s是30秒。注意默认情况下,新索引具有一个副本,这意味着应该需要两个活动分片副本以满足需要的quorum。但是,这些默认设置会阻止我们对单节点群集执行任何有用的操作。为避免此问题,仅当number_of_replicas大于1时才强制要求仲裁。 移除consistency这个参数之后,用wait_for_active_shards这个参数替代了。原因就是,consistency检查是在Put之前做的。然而,虽然检查的时候,shard满足quorum,但是真正从primary shard写到replica之前,仍会出现shard挂掉,但Update Api会返回succeed。因此,这个检查并不能保证replica成功写入,甚至这个primary shard是否能成功写入也未必能保证。 为了提高对系统写入的弹性,使用wait_for_active_shards,可以将索引操作配置为在继续操作之前等待一定数量的活动分片副本。如果必需数量的活动分片副本不可用,则写入操作必须等待并重试,直到必需的分片副本已启动或发生超时。默认情况下,写入操作仅等待主分片在继续(即wait_for_active_shards=1)之前处于活动状态。注意,此设置大大降低了写入操作不写入所需数量的分片副本的可能性,但它并未完全消除这种可能性,因为此检查在写入操作开始之前发生。一旦写入操作正在进行,复制在任何数量的分片副本上仍然可能失败,但仍然可以在主分片上成功。在_shards写操作的响应部分揭示了其复制成功/失败碎片的份数。 PUT /test_index/_doc/20?wait_for_active_shards=1{ "test_field":"test consistency"}{ "_index" : "test_index", "_type" : "_doc", "_id" : "20", "_version" : 1, "result" : "created", "_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }, "_seq_no" : 10, "_primary_term" : 2}"_shards" : { "total" : 2, "successful" : 1, "failed" : 0 }

April 21, 2019 · 1 min · jiezi

elasticsearch学习笔记(十七)——Elasticsearch document数据路由原理以及主分片的不可变

1、Elasticsearch document数据路由原理我们知道,一个index的数据会被分成多个分片shard,所以说一个document只能存在与一个shard中。当客户端创建document的时候,elasticsearch此时就需要决定这个document是放在这个index的哪个分片shard中,这个过程就称之为document routing,即数据路由。2、document数据路由算法算法;shard = hash(routing) % number_of_primary_shards举个例子,假设一个index有5个primary shard(p0,p1,p2,p3,p4)。每次对index的一个document进行增删改查的时候,都会带过来一个routing number,默认就是这个documentd的_id(可能是手动指定,也可以是自动生成),routing=_id。假设_id=1,那么就会将routing=1这个routing值传入一个hash函数中,产生一个routing值的hash值,假设hash(routing)=21,然后将hash函数产生的值对这个index的primary shard的数量求余数,21 % 5 = 1也就决定了这个document就放在p1上。注意:此这个计算过程就可以看出,决定一个document在哪个shard上,最重要的值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中生成的hash值一定是相同的。无论最后计算的hash值是多少,对number_of_primary_shards求余数,结果一定在0~number_of_primary_shards之间。3、routing实战默认的routing就是_id,也可以在发送请求的时候手动指定。下面做一个比较有趣的例子先测试一下默认的routing//先插入数据PUT /test_index/_doc/10{ “test_field”: “test10 routing _id”}//获取数据不带routing参数GET /test_index/_doc/10{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “10”, “_version” : 1, “_seq_no” : 0, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test10 routing _id” }}//获取数据带routing参数 参数值为_idGET /test_index/_doc/10?routing=10{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “10”, “_version” : 1, “_seq_no” : 0, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test10 routing _id” }}再来测试一下带上routing值,为了看到效果我们让_id的值与routing的值不一样//先插入数据PUT /test_index/_doc/11?routing=12{ “test_field”: “test routing not _id”}//获取数据不带routing参数GET /test_index/_doc/11{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “11”, “found” : false}//获取数据带routing参数 参数值为自定义的值GET /test_index/_doc/11?routing=12{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “11”, “_version” : 1, “_seq_no” : 9, “_primary_term” : 1, “_routing” : “12”, “found” : true, “_source” : { “test_field” : “test routing not _id” }}手动指定的routing value是很有用的,可以保证某一类的document一定被路由到一个shard中去,那么在后续进行应用级别的负载均衡以及提升批量读取的性能的时候,是很有帮助的。4、主分片数量不可变通过上面的分析,特别是路由算法,我们不难知道,在我们最开始创建索引的时候,确定了primary shard的数量,之后根据路由算法,每个document就被路由到了指定的shard上面,之后的各种操作路由规则都是一样的。试想一下,如果我们改变了primary shard的数量,那么路由算法取余的时候值可能就跟之前的不一样了,就会被路由到其它的shard上面去,就会导致数据混乱,本该已经存在的document,可能通过查询根本查不到,某种程度上说这也会造成数据的丢失。 ...

April 20, 2019 · 1 min · jiezi

elasticsearch学习笔记(十六)——Elasticsearch mget批量查询api实战

首先说明一下为什么需要批量查询操作?假设一下比如说我们没有批量查询的操作,那么当我们获取数据的时候,就是一条一条的查询。设想一下我们要获取100条数据,那么就要发送100次请求,这个开销时很大的。但是有了批量查询的话,查询100条数据,就只需要发送一次网络请求就可以了,网络请求的性能开销缩减100倍。下面是实战部分,演示一下一条一条查询与批量查询:(1)一条一条查询GET /test_index/_doc/1{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 8, “_seq_no” : 7, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test test”, “name” : “test1” }}GET /test_index/_doc/2{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “2”, “_version” : 4, “_seq_no” : 3, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test client 1”, “name” : “test1” }}(2)mget批量查询GET /_mget{ “docs”: [ { “_index”: “test_index”, “_id”: 1 }, { “_index”: “test_index”, “_id”: 2 } ]}{ “docs” : [ { “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 8, “_seq_no” : 7, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test test”, “name” : “test1” } }, { “_index” : “test_index”, “_type” : “_doc”, “_id” : “2”, “_version” : 4, “_seq_no” : 3, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test client 1”, “name” : “test1” } } ]} ...

April 20, 2019 · 1 min · jiezi

elasticsearch学习笔记(十五)——Elasticsearch partial update内置乐观锁并发控制

Elasticsearch partial update内置了乐观锁并发控制机制。同样是基于_version(新版本更新为if_seq_no和if_primary_term)进行乐观锁的并发控制。详细请看:https://segmentfault.com/a/11…这里多提一点就是使用partial update有一个参数叫retry_on_conflict,也就是可以基于retry策略:我们回顾一下之前说的乐观锁并发控制策略在高并发更新数据时,它基于最新的数据和if_seq_no,if_primary_term进行修改,可能这个过程会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下。而partial update就是再次基础上添加了一个参数retry_on_conflict,可以设置最多重复的次数。示例:POST /test_index/_update/3?retry_on_conflict=5{ “doc”: { “test_field1”: “update test1” }}GET /test_index/_doc/3{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “3”, “_version” : 3, “_seq_no” : 2, “_primary_term” : 1, “found” : true, “_source” : { “test_field1” : “update test1”, “test_field2” : “update test2” }}

April 20, 2019 · 1 min · jiezi

elasticsearch学习笔记(十四)——Elasticsearch partial update实现原理和实践

首先来讲一下为什么会有partial update。在之前对于创建文档和更新替换文档的格式都是:PUT /{index}/{type}/{id}{}一般对应到应用程序中,每次的执行流程基本都是这样的:(1)应用程序先发起一个get请求,获取到document,展示到前台界面,供用户查看和修改(2)用户在前台界面修改数据,发送到后台(3)后台代码会将用户修改的数据在内存中进行执行,然后封装好修改后的全量数据(4)然后在发送PUT请求到ES中,进行全量替换(5)ES会将老的document标记为deleted,然后重新创建一个新的document之前的流程有1个问题就是每次修改数据的时候,由于都是替换,所以每次带上的字段不仅包括要修改的字段,还需要带上其它的所有字段(即使那些字段根本就不需要修改)。针对这一点,于是ES就有了partial update。格式就是:POST /{index}/{type}/{id}/_update{ “doc”: { “需要修改的字段” }}这样看起来好像是方便了很多,每次修改时只需要传入少数几个发生修改的字段即可,不需要将全量的document数据发送过去。partial update实现原理以及优点partial update实现原理:从本质上来说,它的实现原理和传统的全量替换的方式是几乎一样的。过程如下:(1)在内部会先获取document(2)将传过来的field更新到document的json中去(3)将旧的document标记为deleted(4)将修改后的新的document创建出来既然说本质都一样,那它相比传统的方式优点在哪里呢?比较之后不难发现有以下的优点:(1)所有的查询、修改和写回操作都发生在ES中的一个shard内部,与传统全量替换将操作放在内存的方式相比,避免了所有网络数据传输的开销(减少了2次网络请求),大大提升了性能(2)减少了查询和修改中的时间间隔,可以有效减少并发冲突的情况实践:PUT /test_index/_doc/3{ “test_field1”: “test1”, “test_field2”: “test2”}GET /test_index/_doc/3{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “3”, “_version” : 1, “_seq_no” : 0, “_primary_term” : 1, “found” : true, “_source” : { “test_field1” : “test1”, “test_field2” : “test2” }}POST /test_index/_update/3{ “doc”: { “test_field2” : “update test2” }}GET /test_index/_doc/3{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “3”, “_version” : 2, “_seq_no” : 1, “_primary_term” : 1, “found” : true, “_source” : { “test_field1” : “test1”, “test_field2” : “update test2” }} ...

April 20, 2019 · 1 min · jiezi

elasticsearch学习笔记(十三)——Elasticsearch乐观锁并发控制实战

1、elasticsearch基于_version进行乐观锁的并发控制(1)先构造一条数据PUT /test_index/_doc/1{ “test_field”:“test test”}(2)模拟两个客户端都获取到同一条数据GET /test_index/_doc/1(3)其中一个客户端先更新了一下这个数据PUT /test_index/_doc/1?version=1{ “test_field”: “test client 1”}(4)另外一个客户端,尝试基于version=1的数据去修改,同样带上version版本号,进行乐观锁的并发控制PUT /test_index/_doc/1?version=1{ “test_field”: “test client 2”}此时就会报错version conflict(5)在乐观锁成功阻止并发问题之后,尝试正确的完成更新GET /test_index/_doc/1对于更新失败的客户端,查询出最新的版本号之后,基于最新的数据和版本号去再次进行修改,修改时,带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下注意特别说明,要完成上面的实验需要es版本低于6.7。version Deprecated in 6.7.0. Please use if_seq_no & if_primary_term instead. See Optimistic concurrency control for more details.这段说得很清楚,高版本是使用if_seg_no和if_primary_term这两个参数实现的。下面把上面的实现用高版本做一下。我用的版本是7.0的(1)先构造一条数据PUT /test_index/_doc/1{ “test_field”:“test test”}{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 3, “result” : “created”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 2, “_primary_term” : 1}(2)模拟两个客户端,都获取到了同一条数据GET /test_index/_doc/1{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 3, “_seq_no” : 2, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test test” }}(3)其中一个客户端,先更新了一下这个数据PUT /test_index/_doc/1?if_seq_no=2&if_primary_term=1{ “test_field”:“test client 1”}{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 4, “result” : “updated”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 3, “_primary_term” : 1}(4)另外一个客户端,尝试基于if_seq_no和if_primary_term的数据进行修改,进行乐观锁的并发控制PUT /test_index/_doc/1?if_seq_no=2&if_primary_term=1{ “test_field”:“test client 2”}{ “error”: { “root_cause”: [ { “type”: “version_conflict_engine_exception”, “reason”: “[1]: version conflict, required seqNo [2], primary term [1]. current document has seqNo [3] and primary term [1]”, “index_uuid”: “0jAS2GP1TPG5J8PlqGdYIQ”, “shard”: “4”, “index”: “test_index” } ], “type”: “version_conflict_engine_exception”, “reason”: “[1]: version conflict, required seqNo [2], primary term [1]. current document has seqNo [3] and primary term [1]”, “index_uuid”: “0jAS2GP1TPG5J8PlqGdYIQ”, “shard”: “4”, “index”: “test_index” }, “status”: 409}(5)在乐观锁成功阻止并发问题之后,尝试正确的完成更新GET /test_index/_doc/1{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 4, “_seq_no” : 3, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test client 1” }}基于最新的数据和if_seq_no,if_primary_term进行修改,可能这个过程会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下PUT /test_index/_doc/1?if_seq_no=3&if_primary_term=1{ “test_field”:“test client 2”}{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 5, “result” : “updated”, “_shards” : { “total” : 2, “successful” : 1, “failed” : 0 }, “_seq_no” : 4, “_primary_term” : 1}GET /test_index/_doc/1{ “_index” : “test_index”, “_type” : “_doc”, “_id” : “1”, “_version” : 5, “_seq_no” : 4, “_primary_term” : 1, “found” : true, “_source” : { “test_field” : “test client 2” }}对于if_seq_no和if_primary_term,官方文档已经有比较详细的叙述,https://www.elastic.co/guide/… 。这里我说下简单的理解方式,对于if_primary_term记录的就是具体的哪个主分片,而if_seq_no这个参数起的作用和旧版本中的_version是一样的,之所以加上if_primary_term这个参数主要是提高并发的性能以及更自然,因为每个document都只会在某一个主分片中,所以由所在主分片分配序列号比由之前通过一个参数_version,相当于由整个ES集群分配版本号要来的更好。To ensure an older version of a document doesn’t overwrite a newer version, every operation performed to a document is assigned a sequence number by the primary shard that coordinates that change. The sequence number is increased with each operation and thus newer operations are guaranteed to have a higher sequence number than older operations. Elasticsearch can then use the sequence number of operations to make sure a newer document version is never overridden by a change that has a smaller sequence number assigned to it.简单翻译就是为确保较旧版本的文档不会覆盖较新版本,对文档执行的每个操作都会由协调该更改的主分片分配序列号。每次操作都会增加序列号,因此保证较新的操作具有比旧操作更高的序列号。然后,Elasticsearch可以使用序列号操作来确保更新的文档版本永远不会被分配给它的序列号更小的更改覆盖。2、基于external version进行乐观锁的并发控制(6.7版本已移除)具体的实战就不做了,本质思想也很简单,就是版本号是存储在自己的数据库中的,可以由开发人员自己控制。但是在6.7版本之后,就移除这个功能,主要是因为:The update API does not support versioning other than internalExternal (version types external and external_gte) or forced (version type force) versioning is not supported by the update API as it would result in Elasticsearch version numbers being out of sync with the external system. 更新API不支持外部(版本类型external和external_gte)或强制(版本类型force)版本控制,因为它会导致Elasticsearch版本号与外部系统不同步 ...

April 20, 2019 · 3 min · jiezi

elasticsearch学习笔记(十二)——Elasticsearch并发冲突问题以及锁机制

1、Elasticsearch并发冲突问题对于一般的ES操作流程是:1、先get document数据,比如获取到商品数据,将数据显示到网页上,同时在内存中缓存该documentd的数据2、当网页发生了购买后,直接基于内存中的数据,进行计算和操作3、将计算后的结果写回ES中下面描述一下场景比如在电商场景下,假设说,我们有一个程序,工作流程如下:1、读取商品的信息2、用户下单购买3、更新商品信息(主要是将库存减1)我们假设程序是多线程的,所以说可能有多个线程并发的去执行上述的3个步骤流程将上述场景具体到某个商品的库存修改的时候,假设一个牙膏的库存是100件,现在同时有两个人都过来读取了牙膏的数据,然后下单购买了这管牙膏,此时两个线程并发执行,同时在进行商品库存的修改。如图所示,在正常的情况下,我们期望线程A将库存-1,设置为99件;然后线程B接着这个99件,将库存-1,变为98件,然后写入到ES中。但是总有一个线程是先到的,假设就是线程A,此时线程A就会先将牙膏的库存设置为99件,然后线程B再次将牙膏的库存设置为99件,结果很显然就不是我们想要的。上述的这个流程,其实就是ES中的并发冲突问题,会导致数据不准确。2、悲观锁与乐观锁两种并发控制方案这里先附上中华石衫老师画的手工图下面简单做一下描述和概括悲观锁:所谓悲观锁就是在任何情况下都上锁,上锁之后,就只有一个线程可以操作这一条数据,其它线程只能等待,当然在不同的场景下,上的锁会有所不同,可以是行级锁,表级锁,读锁,写锁。通俗的来讲,加了悲观锁的话,对数据操作的时候就相当于是单线程的了。乐观锁:其实所谓的乐观锁,根本就没有加锁,只是多了一标识字段,这个字段可以是一个整数类型的,也可以是时间类型的。主要的作用就是在每次修改数据的时候会做一层判断,判断数据是否已经被修改过了,如果已经被修改了,那么就会重新获取数据,在修改,这个过程不断进行知道数据修改成功。下面比较一下两种锁的优缺点1、对于悲观锁,它使用起来很方便,直接加锁就可以了,对于应用程序来说,它是透明的,不需要做额外的操作。但是每次都需要获取到锁之后才能对数据进行修改,也就是同一时间只能有一个线程能够进行操作,并发能力很低2、对于乐观锁,它根本就没有加锁,所以并发能力很高。但是每次更新数据的时候都需要对标识字段做一个判断,这个过程可能要重复很多遍。而且是需要应用程序做处理的。3、Elasticseach基于_version进行乐观锁的并发控制对于ES来说,它是采用乐观锁,对应的乐观锁的标识字段是_version,是一个整数类型,一开始创建document时,_version是等于1的,之后对document每修改一次,_version版本号就会自动加1。

April 19, 2019 · 1 min · jiezi

聊聊elasticsearch的TransportProxyClient

序本文主要研究一下elasticsearch的TransportProxyClientTransportProxyClientelasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportProxyClient.javafinal class TransportProxyClient { private final TransportClientNodesService nodesService; private final Map<Action, TransportActionNodeProxy> proxies; TransportProxyClient(Settings settings, TransportService transportService, TransportClientNodesService nodesService, List<GenericAction> actions) { this.nodesService = nodesService; Map<Action, TransportActionNodeProxy> proxies = new HashMap<>(); for (GenericAction action : actions) { if (action instanceof Action) { proxies.put((Action) action, new TransportActionNodeProxy(settings, action, transportService)); } } this.proxies = unmodifiableMap(proxies); } public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> void execute(final Action<Request, Response, RequestBuilder> action, final Request request, ActionListener<Response> listener) { final TransportActionNodeProxy<Request, Response> proxy = proxies.get(action); assert proxy != null : “no proxy found for action: " + action; nodesService.execute((n, l) -> proxy.execute(n, request, l), listener); }}TransportProxyClient的构造器接收Settings、TransportService、TransportClientNodesService、List<GenericAction>四个参数TransportProxyClient的构造器会根据actions来给每个action创建TransportActionNodeProxy,并放入到名为proxies的map中TransportProxyClient主要是提供了execute方法,该方法从proxies取出对应的TransportActionNodeProxy,然后通过TransportClientNodesService的execute方法来执行proxy.execute方法TransportActionNodeProxyelasticsearch-6.4.3-sources.jar!/org/elasticsearch/action/TransportActionNodeProxy.javapublic class TransportActionNodeProxy<Request extends ActionRequest, Response extends ActionResponse> extends AbstractComponent { private final TransportService transportService; private final GenericAction<Request, Response> action; private final TransportRequestOptions transportOptions; public TransportActionNodeProxy(Settings settings, GenericAction<Request, Response> action, TransportService transportService) { super(settings); this.action = action; this.transportService = transportService; this.transportOptions = action.transportOptions(settings); } public void execute(final DiscoveryNode node, final Request request, final ActionListener<Response> listener) { ActionRequestValidationException validationException = request.validate(); if (validationException != null) { listener.onFailure(validationException); return; } transportService.sendRequest(node, action.name(), request, transportOptions, new ActionListenerResponseHandler<>(listener, action::newResponse)); }}TransportActionNodeProxy的构造器要求输入Settings、GenericAction、TransportService三个参数;TransportActionNodeProxy提供了execute方法,它的方法参数要求输入DiscoveryNode、Request、ActionListener,该方法主要是对ActionListener包装为ActionListenerResponseHandler,然后调用transportService.sendRequestNodesTransportClientNodesService Nodeselasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportClientNodesService.javafinal class TransportClientNodesService extends AbstractComponent implements Closeable { private final TimeValue nodesSamplerInterval; private final long pingTimeout; private final ClusterName clusterName; private final TransportService transportService; private final ThreadPool threadPool; private final Version minCompatibilityVersion; // nodes that are added to be discovered private volatile List<DiscoveryNode> listedNodes = Collections.emptyList(); private final Object mutex = new Object(); private volatile List<DiscoveryNode> nodes = Collections.emptyList(); // Filtered nodes are nodes whose cluster name does not match the configured cluster name private volatile List<DiscoveryNode> filteredNodes = Collections.emptyList(); private final AtomicInteger tempNodeIdGenerator = new AtomicInteger(); private final NodeSampler nodesSampler; private volatile ScheduledFuture nodesSamplerFuture; private final AtomicInteger randomNodeGenerator = new AtomicInteger(Randomness.get().nextInt()); private final boolean ignoreClusterName; private volatile boolean closed; private final TransportClient.HostFailureListener hostFailureListener; //…… public TransportClientNodesService addTransportAddresses(TransportAddress… transportAddresses) { synchronized (mutex) { if (closed) { throw new IllegalStateException(“transport client is closed, can’t add an address”); } List<TransportAddress> filtered = new ArrayList<>(transportAddresses.length); for (TransportAddress transportAddress : transportAddresses) { boolean found = false; for (DiscoveryNode otherNode : listedNodes) { if (otherNode.getAddress().equals(transportAddress)) { found = true; logger.debug(“address [{}] already exists with [{}], ignoring…”, transportAddress, otherNode); break; } } if (!found) { filtered.add(transportAddress); } } if (filtered.isEmpty()) { return this; } List<DiscoveryNode> builder = new ArrayList<>(listedNodes); for (TransportAddress transportAddress : filtered) { DiscoveryNode node = new DiscoveryNode("#transport#-” + tempNodeIdGenerator.incrementAndGet(), transportAddress, Collections.emptyMap(), Collections.emptySet(), minCompatibilityVersion); logger.debug(“adding address [{}]”, node); builder.add(node); } listedNodes = Collections.unmodifiableList(builder); nodesSampler.sample(); } return this; } public TransportClientNodesService removeTransportAddress(TransportAddress transportAddress) { synchronized (mutex) { if (closed) { throw new IllegalStateException(“transport client is closed, can’t remove an address”); } List<DiscoveryNode> listNodesBuilder = new ArrayList<>(); for (DiscoveryNode otherNode : listedNodes) { if (!otherNode.getAddress().equals(transportAddress)) { listNodesBuilder.add(otherNode); } else { logger.debug(“removing address [{}] from listed nodes”, otherNode); } } listedNodes = Collections.unmodifiableList(listNodesBuilder); List<DiscoveryNode> nodesBuilder = new ArrayList<>(); for (DiscoveryNode otherNode : nodes) { if (!otherNode.getAddress().equals(transportAddress)) { nodesBuilder.add(otherNode); } else { logger.debug(“disconnecting from node with address [{}]”, otherNode); transportService.disconnectFromNode(otherNode); } } nodes = Collections.unmodifiableList(nodesBuilder); nodesSampler.sample(); } return this; } //……}TransportClientNodesService定义了三个关于DiscoveryNode的List属性,分别是listedNodes、nodes、filteredNodesaddTransportAddresses方法会更新listedNodes,然后调用nodesSampler.sample()更新nodes及filteredNodes;removeTransportAddress方法会更新listedNodes,nodes,然后调用nodesSampler.sample()更新nodes及filteredNodeslistedNodes即为通过addTransportAddresses方法添加的node(一般是通过配置文件指定的clusterNodes);nodesSampler.sample()方法会对listedNodes进行进一步检测,比如将clusterName不是当前配置的clusterName的放到filteredNodes,剩下的再进行连接的建立,成功的放到nodes里头TransportClient Nodeselasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportClient.javapublic abstract class TransportClient extends AbstractClient { private final TransportClientNodesService nodesService; private final TransportProxyClient proxy; //…… /** * Returns the current connected transport nodes that this client will use. * <p> * The nodes include all the nodes that are currently alive based on the transport * addresses provided. / public List<DiscoveryNode> connectedNodes() { return nodesService.connectedNodes(); } /* * The list of filtered nodes that were not connected to, for example, due to * mismatch in cluster name. / public List<DiscoveryNode> filteredNodes() { return nodesService.filteredNodes(); } /* * Returns the listed nodes in the transport client (ones added to it). / public List<DiscoveryNode> listedNodes() { return nodesService.listedNodes(); } /* * Adds a transport address that will be used to connect to. * <p> * The Node this transport address represents will be used if its possible to connect to it. * If it is unavailable, it will be automatically connected to once it is up. * <p> * In order to get the list of all the current connected nodes, please see {@link #connectedNodes()}. / public TransportClient addTransportAddress(TransportAddress transportAddress) { nodesService.addTransportAddresses(transportAddress); return this; } /* * Adds a list of transport addresses that will be used to connect to. * <p> * The Node this transport address represents will be used if its possible to connect to it. * If it is unavailable, it will be automatically connected to once it is up. * <p> * In order to get the list of all the current connected nodes, please see {@link #connectedNodes()}. / public TransportClient addTransportAddresses(TransportAddress… transportAddress) { nodesService.addTransportAddresses(transportAddress); return this; } /* * Removes a transport address from the list of transport addresses that are used to connect to. / public TransportClient removeTransportAddress(TransportAddress transportAddress) { nodesService.removeTransportAddress(transportAddress); return this; } //……}TransportClient提供了connectedNodes、filteredNodes、listedNodes方法,可以看到它们内部都是调用的TransportClientNodesService对应的方法;从注释上可以看到,connectedNodes返回的是当前已经建立连接的nodes,供client端使用;filteredNodes返回的是因为clusterName不匹配导致被过滤掉的nodes,这些nodes不会被client使用;listedNodes返回的是通过addTransportAddresses添加的nodesNodeSamplerScheduledNodeSamplerelasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportClientNodesService.java TransportClientNodesService(Settings settings, TransportService transportService, ThreadPool threadPool, TransportClient.HostFailureListener hostFailureListener) { super(settings); this.clusterName = ClusterName.CLUSTER_NAME_SETTING.get(settings); this.transportService = transportService; this.threadPool = threadPool; this.minCompatibilityVersion = Version.CURRENT.minimumCompatibilityVersion(); this.nodesSamplerInterval = TransportClient.CLIENT_TRANSPORT_NODES_SAMPLER_INTERVAL.get(this.settings); this.pingTimeout = TransportClient.CLIENT_TRANSPORT_PING_TIMEOUT.get(this.settings).millis(); this.ignoreClusterName = TransportClient.CLIENT_TRANSPORT_IGNORE_CLUSTER_NAME.get(this.settings); if (logger.isDebugEnabled()) { logger.debug(“node_sampler_interval[{}]”, nodesSamplerInterval); } if (TransportClient.CLIENT_TRANSPORT_SNIFF.get(this.settings)) { this.nodesSampler = new SniffNodesSampler(); } else { this.nodesSampler = new SimpleNodeSampler(); } this.hostFailureListener = hostFailureListener; this.nodesSamplerFuture = threadPool.schedule(nodesSamplerInterval, ThreadPool.Names.GENERIC, new ScheduledNodeSampler()); } //…… class ScheduledNodeSampler implements Runnable { @Override public void run() { try { nodesSampler.sample(); if (!closed) { nodesSamplerFuture = threadPool.schedule(nodesSamplerInterval, ThreadPool.Names.GENERIC, this); } } catch (Exception e) { logger.warn(“failed to sample”, e); } } } //……TransportClientNodesService的构造器里头会根据settings的client.transport.sniff配置(默认是false)来判断是创建SniffNodesSampler还是SimpleNodeSampler,通过threadPool注册一个调度任务,每隔nodesSamplerInterval执行ScheduledNodeSampler;ScheduledNodeSampler实现了Runnable接口,其fun方法主要是调用nodesSampler.sample(),之后只要TransportClientNodesService没有close,则会继续注册调度任务,并更新nodesSamplerFutureNodeSamplerelasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportClientNodesService.java abstract class NodeSampler { public void sample() { synchronized (mutex) { if (closed) { return; } doSample(); } } protected abstract void doSample(); /* * Establishes the node connections. If validateInHandshake is set to true, the connection will fail if * node returned in the handshake response is different than the discovery node. / List<DiscoveryNode> establishNodeConnections(Set<DiscoveryNode> nodes) { for (Iterator<DiscoveryNode> it = nodes.iterator(); it.hasNext(); ) { DiscoveryNode node = it.next(); if (!transportService.nodeConnected(node)) { try { logger.trace(“connecting to node [{}]”, node); transportService.connectToNode(node); } catch (Exception e) { it.remove(); logger.debug(() -> new ParameterizedMessage(“failed to connect to discovered node [{}]”, node), e); } } } return Collections.unmodifiableList(new ArrayList<>(nodes)); } }NodeSampler是个抽象类,它定义了sample方法,其内部是调用定义的抽象方法doSample;NodeSampler还提供了establishNodeConnections方法,它通过transportService.nodeConnected(node)来判断node是否是connected的,如果不是则会通过transportService.connectToNode(node)再尝试连接一次,如果抛异常则将该节点移除掉,最后返回这次检测是connected的nodes;它有两个子类,分别是SimpleNodeSampler、SniffNodesSamplerSimpleNodeSamplerelasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportClientNodesService.java class SimpleNodeSampler extends NodeSampler { @Override protected void doSample() { HashSet<DiscoveryNode> newNodes = new HashSet<>(); ArrayList<DiscoveryNode> newFilteredNodes = new ArrayList<>(); for (DiscoveryNode listedNode : listedNodes) { try (Transport.Connection connection = transportService.openConnection(listedNode, LISTED_NODES_PROFILE)){ final PlainTransportFuture<LivenessResponse> handler = new PlainTransportFuture<>( new FutureTransportResponseHandler<LivenessResponse>() { @Override public LivenessResponse read(StreamInput in) throws IOException { LivenessResponse response = new LivenessResponse(); response.readFrom(in); return response; } }); transportService.sendRequest(connection, TransportLivenessAction.NAME, new LivenessRequest(), TransportRequestOptions.builder().withType(TransportRequestOptions.Type.STATE).withTimeout(pingTimeout).build(), handler); final LivenessResponse livenessResponse = handler.txGet(); if (!ignoreClusterName && !clusterName.equals(livenessResponse.getClusterName())) { logger.warn(“node {} not part of the cluster {}, ignoring…”, listedNode, clusterName); newFilteredNodes.add(listedNode); } else { // use discovered information but do keep the original transport address, // so people can control which address is exactly used. DiscoveryNode nodeWithInfo = livenessResponse.getDiscoveryNode(); newNodes.add(new DiscoveryNode(nodeWithInfo.getName(), nodeWithInfo.getId(), nodeWithInfo.getEphemeralId(), nodeWithInfo.getHostName(), nodeWithInfo.getHostAddress(), listedNode.getAddress(), nodeWithInfo.getAttributes(), nodeWithInfo.getRoles(), nodeWithInfo.getVersion())); } } catch (ConnectTransportException e) { logger.debug(() -> new ParameterizedMessage(“failed to connect to node [{}], ignoring…”, listedNode), e); hostFailureListener.onNodeDisconnected(listedNode, e); } catch (Exception e) { logger.info(() -> new ParameterizedMessage(“failed to get node info for {}, disconnecting…”, listedNode), e); } } nodes = establishNodeConnections(newNodes); filteredNodes = Collections.unmodifiableList(newFilteredNodes); } }SimpleNodeSampler的doSample方法会对nodes进行更进一步的存活检测,主要是发送LivenessRequest,如果能成功返回LivenessResponse,则判断clusterName是否一致,不一致的添加到newFilteredNodes,最后赋值给filteredNodes;一致的添加到newNodes中,最后通过establishNodeConnections方法建立连接并移除连接失败的node(重试一次)最后赋值给nodesSniffNodesSamplerelasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportClientNodesService.java class SniffNodesSampler extends NodeSampler { @Override protected void doSample() { // the nodes we are going to ping include the core listed nodes that were added // and the last round of discovered nodes Set<DiscoveryNode> nodesToPing = new HashSet<>(); for (DiscoveryNode node : listedNodes) { nodesToPing.add(node); } for (DiscoveryNode node : nodes) { nodesToPing.add(node); } final CountDownLatch latch = new CountDownLatch(nodesToPing.size()); final ConcurrentMap<DiscoveryNode, ClusterStateResponse> clusterStateResponses = ConcurrentCollections.newConcurrentMap(); try { for (final DiscoveryNode nodeToPing : nodesToPing) { threadPool.executor(ThreadPool.Names.MANAGEMENT).execute(new AbstractRunnable() { /* * we try to reuse existing connections but if needed we will open a temporary connection * that will be closed at the end of the execution. */ Transport.Connection connectionToClose = null; void onDone() { try { IOUtils.closeWhileHandlingException(connectionToClose); } finally { latch.countDown(); } } @Override public void onFailure(Exception e) { onDone(); if (e instanceof ConnectTransportException) { logger.debug(() -> new ParameterizedMessage(“failed to connect to node [{}], ignoring…”, nodeToPing), e); hostFailureListener.onNodeDisconnected(nodeToPing, e); } else { logger.info(() -> new ParameterizedMessage( “failed to get local cluster state info for {}, disconnecting…”, nodeToPing), e); } } @Override protected void doRun() throws Exception { Transport.Connection pingConnection = null; if (nodes.contains(nodeToPing)) { try { pingConnection = transportService.getConnection(nodeToPing); } catch (NodeNotConnectedException e) { // will use a temp connection } } if (pingConnection == null) { logger.trace(“connecting to cluster node [{}]”, nodeToPing); connectionToClose = transportService.openConnection(nodeToPing, LISTED_NODES_PROFILE); pingConnection = connectionToClose; } transportService.sendRequest(pingConnection, ClusterStateAction.NAME, Requests.clusterStateRequest().clear().nodes(true).local(true), TransportRequestOptions.builder().withType(TransportRequestOptions.Type.STATE) .withTimeout(pingTimeout).build(), new TransportResponseHandler<ClusterStateResponse>() { @Override public ClusterStateResponse newInstance() { return new ClusterStateResponse(); } @Override public String executor() { return ThreadPool.Names.SAME; } @Override public void handleResponse(ClusterStateResponse response) { clusterStateResponses.put(nodeToPing, response); onDone(); } @Override public void handleException(TransportException e) { logger.info(() -> new ParameterizedMessage( “failed to get local cluster state for {}, disconnecting…”, nodeToPing), e); try { hostFailureListener.onNodeDisconnected(nodeToPing, e); } finally { onDone(); } } }); } }); } latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } HashSet<DiscoveryNode> newNodes = new HashSet<>(); HashSet<DiscoveryNode> newFilteredNodes = new HashSet<>(); for (Map.Entry<DiscoveryNode, ClusterStateResponse> entry : clusterStateResponses.entrySet()) { if (!ignoreClusterName && !clusterName.equals(entry.getValue().getClusterName())) { logger.warn(“node {} not part of the cluster {}, ignoring…”, entry.getValue().getState().nodes().getLocalNode(), clusterName); newFilteredNodes.add(entry.getKey()); continue; } for (ObjectCursor<DiscoveryNode> cursor : entry.getValue().getState().nodes().getDataNodes().values()) { newNodes.add(cursor.value); } } nodes = establishNodeConnections(newNodes); filteredNodes = Collections.unmodifiableList(new ArrayList<>(newFilteredNodes)); } }SniffNodesSampler的doSample方法首先将listedNodes及nodes合并为名为nodesToPing的Set,之后就挨个将nodesToPing的node放入到线程池异步执行检测,这里通过CountDownLatch来等待所有节点异步执行完毕异步线程池检测的逻辑是对node发送Requests.clusterStateRequest().clear().nodes(true).local(true)请求,如果成功则返回ClusterStateResponse,并添加到clusterStateResponses这个ConcurrentMap中之后遍历clusterStateResponses这个ConcurrentMap,clusterName不一致的node添加到newFilteredNodes,最后赋值给filteredNodes;clusterName一致的则遍历ClusterStateResponse.getState().nodes().getDataNodes().values(),将这些node添加到newNodes,最后通过establishNodeConnections方法建立连接并移除连接失败的node(重试一次)最后赋值给nodesTransportClientNodesService.executeelasticsearch-6.4.3-sources.jar!/org/elasticsearch/client/transport/TransportClientNodesService.javafinal class TransportClientNodesService extends AbstractComponent implements Closeable { private final AtomicInteger randomNodeGenerator = new AtomicInteger(Randomness.get().nextInt()); //…… public <Response> void execute(NodeListenerCallback<Response> callback, ActionListener<Response> listener) { // we first read nodes before checking the closed state; this // is because otherwise we could be subject to a race where we // read the state as not being closed, and then the client is // closed and the nodes list is cleared, and then a // NoNodeAvailableException is thrown // it is important that the order of first setting the state of // closed and then clearing the list of nodes is maintained in // the close method final List<DiscoveryNode> nodes = this.nodes; if (closed) { throw new IllegalStateException(“transport client is closed”); } ensureNodesAreAvailable(nodes); int index = getNodeNumber(); RetryListener<Response> retryListener = new RetryListener<>(callback, listener, nodes, index, hostFailureListener); DiscoveryNode node = retryListener.getNode(0); try { callback.doWithNode(node, retryListener); } catch (Exception e) { try { //this exception can’t come from the TransportService as it doesn’t throw exception at all listener.onFailure(e); } finally { retryListener.maybeNodeFailed(node, e); } } } private void ensureNodesAreAvailable(List<DiscoveryNode> nodes) { if (nodes.isEmpty()) { String message = String.format(Locale.ROOT, “None of the configured nodes are available: %s”, this.listedNodes); throw new NoNodeAvailableException(message); } } private int getNodeNumber() { int index = randomNodeGenerator.incrementAndGet(); if (index < 0) { index = 0; randomNodeGenerator.set(0); } return index; } public static class RetryListener<Response> implements ActionListener<Response> { private final NodeListenerCallback<Response> callback; private final ActionListener<Response> listener; private final List<DiscoveryNode> nodes; private final int index; private final TransportClient.HostFailureListener hostFailureListener; private volatile int i; RetryListener(NodeListenerCallback<Response> callback, ActionListener<Response> listener, List<DiscoveryNode> nodes, int index, TransportClient.HostFailureListener hostFailureListener) { this.callback = callback; this.listener = listener; this.nodes = nodes; this.index = index; this.hostFailureListener = hostFailureListener; } @Override public void onResponse(Response response) { listener.onResponse(response); } @Override public void onFailure(Exception e) { Throwable throwable = ExceptionsHelper.unwrapCause(e); if (throwable instanceof ConnectTransportException) { maybeNodeFailed(getNode(this.i), (ConnectTransportException) throwable); int i = ++this.i; if (i >= nodes.size()) { listener.onFailure(new NoNodeAvailableException(“None of the configured nodes were available: " + nodes, e)); } else { try { callback.doWithNode(getNode(i), this); } catch(final Exception inner) { inner.addSuppressed(e); // this exception can’t come from the TransportService as it doesn’t throw exceptions at all listener.onFailure(inner); } } } else { listener.onFailure(e); } } final DiscoveryNode getNode(int i) { return nodes.get((index + i) % nodes.size()); } final void maybeNodeFailed(DiscoveryNode node, Exception ex) { if (ex instanceof NodeDisconnectedException || ex instanceof NodeNotConnectedException) { hostFailureListener.onNodeDisconnected(node, ex); } } } //……}TransportClientNodesService提供的execute方法主要是做了两个事情,一个是对nodes节点进行客户端的负载均衡,一个是通过RetryListener对请求增加重试机制ensureNodesAreAvailable方法首先确保nodes这个列表不为空,如果为空则抛出NoNodeAvailableException;之后通过getNodeNumber方法来确定index值,该方法使用randomNodeGenerator递增得到index,如果index大于等于0则返回,如果index小于0则重置randomNodeGenerator的值为0并返回0;这里randomNodeGenerator是AtomicInteger类型,其初始值为Randomness.get().nextInt()RetryListener的构造器接收上一步计算出来的index值,它有一个i变量,初始为0,在onFailure的时候,如果是ConnectTransportException异常,则会进行重试,重试的时候首先将i递增,之后判断如果i>=nodes大小则停止重试,抛出NoNodeAvailableException,否则继续调用callback.doWithNode进行重试,重试时是通过getNode方法获取node,同时传入当前的listener;getNode方法采取的是(index + i) % nodes.size()来获取node的index,形成Round Robin的效果;对于RetryListener来说,内部重试时i会递增,对于execute方法来说,index值也是递增的,因而无论请求成功还是失败,对nodes的方法都形成Round Robin的效果小结TransportProxyClient主要是提供了execute方法,该方法从proxies取出对应的TransportActionNodeProxy,然后通过TransportClientNodesService的execute方法来执行proxy.execute方法;TransportActionNodeProxy提供了execute方法,它的方法参数要求输入DiscoveryNode、Request、ActionListener,该方法主要是对ActionListener包装为ActionListenerResponseHandler,然后调用transportService.sendRequestTransportClientNodesService定义了三个关于DiscoveryNode的List属性,分别是listedNodes、nodes、filteredNodes;其中listedNodes是通过addTransportAddresses添加的nodes;nodes是当前已经建立连接的node列表,供client端使用;filteredNodes是因为clusterName不匹配导致被过滤掉的nodes,这些nodes不会被client使用TransportClientNodesService的构造器里头会根据settings的client.transport.sniff配置(默认是false)来判断是创建SniffNodesSampler还是SimpleNodeSampler,通过threadPool注册一个调度任务,每隔nodesSamplerInterval执行ScheduledNodeSampler;ScheduledNodeSampler实现了Runnable接口,其fun方法主要是调用nodesSampler.sample(),之后只要TransportClientNodesService没有close,则会继续注册调度任务,并更新nodesSamplerFutureNodeSampler是个抽象类,它定义了sample方法,其内部是调用定义的抽象方法doSample;NodeSampler还提供了establishNodeConnections方法,它通过transportService.nodeConnected(node)来判断node是否是connected的,如果不是则会通过transportService.connectToNode(node)再尝试连接一次,如果抛异常则将该节点移除掉,最后返回这次检测是connected的nodes;它有两个子类,分别是SimpleNodeSampler、SniffNodesSamplerSimpleNodeSampler的doSample方法会对nodes进行更进一步的存活检测,主要是发送LivenessRequest,如果能成功返回LivenessResponse,则判断clusterName是否一致,不一致的添加到newFilteredNodes,最后赋值给filteredNodes;一致的添加到newNodes中,最后通过establishNodeConnections方法建立连接并移除连接失败的node(重试一次)最后赋值给nodesSniffNodesSampler的doSample方法首先将listedNodes及nodes合并为名为nodesToPing的Set,之后就挨个将nodesToPing的node放入到线程池异步执行检测,这里通过CountDownLatch来等待所有节点异步执行完毕;异步线程池检测的逻辑是对node发送Requests.clusterStateRequest().clear().nodes(true).local(true)请求,如果成功则返回ClusterStateResponse,并添加到clusterStateResponses这个ConcurrentMap中;之后遍历clusterStateResponses这个ConcurrentMap,clusterName不一致的node添加到newFilteredNodes,最后赋值给filteredNodes;clusterName一致的则遍历ClusterStateResponse.getState().nodes().getDataNodes().values(),将这些node添加到newNodes,最后通过establishNodeConnections方法建立连接并移除连接失败的node(重试一次)最后赋值给nodesTransportClientNodesService提供的execute方法主要是做了两个事情,一个是对nodes节点进行客户端的负载均衡,一个是通过RetryListener对请求增加重试机制;其对nodes的负载均衡策略为Round Robin,而RetryListener只对ConnectTransportException异常进行重试,最大重试次数为nodes.size()-1docno node available elasticsearchElasticsearch之client源码简要分析elasticsearch 源代码分析之客户端负载均衡 ...

April 19, 2019 · 10 min · jiezi

elasticsearch学习笔记(十一)——document的核心元数据、操作以及原理

先展示一个document数据结构GET /product/_doc/1{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 1, “_seq_no” : 0, “_primary_term” : 1, “found” : true, “_source” : { “name” : “gaolujie yagao”, “desc” : “gaoxiao meibai”, “price” : 30, “producer” : “gaolujie producer”, “tags” : [ “meibai”, “fangzhu” ] }}下面我们就来开始分析了1、document的核心元数据(1)_index元数据1、_index代表一个document存放在哪个index中2、对于document,类似的数据都是放在一个索引里面的,,非类似的数据放在不同的索引中。例如,product_index是包含了所有商品的index,sales_index是包含了所有商品的销售数据的index,inventory_index是包含了所有库存相关的数据。如果想把所有的这些数据都放在一个索引中,比如创建一个company_index,是不合适的。3、对于每个索引一般都是包含了很多类似的document,类似是什么意思,其实指的就是说,这些document的fields很大一部分是相同的,如果说你放了三个document,但是每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了。4、对于语法,要求每个索引名称必须是小写的,不能用下划线开头,不能包含逗号。(2)_type元数据1、_type代表这个document属于index中的哪个类别(type)2、一个索引只能有一个type,在后面的ES高版本中可能会废弃掉3、对于type的语法,它可以是大写或是小写,但是同时不能用下划线开头,不能包含逗号(3)_id元数据1、_id代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document2、我们可以手动指定document的id,也可以不指定,那ES就会自动为我们创建一个id下面附上中华石衫老师的手工图,说明一下为什么不同类型的数据不用一个索引存放归纳一下就是如果把多个不同类型的数据放在一个索引中存储,当用户查询某一类的数据的时候比如商品数据,大量的请求过来,发现此时后台数据分析系统对这个索引下的另一类数据在做聚合分析比如销售数据,此时这些shard正在执行非常耗时,耗费资源的大型的聚合分析操作。就会导致document get请求,大量的性能不好,甚至超时。让用户感觉上来说,网速好慢,影响用户体验。2、document id的生成(1)手动指定document id1、手动指定document id时,需要看下是否满足前提条件:一般来说,是从某些其他的系统中,导入一些数据到es时,会采取这种方式,就是使用系统中已有数据的唯一标识,作为es中的document id。举个例子,假如我们现在在开发一个电商网站,做搜索功能,或者是OA系统,做员工的检索功能。这个时候数据首先会在网站系统或者IT系统内部的数据库中,会先有一份,此时肯定就会有一个数据库的primary id(自增长,UUID,或者是业务编号)如果将数据导入到ES中,此时就比较适合采用数据在数据库中的已有primary key。2、格式PUT /{index}/{type}/{id}(2)自动生成document id在什么情况下使用自动的document id。对于日志的搜集使用自动的document id是比较适合的。还有就是比如我们是在做一个系统,这个系统主要的数据存储就是es一种,也就是说,数据产生出来以后,可能就没有id,直接就放ES存储,那么这个时候,可能就不太适合说手动指定document id的形式了。格式:POST /{index}/{type}注:自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突3、_source元数据以及定制返回结果(1)_source元数据先用一个例子引出一个document的_source,以及它的结构GET /product/_doc/1{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 1, “_seq_no” : 0, “_primary_term” : 1, “found” : true, “_source” : { “name” : “gaolujie yagao”, “desc” : “gaoxiao meibai”, “price” : 30, “producer” : “gaolujie producer”, “tags” : [ “meibai”, “fangzhu” ] }}可以看出_source元数据就是说,我们在创建一个document的时候,使用的那个放在request body请求体中的json串。(2)定制返回结果指定_source参数返回哪些field即可GET /product/_doc/1?_source=name,desc,tags{ “_index” : “product”, “_type” : “_doc”, “_id” : “1”, “_version” : 1, “_seq_no” : 0, “_primary_term” : 1, “found” : true, “_source” : { “name” : “gaolujie yagao”, “desc” : “gaoxiao meibai”, “tags” : [ “meibai”, “fangzhu” ] }}4、document的全量替换、强制创建以及lazy delete机制(1)document的全量替换1、全量替换的语法和创建文档是一样的,如果document id不存在,那么就是创建;如果document id已经存在,那么就是全量替换操作,替换document的json串内容2、document是不可变的,如果要修改document的内容,第一种方式就是全量替换,直接对document重新建立索引,替换里面所有的内容3、ES会将老的document标记为deleted,然后新增我们给定的一个document,当我们创建越来越多的document的时候,es会在适当的时机在后台自动删除标记为deleted的document(2)document强制创建创建文档和全量替换的语法是一样的,但是有时我们想新建文档,不想替换文档格式:PUT /{index}/{type}/{id}?op_type=create(3)document的删除格式:DELETE /{index}/{type}/{id}注意删除并不是物理删除,只是会将文档标记为deleted,当数据越来越多的时候,会在后台自动删除 ...

April 19, 2019 · 1 min · jiezi

elasticsearch: failed to send join request to master

环境: es 6.5.4最近断电服务器重启后,es集群里面有一台机器(node-209)一直报错,错误具体信息如下:[2019-04-16T23:56:59,255][INFO ][o.e.d.z.ZenDiscovery ] [node-209] failed to send join request to master [{node-208}{DmE9PTb1RI-QwyeDe3xPSg}{VLZTMH5yS7GYQrE89-hzwA}{192.168.5.208}{192.168.5.208:9300}{ml.machine_memory=66999791616, ml.max_open_jobs=20, xpack.installed=true, ml.enabled=true}], reason [RemoteTransportException[[node-208][192.168.5.208:9300][internal:discovery/zen/join]]; nested: ConnectTransportException[[node-209][172.17.0.1:9300] connect_timeout[30s]]; ]然后我开始一直按照failed to send join request to master 去找解决方案,结果都是说该节点下的数据是错误的,我备份了data,然后情况了data下的数据,结果还是有如上错误。后面反馈到老大哪儿去,老大详细看了下错误,一下就发现其中不对劲,node-209 的本机IP是: 192.168.5.209, 但是 在注册成es节点时的IP确是 172.17.0.1,查看下了该节点所在机器的网卡信息,172.17.0.1 这个是被docker虚拟出来的地址,而ES集群中只有该节点装有docker,导致 不能通过 172.17.0.1跟其他192.168.5.*上的节点通信。修改es的配置,将修改为 network.host: 192.168.5.209, 然后本机测试 192.168.5.209:9200,访问也OK总结:看信息一定要先看全啊,不能看了一半就跑

April 19, 2019 · 1 min · jiezi

聊聊springboot elasticsearch healthIndicator

序本文主要研究一下springboot elasticsearch healthIndicatorElasticsearchHealthIndicatorPropertiesspring-boot-actuator-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticsearchHealthIndicatorProperties.java@ConfigurationProperties(prefix = “management.health.elasticsearch”, ignoreUnknownFields = false)public class ElasticsearchHealthIndicatorProperties { /** * Comma-separated index names. / private List<String> indices = new ArrayList<>(); /* * Time to wait for a response from the cluster. / private Duration responseTimeout = Duration.ofMillis(100); public List<String> getIndices() { return this.indices; } public void setIndices(List<String> indices) { this.indices = indices; } public Duration getResponseTimeout() { return this.responseTimeout; } public void setResponseTimeout(Duration responseTimeout) { this.responseTimeout = responseTimeout; }}ElasticsearchHealthIndicatorProperties提供了indices,responseTimeout两个配置项ElasticSearchClientHealthIndicatorAutoConfigurationspring-boot-actuator-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchClientHealthIndicatorAutoConfiguration.java@Configuration@ConditionalOnClass(Client.class)@ConditionalOnBean(Client.class)@ConditionalOnEnabledHealthIndicator(“elasticsearch”)@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)@AutoConfigureAfter(ElasticsearchAutoConfiguration.class)@EnableConfigurationProperties(ElasticsearchHealthIndicatorProperties.class)public class ElasticSearchClientHealthIndicatorAutoConfiguration extends CompositeHealthIndicatorConfiguration<ElasticsearchHealthIndicator, Client> { private final Map<String, Client> clients; private final ElasticsearchHealthIndicatorProperties properties; public ElasticSearchClientHealthIndicatorAutoConfiguration( Map<String, Client> clients, ElasticsearchHealthIndicatorProperties properties) { this.clients = clients; this.properties = properties; } @Bean @ConditionalOnMissingBean(name = “elasticsearchHealthIndicator”) public HealthIndicator elasticsearchHealthIndicator() { return createHealthIndicator(this.clients); } @Override protected ElasticsearchHealthIndicator createHealthIndicator(Client client) { Duration responseTimeout = this.properties.getResponseTimeout(); return new ElasticsearchHealthIndicator(client, (responseTimeout != null) ? responseTimeout.toMillis() : 100, this.properties.getIndices()); }}ElasticSearchClientHealthIndicatorAutoConfiguration创建的是ElasticsearchHealthIndicator(elasticsearch),它是通过org.elasticsearch.client.Client去检测的ElasticsearchHealthIndicatorspring-boot-actuator-2.1.4.RELEASE-sources.jar!/org/springframework/boot/actuate/elasticsearch/ElasticsearchHealthIndicator.javapublic class ElasticsearchHealthIndicator extends AbstractHealthIndicator { private static final String[] ALL_INDICES = { “_all” }; private final Client client; private final String[] indices; private final long responseTimeout; /* * Create a new {@link ElasticsearchHealthIndicator} instance. * @param client the Elasticsearch client * @param responseTimeout the request timeout in milliseconds * @param indices the indices to check / public ElasticsearchHealthIndicator(Client client, long responseTimeout, List<String> indices) { this(client, responseTimeout, (indices != null) ? StringUtils.toStringArray(indices) : null); } /* * Create a new {@link ElasticsearchHealthIndicator} instance. * @param client the Elasticsearch client * @param responseTimeout the request timeout in milliseconds * @param indices the indices to check */ public ElasticsearchHealthIndicator(Client client, long responseTimeout, String… indices) { super(“Elasticsearch health check failed”); this.client = client; this.responseTimeout = responseTimeout; this.indices = indices; } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { ClusterHealthRequest request = Requests.clusterHealthRequest( ObjectUtils.isEmpty(this.indices) ? ALL_INDICES : this.indices); ClusterHealthResponse response = this.client.admin().cluster().health(request) .actionGet(this.responseTimeout); switch (response.getStatus()) { case GREEN: case YELLOW: builder.up(); break; case RED: default: builder.down(); break; } builder.withDetail(“clusterName”, response.getClusterName()); builder.withDetail(“numberOfNodes”, response.getNumberOfNodes()); builder.withDetail(“numberOfDataNodes”, response.getNumberOfDataNodes()); builder.withDetail(“activePrimaryShards”, response.getActivePrimaryShards()); builder.withDetail(“activeShards”, response.getActiveShards()); builder.withDetail(“relocatingShards”, response.getRelocatingShards()); builder.withDetail(“initializingShards”, response.getInitializingShards()); builder.withDetail(“unassignedShards”, response.getUnassignedShards()); }}ElasticsearchHealthIndicator继承了AbstractHealthIndicator,这里如果不指定indices的话,默认是ALL_INDICES(_all);doHealthCheck方法使用client.admin().cluster().health(request)来进行请求,之后根据ClusterHealthResponse的状态来决定是up还是down,如果是GREEN或YELLOW则返回Status.UP,如果是RED则返回Status.DOWNElasticSearchRestHealthIndicatorAutoConfigurationspring-boot-actuator-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchRestHealthIndicatorAutoConfiguration.java@Configuration@ConditionalOnClass(RestClient.class)@ConditionalOnBean(RestClient.class)@ConditionalOnEnabledHealthIndicator(“elasticsearch”)@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)@AutoConfigureAfter({ RestClientAutoConfiguration.class, ElasticSearchClientHealthIndicatorAutoConfiguration.class })public class ElasticSearchRestHealthIndicatorAutoConfiguration extends CompositeHealthIndicatorConfiguration<ElasticsearchRestHealthIndicator, RestClient> { private final Map<String, RestClient> clients; public ElasticSearchRestHealthIndicatorAutoConfiguration( Map<String, RestClient> clients) { this.clients = clients; } @Bean @ConditionalOnMissingBean(name = “elasticsearchRestHealthIndicator”) public HealthIndicator elasticsearchRestHealthIndicator() { return createHealthIndicator(this.clients); } @Override protected ElasticsearchRestHealthIndicator createHealthIndicator(RestClient client) { return new ElasticsearchRestHealthIndicator(client); }}ElasticSearchRestHealthIndicatorAutoConfiguration创建的是ElasticsearchRestHealthIndicator(elasticsearchRest),它是通过org.elasticsearch.client.RestClient去检测的ElasticsearchRestHealthIndicatorspring-boot-actuator-2.1.4.RELEASE-sources.jar!/org/springframework/boot/actuate/elasticsearch/ElasticsearchRestHealthIndicator.javapublic class ElasticsearchRestHealthIndicator extends AbstractHealthIndicator { private static final String RED_STATUS = “red”; private final RestClient client; private final JsonParser jsonParser; public ElasticsearchRestHealthIndicator(RestClient client) { super(“Elasticsearch health check failed”); this.client = client; this.jsonParser = JsonParserFactory.getJsonParser(); } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { Response response = this.client .performRequest(new Request(“GET”, “/_cluster/health/”)); StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() != HttpStatus.SC_OK) { builder.down(); builder.withDetail(“statusCode”, statusLine.getStatusCode()); builder.withDetail(“reasonPhrase”, statusLine.getReasonPhrase()); return; } try (InputStream inputStream = response.getEntity().getContent()) { doHealthCheck(builder, StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8)); } } private void doHealthCheck(Health.Builder builder, String json) { Map<String, Object> response = this.jsonParser.parseMap(json); String status = (String) response.get(“status”); if (RED_STATUS.equals(status)) { builder.outOfService(); } else { builder.up(); } builder.withDetails(response); }}ElasticsearchRestHealthIndicator继承了AbstractHealthIndicator,构造器通过JsonParserFactory.getJsonParser()创建了JsonParserdoHealthCheck方法通过RestClient.performRequest(new Request(“GET”, “/_cluster/health/”))进行请求,如果http response status code不是HttpStatus.SC_OK,直接返回Status.DOWN;如果是HttpStatus.SC_OK再进一步解析json判断私有的doHealthCheck方法通过jsonParser.parseMap(json)解析返回json为Map,然后取status字段,如果是red则返回Status.OUT_OF_SERVICE,否则返回Status.UPGET /_cluster/health/在http response status code为200的情况下返回的结构实例如下:{ “cluster_name” : “docker-cluster”, “status” : “yellow”, “timed_out” : false, “number_of_nodes” : 1, “number_of_data_nodes” : 1, “active_primary_shards” : 8, “active_shards” : 8, “relocating_shards” : 0, “initializing_shards” : 0, “unassigned_shards” : 6, “delayed_unassigned_shards” : 0, “number_of_pending_tasks” : 0, “number_of_in_flight_fetch” : 0, “task_max_waiting_in_queue_millis” : 0, “active_shards_percent_as_number” : 57.14285714285714}ElasticSearchJestHealthIndicatorAutoConfigurationspring-boot-actuator-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/actuate/autoconfigure/elasticsearch/ElasticSearchJestHealthIndicatorAutoConfiguration.java@Configuration@ConditionalOnClass(JestClient.class)@ConditionalOnBean(JestClient.class)@ConditionalOnEnabledHealthIndicator(“elasticsearch”)@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class)@AutoConfigureAfter({ JestAutoConfiguration.class, ElasticSearchClientHealthIndicatorAutoConfiguration.class })public class ElasticSearchJestHealthIndicatorAutoConfiguration extends CompositeHealthIndicatorConfiguration<ElasticsearchJestHealthIndicator, JestClient> { private final Map<String, JestClient> clients; public ElasticSearchJestHealthIndicatorAutoConfiguration( Map<String, JestClient> clients) { this.clients = clients; } @Bean @ConditionalOnMissingBean(name = “elasticsearchHealthIndicator”) public HealthIndicator elasticsearchHealthIndicator() { return createHealthIndicator(this.clients); } @Override protected ElasticsearchJestHealthIndicator createHealthIndicator(JestClient client) { return new ElasticsearchJestHealthIndicator(client); }}ElasticSearchJestHealthIndicatorAutoConfiguration创建的是ElasticsearchJestHealthIndicator(elasticsearch),它是通过io.searchbox.client.JestClient去检测的ElasticsearchJestHealthIndicatorspring-boot-actuator-2.1.4.RELEASE-sources.jar!/org/springframework/boot/actuate/elasticsearch/ElasticsearchJestHealthIndicator.javapublic class ElasticsearchJestHealthIndicator extends AbstractHealthIndicator { private final JestClient jestClient; private final JsonParser jsonParser = JsonParserFactory.getJsonParser(); public ElasticsearchJestHealthIndicator(JestClient jestClient) { super(“Elasticsearch health check failed”); this.jestClient = jestClient; } @Override protected void doHealthCheck(Health.Builder builder) throws Exception { JestResult healthResult = this.jestClient .execute(new io.searchbox.cluster.Health.Builder().build()); if (healthResult.getResponseCode() != 200 || !healthResult.isSucceeded()) { builder.down(); builder.withDetail(“statusCode”, healthResult.getResponseCode()); } else { Map<String, Object> response = this.jsonParser .parseMap(healthResult.getJsonString()); String status = (String) response.get(“status”); if (status.equals(io.searchbox.cluster.Health.Status.RED.getKey())) { builder.outOfService(); } else { builder.up(); } builder.withDetails(response); } }}ElasticsearchJestHealthIndicator继承了AbstractHealthIndicator,构造器通过接收JestClientdoHealthCheck方法通过jestClient.execute(new io.searchbox.cluster.Health.Builder().build())进行请求,如果http response status code不是200,或者healthResult.isSucceeded()不是true则直接返回Status.DOWN如果http response status code是200且healthResult.isSucceeded()为true则再进一步通过jsonParser.parseMap(json)解析返回json为Map,然后取status字段,如果是io.searchbox.cluster.Health.Status.RED.getKey()则返回Status.OUT_OF_SERVICE,否则返回Status.UP小结springboot提供了三个elasticsearch的healthIndicator配置,分别是ElasticSearchClientHealthIndicatorAutoConfiguration、ElasticSearchRestHealthIndicatorAutoConfiguration、ElasticSearchJestHealthIndicatorAutoConfigurationElasticSearchClientHealthIndicatorAutoConfiguration创建的是ElasticsearchHealthIndicator(elasticsearch),它是通过org.elasticsearch.client.Client去检测的ElasticSearchRestHealthIndicatorAutoConfiguration创建的是ElasticsearchRestHealthIndicator(elasticsearchRest),它是通过org.elasticsearch.client.RestClient去检测的ElasticSearchJestHealthIndicatorAutoConfiguration创建的是ElasticsearchJestHealthIndicator(elasticsearch),它是通过io.searchbox.client.JestClient去检测的ElasticsearchHealthIndicatorProperties提供了indices,responseTimeout两个配置项,对于使用org.elasticsearch.client.Client的如果没有配置应用使用的indices,则使用ALL_INDICES(_all)去请求;而使用org.elasticsearch.client.RestClient或io.searchbox.client.JestClient的它们请求的是/_cluster/health/这个接口docspring-boot-actuator-autoconfigure module ...

April 18, 2019 · 4 min · jiezi

elasticsearch学习笔记(十)——Elasticsearch横向扩容过程与容错机制

下面简单描述一下Elasticsearch横向扩容过程与容错机制1、横向扩容过程对于ES默认创建的索引有10个shard,其中有5个是primary shard,5个是replica shard。在ES内部会自动做一些事情:(1)primary shard & replica shard会自动负载均衡。均匀的分布在各个节点(2)保持每个节点node拥有更少的shard,IO/CPU/Memory资源给每个shard分配更多,使得每个shard性能更好(3)Elasticsearch的扩容极限,由于有10个shard(5个primary shard,5个replica shard),所以最多可以扩容到6台机器,此时每个shard可以占用单台服务器的所有资源,性能最好。(4)如果超出扩容的极限,可以动态的修改replica数量,比如将replica修改为2,那么就有15个分片(5个primary shard,10个replica shard),此时就可以扩容到15台机器,比之前拥有更高的读吞吐量。(5)如果只有5台机器,15个分片(5个primary shard,10个replica shard),每个shard占用的资源会更少,但是容错性会比10个分片的要好,此时最多可以容纳2台机器宕机,而10个分片只能容纳1台机器宕机。这些知识点告诉我们,一方面扩容应该怎么去扩,怎么去提升系统整体的吞吐量;另一方面还要考虑到系统的容错性,怎样提高系统的容错性,让尽可能多的服务器宕机,不会造成数据的丢失。2、容错机制详解场景描述:假设master node1节点宕机的一瞬间,P0,P1,P2,P3,P4这些primary shard就没了,也就是说此时就不是active status下面是ES做的容错的一个过程:第一步:master选举,自动选择另一台node作为新的master节点,承担起master的责任来第二步:新的master node2将丢失掉primary shard的某个replica shard提升为primary shard。此时cluster status就会变为yellow,因为primary shard全部变成active了,但是少了一个replica shard,所以就不是所有的replica shard都是active的第三步:重启故障的node,新的master会将缺失的副本都copy一份到该node上去。而且该node会使用之前已有的shard数据,只是同步一下宕机之后发生过的修改。cluster的状态变为green,因为primary shard和replica shard都齐全了。

April 17, 2019 · 1 min · jiezi

elasticsearch学习笔记(九)——shard&replica机制以及ES集群节点的问题

1、shard和replica知识归纳梳理(1)一个index包含多个shard(2)每个shard都是一个最小工作单元,承载部分数据,可以说就是一个lucene实例,拥有完整的建立索引和处理请求的能力(3)每当ES集群增加节点时,shard会自动在nodes中实现负载均衡(4)对于primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard中(5)replica shard时primary shard的副本,主要负责容错,以及承担读请求的负载(6)primary shard的数量在创建索引的时候就已经固定了,但是replica shard的数量可以随时修改(7)primary shard的默认数量是5个,replica默认是1,也就是在默认的情况下,有5个primary shard和5个replica shard(8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,也就起不到容错的作用了),但是可以和其它primary shard的replica shard放在同一个节点上2、对于单节点ES集群存在的问题假设我们创建一个索引test_indexPUT /test_index{ “settings”: { “number_of_shards”: 5, “number_of_replicas”: 1 }}(1)单node环境下,创建一个index叫test_index,有5个primary shard,5个replica shard(2)集群的status是yellowGET /_cat/health?vepoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent1555495650 10:07:30 elasticsearch yellow 1 1 8 8 0 0 6 0 - 57.1%(3)这个时候,只会将5个primary shard分配到仅有的一个node上去,另外5个replica shard是无法分配的(4)此时集群是可以正常工作的,但是一旦出现节点宕机,数据就会全部丢失,此时节点也不可用,无法承担任何请求结构如下:3、对于两个节点ES集群上面test_index索引的shard分配的结构就会变成:此时即使存放primary shard的节点挂掉了,ES的shard allocation 会被触发,此时对应的primary shard的一个副本会变成primary shard在什么场景下会触发Shard的Allocation:创建/删除一个Index;加入/离开一个Node;手动执行了Reroute命令;修改了Replica设置;

April 17, 2019 · 1 min · jiezi