Elasticarch架构

更新时间:2023-06-04 07:35:30 阅读: 评论:0

Elasticarch架构
Elasticarch 是最近两年异军突起的⼀个兼有搜索引擎和NoSQL数据库功能的开源系统,基于Java/Lucene构建。最近研究了⼀下,感觉 Elasticarch 的架构以及其开源的⽣态构建都有许多可借鉴之处,所以整理成⽂章分享下。本⽂的代码以及架构分析主要基于Elasticarch 2.X 最新稳定版。 Elasticarch 看名字就能⼤概了解下它是⼀个弹性的搜索引擎。⾸先弹性隐含的意思是分布式,单机系统是没法弹起来的,然后加上灵活的伸缩机制,就是这⾥的 Elastic 包含的意思。它的搜索存储功能主要是 Lucene 提供的,Lucene 相当于其存储引擎,它在之上封装了索引,查询,以及分布式相关的接⼝。
Elasticarch 中的⼏个概念
集群(Cluster)⼀组拥有共同的 cluster name 的节点。
节点(Node) 集群中的⼀个 Elasticearch 实例。
索引(Index) 相当于关系数据库中的databa概念,⼀个集群中可以包含多个索引。这个是个逻辑概念。
主分⽚(Primary shard) 索引的⼦集,索引可以切分成多个分⽚,分布到不同的集群节点上。分⽚对应的是 Lucene 中的索引。
副本分⽚(Replica shard)每个主分⽚可以有⼀个或者多个副本。
类型(Type)相当于数据库中的table概念,mapping是针对 Type 的。同⼀个索引⾥可以包含多个 Type。
Mapping 相当于数据库中的schema,⽤来约束字段的类型,不过 Elasticarch 的 mapping 可以⾃动根据数据创建。
⽂档(Document) 相当于数据库中的row。开学第一周作文
字段(Field)相当于数据库中的column。
分配(Allocation) 将分⽚分配给某个节点的过程,包括分配主分⽚或者副本。如果是副本,还包含从主分⽚复制数据的过程。
分布式以及 Elastic
分布式系统要解决的第⼀个问题就是节点之间互相发现以及选主的机制。如果使⽤了 Zookeeper/Etcd 这样的成熟的服务发现⼯具,这两个问题都⼀并解决了。但 Elasticarch 并没有依赖这样的⼯具,带来的好处是部署服务的成本和复杂度降低了,不⽤预先依赖⼀个服务发现的集群,缺点当然是将复杂度带⼊了 Elasticarch 内部。
服务发现以及选主 ZenDiscovery
1. 节点启动后先ping(这⾥的ping是 Elasticarch 的⼀个RPC命令。如果 ping.unicast.hosts 有设置,则ping设置
中的host,否则尝试ping localhost 的⼏个端⼝, Elasticarch ⽀持同⼀个主机启动多个节点)
2. Ping的respon会包含该节点的基本信息以及该节点认为的master节点。
3. 选举开始,先从各节点认为的master中选,规则很简单,按照id的字典序排序,取第⼀个。
4. 如果各节点都没有认为的master,则从所有节点中选择,规则同上。这⾥有个限制条件就是
5. 最后选举结果是肯定能选举出⼀个master,如果只有⼀个local节点那就选出的是⾃⼰。
6. 如果当前节点是master,则开始等待节点数达到 minimum_master_nodes,然后提供服务。
7. 如果当前节点不是master,则尝试加⼊master。
Elasticarch 将以上服务发现以及选主的流程叫做 ZenDiscovery 。由于它⽀持任意数⽬的集群(1-N),所以不能像 Zookeeper/Etcd 那样限制节点必须是奇数,也就⽆法⽤投票的机制来选主,⽽是通过⼀个规则,只要所有的节点都遵循同样的规则,得到的信息都是对等的,选出来的主节点肯定是⼀致的。但分布式系统的问题就出在信息不对等的情况,这时候很容易出现脑裂(Split-Brain)的问题,⼤多数解决⽅案就是设置⼀个quorum值,要求可⽤节点必须⼤于quorum(⼀般是超过半数节点),才能对外提供服务。⽽ Elasticarch 中,这个quorum的配置就是 inimum_master_nodes 。 说到这⾥要吐槽下 Elasticarch 的⽅法和变量命名,它的⽅法和配置中的master指的是master的候选节点,也就是说可能成为master的节点,并不是表⽰当前的master,我就被它的⼀个isMasterNode ⽅法坑了,开始⼀直没能理解它的选举规则。
弹性伸缩 Elastic
Elasticarch 的弹性体现在两个⽅⾯: 1. 服务发现机制让节点很容易加⼊和退出。 2. 丰富的设置以及allocation API。
Elasticarch 节点启动的时候只需要配置ping.unicast.hosts,这⾥不需要列举集群中所有的节点,只要知道其中⼀个即可。当然为了避免重启集群时正好配置的节点挂掉,最好多配置⼏个节点。节点退出时只需要调⽤ API 将该节点从集群中排除 (),系统会⾃动迁移该节点上的数据,然
后关闭该节点即可。当然最好也将不可⽤的已知节点从其他节点的配置中去除,避免下次启动时出错。
分⽚(Shard)以及副本(Replica)  分布式存储系统为了解决单机容量以及容灾的问题,都需要有分⽚以及副本机制。Elasticarch 没有采⽤节点级别的主从复制,⽽是基于分⽚。它当前还未提供分⽚切分(shard-splitting)的机制,只能创建索引的时候静态设置。
⽐如上图所⽰,开始设置为5个分⽚,在单个节点上,后来扩容到5个节点,每个节点有⼀个分⽚。如果继续扩容,是不能⾃动切分进⾏数据迁移的。官⽅⽂档的说法是分⽚切分成本和重新索引的成本差不多,所以建议⼲脆通过接⼝。
Elasticarch 的分⽚默认是基于id 哈希的,id可以⽤户指定,也可以⾃动⽣成。但这个可以通过参数(routing)或者在mapping配置中修改。当前版本默认的哈希算法是。
Elasticarch 禁⽌同⼀个分⽚的主分⽚和副本分⽚在同⼀个节点上,所以如果是⼀个节点的集群是不能有副本的。
恢复以及容灾
分布式系统的⼀个要求就是要保证⾼可⽤。前⾯描述的退出流程是节点主动退出的场景,但如果是故障导致节点挂掉,Elasticarch 就会主动allocation。但如果节点丢失后⽴刻allocation,稍后节点恢复⼜⽴刻加⼊,会造成浪费。Elasticarch的恢复流程⼤致如下:
1. 集群中的某个节点丢失⽹络连接
2. master提升该节点上的所有主分⽚的在其他节点上的副本为主分⽚
红楼梦是哪个朝代的故事3. cluster集群状态变为 yellow ,因为副本数不够
4. 等待⼀个超时设置的时间,如果丢失节点回来就可以⽴即恢复(默认为1分钟,通过 de_left.delayed_timeout
设置)。如果该分⽚已经有写⼊,则通过translog进⾏增量同步数据。
5. 否则将副本分配给其他节点,开始同步数据。
但如果该节点上的分⽚没有副本,则⽆法恢复,集群状态会变为red,表⽰可能要丢失该分⽚的数据了。
分布式集群的另外⼀个问题就是集群整个重启后可能导致不预期的分⽚重新分配(部分节点没有启动完成的时候,集群以为节点丢失),浪费带宽。所以 Elasticarch 通过以下静态配置(不能通过API修改)控制整个流程,以10个节点的集群为例:
⽐如10个节点的集群,按照上⾯的规则配置,当集群重启后,⾸先系统等待 minimum_master_nodes(6)个节点加⼊才会选出master, recovery操作是在 master节点上进⾏的,由于我们设置了 recover_after_nodes(8),系统会继续等待到8个节点加⼊, 才开始进⾏recovery。当开始recovery的时候,如果发现集群中的节点数⼩于expected_nodes,也就是还有部分节点未加⼊,于是开始recover_after_time 倒计时(如果节点数达到expected_nodes则⽴刻进⾏ recovery),5分钟后,如果剩余的节点依然没有加⼊,则会进⾏数据recovery。
搜索引擎 Search
Elasticarch 除了⽀持 Lucene 本⾝的检索功能外,在之上做了⼀些扩展。 1. 脚本⽀持
Elasticarch 默认⽀持groovy脚本,扩展了 Lucene 的评分机制,可以很容易的⽀持复杂的⾃定义评分算法。它默认只⽀持通过sandbox⽅式实现的脚本语⾔(如lucene expression,mustache),groovy必须明确设置后才能开启。Groovy的安全机制是通过java.curity.AccessControlContext设置了⼀个class⽩名单来控制权限的,1.x版本的时候是⾃⼰做的⼀个⽩名单过滤器,但限制策略有漏洞,导致⼀个。 2. 默认会⽣成⼀个 _all 字段,将所有其他字段的值拼接在⼀起。这样搜索时可以不指定字段,并且⽅便实现跨字段的检索。 3. Suggester Elasticarch 通过扩展的索引机制,可以实现像google那样的⾃动完成suggestion以及搜索词语错误纠正的suggestion。
自行车技巧
NoSQL 数据库
Elasticarch 可以作为数据库使⽤,主要依赖于它的以下特性:
1. 默认在索引中保存原始数据,并可获取。这个主要依赖 Lucene 的store功能。
2. 实现了translog,提供了实时的数据读取能⼒以及完备的数据持久化能⼒(在服务器异常挂掉的情况下依然不会丢数据)。Lucene
因为有 IndexWriter buffer, 如果进程异常挂掉,buffer中的数据是会丢失的。所以 Elasticarch 通过translog来确保不丢数据。
同时通过id直接读取⽂档的时候,Elasticarch 会先尝试从translog中读取,之后才从索引中读取。也就是说,即便是buffer中的数据尚未刷新到索引,依然能提供实时的数据读取能⼒。Elasticarch 的translog 默认是每次写请求完成后统⼀fsync⼀次,同时有个定时任务检测(默认5秒钟⼀次)。如果业务场景需要更⼤的写吞吐量,可以调整translog相关的配置进⾏优化。
3.  以及 schema-free
Elasticarch 的dynamic-mapping相当于根据⽤户提交的数据,动态检测字段类型,⾃动给数据库表
建⽴表结构,也可以动态增加字段,所以它叫做schema-free,⽽不是schema-less。这种⽅式的好处是⽤户能⼀定程度享受schema-less的好处,不⽤提前建⽴表结构,同时因为实际上是有schema的,可以做查询上的优化,检索效率要⽐纯schema-less的数据库⾼许多。但缺点就是已经创建的索引不能变更数据类型(Elasticarch 写⼊数据的时候如果类型不匹配会⾃动尝试做类型转换,如果失败就会报错,⽐如数字类型的字段写⼊字符串”123”是可以的,但写⼊”abc”就不可以。),要损失⼀定的⾃由度。
另外 Elasticarch 提供的index-template功能⽅便⽤户动态创建索引的时候预先设定索引的相关参数以及type mapping,⽐如按天创建⽇志库,template可以设置为对 log-* 的索引都⽣效。
这两个功能我建议新的数据库都可以借鉴下。
4. 丰富的QueryDSL功能
Elasticarch 的query语法基本上和sql对等的,除了join查询,以及嵌套临时表查询不能⽀持。不过 Elasticarch ⽀持嵌套对象以及parent外部引⽤查询,所以⼀定程度上可以解决关联查询的需求。另外group by这种查询可以通过其aggregation实现。
Elasticarch 提供的aggregation能⼒⾮常强⼤,其⽣态圈⾥的 Kibana 主要就是依赖aggregation来实现数据分析以及可视化的。系统架构
Elasticarch 的依赖注⼊⽤的是guice,⽹络使⽤netty,提供http rest和RPC两种协议。
Elasticarch 之所以⽤guice,⽽不是⽤spring做依赖注⼊,关键的⼀个原因是guice可以帮它很容易的实现模块化,通过代码进⾏模块组装,可以很精确的控制依赖注⼊的管理范围。⽐如 Elasticarch 给每个shard单独⽣成⼀个injector,可以将该shard相关的配置以及组件注⼊进去,降低编码和状态管理的复杂度,同时删除shard的时候也⽅便回收相关对象。这⽅⾯有兴趣使⽤guice的可以借鉴。
ClusterState
前⾯我们分析了 Elasticarch 的服务发现以及选举机制,它是内部⾃⼰实现的。服务发现⼯具做的事情其实就是跨服务器的状态同步,多个节点修改同⼀个数据对象,需要有⼀种机制将这个数据对象同步到所有的节点。Elasticarch 的ClusterState 就是这样⼀个数据对象,保存了集群的状态,索引/分⽚的路由表,节点列表,元数据等,还包含⼀个ClusterBlocks,相当于分布式锁,⽤于实现分布式的任务同步。
主节点上有个单独的进程处理 ClusterState 的变更操作,每次变更会更新版本号。变更后会通过PRC接⼝同步到其他节点。主节知道其他节点的ClusterState 的当前版本,发送变更的时候会做diff,实现增量更新。书香家庭
Rest 和 RPC
Elasticarch 的rest请求的传递流程如上图(这⾥对实际流程做了简化): 1. ⽤户发起http请求,Elasticarch 的9200端⼝接受请求后,传递给对应的RestAction。 2. RestAction做的事情很简单,将rest请求转换为RPC的TransportRequest,然后调⽤NodeClient,相当于⽤客户端的⽅式请求RPC服务,只不过transport层会对本节点的请求特殊处理。
为什么胆红素会高
这样做的好处是将http和RPC两层隔离,增加部署的灵活性。部署的时候既可以同时开启RPC和http服务,也可以⽤client模式部署⼀组服务专门提供http rest服务,另外⼀组只开启RPC服务,专门做dat
a节点,便于分担压⼒。
Elasticarch 的RPC的序列化机制使⽤了 Lucene 的压缩数据类型,⽀持vint这样的变长数字类型,省略了字段名,⽤流式⽅式按顺序写⼊字段的值。每个需要传输的对象都需要实现:
void writeTo(StreamOutput out)
T readFrom(StreamInput in)
1
2
两个⽅法。虽然这样实现开发成本略⾼,增删字段也不太灵活,但对 Elasticarch 这样的数据库系统来说,不⽤考虑跨语⾔,增删字段肯定要考虑兼容性,这样做效率最⾼。所以 Elasticarch 的RPC接⼝只有java client可以直接请求,其他语⾔的客户端都⾛的是rest接⼝。
⽹络层
Elasticarch 的⽹络层抽象很值得借鉴。它抽象出⼀个 Transport 层,同时兼有client和rver功能,rver端接收其他节点的连
接,client维持和其他节点的连接,承担了节点之间请求转发的功能。Elasticarch 为了避免传输流量⽐较⼤的操作堵塞连接,所以会按照优先级创建多个连接,称为channel。
recovery: 2个channel专门⽤做恢复数据。如果为了避免恢复数据时将带宽占满,还可以设置恢复数据时的⽹络传输速度。
bulk: 3个channel⽤来传输批量请求等基本⽐较低的请求。
regular: 6个channel⽤来传输通⽤正常的请求,中等级别。为情所困的诗句
state: 1个channel保留给集群状态相关的操作,⽐如集群状态变更的传输,⾼级别。女英文名大全
ping: 1个channel专门⽤来ping,进⾏故障检测。
(3个节点的集群连接⽰意,来源 Elasticarch 官⽅博客)
每个节点默认都会创建13个到其他节点的连接,并且节点之间是互相连接的,每增加⼀个节点,该节点会到每个节点创建13个连接,⽽其他每个节点也会创建13个连回来的连接。
线程池
由于java不⽀持绿⾊线程(fiber/coroutine),我前⾯的《》那篇⽂章也分析了线程池的问题,线程池⾥保留多少线程合适?如何避免慢的任务占⽤线程池,导致其他⽐较快的任务也得不到执⾏?很多应⽤系统⾥,为了避免这种情况,会随⼿创建线程池,最后导致系统⾥充塞了⼤的量的线程池,浪费资源。⽽ Elasticarch 的解决⽅案是分优先级的线程池。它默认创建了10多个线程池,按照不同的优先级以及不同的操作进⾏划分。然后提供了4种类型的线程池,不同的线程池使⽤不同的类型:
CACHED 最⼩为0,⽆上限,⽆队列(SynchronousQueue,没有缓冲buffer),有存活时间检测的线程池。通⽤的,希望能尽可能⽀撑的任务。
DIRECT 直接在调⽤者的线程⾥执⾏,其实这不算⼀种线程池⽅案,主要是为了代码逻辑上的统⼀⽽创造的⼀种线程类型。
FIXED 固定⼤⼩的线程池,带有缓冲队列。⽤于计算和IO的耗时波动较⼩的操作。
SCALING 有最⼩值,最⼤值的伸缩线程池,队列是基于LinkedTransferQueue 改造的实现,和java内置的Executors⽣成的伸缩线程池的区别是优先增加线程,增加到最⼤值后才会使⽤队列,和java内置的线程池规则相反。⽤于计算和IO耗时都不太稳定,需要限制系统承载最⼤任务上限的操作。
这种解决⽅案虽然要求每个⽤到线程池的地⽅都需要评估下执⾏成本以及应该⽤什么样的线程池,但好处是限制了线程池的泛滥,也缓解了不同类型的任务互相之间的影响。
脑洞时间
以后每篇分析架构的⽂章,我都最后会提⼏个和该系统相关的改进或者扩展的想法,称为脑洞时间,作为⼀种锻炼。不过只提供想法,不深⼊分析可⾏性以及实现。
1. ⽀持shard-spliting
这个被⼈吐糟了好长时间,官⽅就是不愿意提供。我简单构想了下,感觉实现这个应该也不复杂。⼀种实现⽅式是按照传统的数据库sharding机制,1分2,2分4,4分8等,主要扩展点在数据迁移以及routing的机制上。但这种⽅式没办法实现1分3,3分5,这样的sharding。另外⼀个办法就是基于当前官⽅推荐的重建索引的机制,只是对外封装成resharding的接⼝,先给旧索引创建别名,客户端通过别名访问索引,然后设定新索引的sharding数⽬,后台创建新的索引,倒数据,等数据追上的时候,切换别名,进⾏完整性检查,这样整个resharding的机制可以⾃动化了。
2. ⽀持mapreduce
认为Elasticarch 可以借鉴 Mongo 的轻量mapreduce机制,这样可以⽀持更丰富的聚合查询。
3. ⽀持语⾳以及图⽚检索
当前做语⾳和图⽚识别的库或者服务的开发者可以提供⼀个 Elasticarch 插件,把语⾳以及图⽚转换成⽂本进⾏索引查询,应⽤场景应该也不少。
4. ⽤ForkJoinPool来替代 Elasticarch 当前的线程池⽅案脑筋
ForkJoinPool加上java8的CompletableFuture,⼀定程度上可以模拟coroutine效果,再加上最新版本的netty内部已经默认⽤了ForkJoinPool,Elasticarch 这种任务有需要拆⼦任务的场景,很适合使⽤ForkJoinPool。
Elasticarch 的开源产品启⽰
还记得10年前在⼤学时候捣⿎ Lucene,弄校园内搜索,还弄了个基于词典的分词⼯具。毕业后第⼀份⼯作也是⽤ Lucene 做站内搜索。当时搭建的服务和 Elasticarch 类似,提供更新和管理索引的api给业务程序,当然没有 Elasticarch 这么强⼤。当时是有想过做类似的⼀个开源产品的,后来发现apache已经出了 Solr(2004年的时候就创建了,2008年1.3发布,已经相对成熟),感觉应该没啥机会了。但 Elasticarch 硬是在这种情况下成长起来了(10年创建,14年才发布1.0)。 ⼆者的功能以及性能⼏乎都不相上下(开始性能上有些差距,但 Solr 有改进,差不多追上了),参看⽂末⽐较链接。

本文发布于:2023-06-04 07:35:30,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/857489.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:节点   集群   数据   线程   服务   时候
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图