【GoDance搜索引擎】搜索引擎集群模块实现笔记

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【GoDance搜索引擎】搜索引擎集群模块实现笔记

正文

分布式一致如何去做?

当时有两种方式实现共识算法,一种是ES的默认发现模块(采用Gossip算法),一种采用强一致性的craft算法。

写设计方案的时候一开始是参照ES的来做的,但是发现了一些问题,网上也说明了一些问题,最后决定改用已经证明过正确性的Raft算法来实现分布式一致。


超时RPC如何去做?

利用管道和定时器,但是有gorutine泄露的问题,这让我想到能不能主动关闭gorutine,查询了资料后得到的答案是不能,这确实是合乎常理的


在 Go issues 中也有人提过类似问题,Dave Cheney 给出了一些思考:


如果一个 goroutine 被强行停止了,它所拥有的资源会发生什么?堆栈被解开了吗?defer 是否被执行?

如果执行 defer,该 goroutine 可能可以继续无限期地生存下去。

如果不执行 defer,该 goroutine 原本的应用程序系统设计逻辑将会被破坏,这肯定不合理。

如果允许强制停止 goroutine,是要释放所有东西,还是直接把它从调度器中踢出去,你想通过此解决什么问题?

所以gorutine只能主动去退出


停止 Goroutine 有几种方法? - 侃豺小哥 - 博客园 (cnblogs.com)


否定了这种方案后,我在网上又找到了其他思路,RPC通信基于底层网络通信,可以通过设置connection的读写超时时间,达到RPC读写超时的目的


翻了半天资料发现16年有一条冻结该包的issue


最后在他的标准库官方文档中发现,该包冻结不接受新功能,完美踩坑,我谢谢你!


w1.png


最终使用了rpcx


分包代码规范问题

我在我负责的集群模块下面分了好多模块,这造成了包管理的混乱(内部功能实现居然要将内部的用到的字段暴露)


网上搜索有关go分包的规范,发现这篇


Organize your code with Go packages — Master Tricks | by Inanc Gumus | Learn Go Programming


在我的cluster包下,不应该出现不同层次功能的包,要出现也应该是集群的不同实现的包


Keep your packages small

Put related packages into sub-directories

Create your packages as related clusters

Hide almost everything

Depend on things that don’t change often

Go has no sub-packages

Put tests into the same directory

Don’t put your code into ./src folders

Don’t export from the main

Smaller programs may not need many packages

Naming packages


Use simple names

Do not use double or more words in names

Let the package path speaks for what the package does

Don’t repeat the package names

Use common abbreviations

Avoid generic package names

Do not use underscores or camel-casing(Just use lowercase letters)

Note: Names can contain Unicode characters

几天后继续补充,由于之前是写Java的,所以很自然而然的就把Java那套给搬过来了。这里我就把模块和包弄混了,所以在一个项目里建了不同模块,实际上应该共用一个mod,这样有利于版本发布。而权限问题应该由包来解决


日志同步问题

由于我是用raft来实现分布式共识的,而在raft算法中日志同步是增量修改的,并不是修改状态。所以我需要对涉及到的操作进行定义,比如节点增加,增加的参数等等


元数据修改操作:


节点增加(还有分片信息)

节点删除(同时删除对应的分片)

分片增加

分片删除

GoDance是一款用go语言编写的分布式搜索引擎,同时也是一款分布式文档数据库。支持分布式搜索以及分布式存储功能,对外提供restful Api接口来操作GoDance。


GoDance整体采用主从架构,实现了Raft算法来保证元数据的一致,并有一系列机制比如tranlog、分片存储、分片迁移、故障转移等来保证集群的高可用和高拓展性;在存储方面,利用了段的思想来提升搜索和插入的性能,同时支持正排索引(B+树)和倒排索引(BST和MAP)来提升搜索能力,并自己实现了相应的数据结构和Linux系统上的持久化机制(MMAP);在路由方面,根据索引内存负载率和机器配置会优先路由效率高的分片节点,同时使用了rpcx来进行rpc调用;在搜索方面,实现了TF-IDF等搜索算法,并利用其实现了相关度搜索。


如何去抽象操作不同类型的日志(日志增加)

类型嵌入

空接口,实现多种类型的数据结构

空接口字段定义操作参数(强制转换,接口+反射)

使用1.18新加入的泛型

Go 1.18 泛型全面讲解:一篇讲清泛型的全部 - SegmentFault 思否


仔细想了想发现自己的这个场景还是不太一样的,无法用泛型


最终使用类型断言和反射解决问题


如何实现Raft的日志一致性

可以理解为二阶段提交,但是有个问题——当确定是否要提交之前(等待那个大多数),是否允许提交新的日志


经过一番推演,我的结论是要的,这样可以保证领导者的日志都是最终状态,有利于跟随者向领导者同步日志,同时也是为了保证日志顺序的一致性


如何设计内存中的状态日志的数据结构

需求:


日志是一项一项递增的

日志存在未提交和提交两种状态,提交的数据需要更新到集群中的状态机中

Leader的探活请求会携带当前最大的提交日志,跟随者需要根据此来提交之前的

节点删除策略

非主节点直接移除,非主节点保留该数据结构,修改其状态为Dead


go语言没有子包的概念,有点伤,自己写模块的时候会有点乱


go模块依赖问题

今天一早发现项目依赖爆红,跟我说reading tree note: note has no verifiable signatures


验证签名失败,搜了半天,大概明白是mod机制的验证问题,最后受不了了,干脆把pkg依赖全删了重新导,最后成功解决


后来搜索资料发现



w2.png

大概是我某个依赖包被无意篡改了,或者官方同个版本的依赖包更新,hash变了。总之就是校验失败了


go的程序初始化问题

因为我负责的集群模块依赖配置模块的导入,所以在编写init方法是有一些顾虑


搜了下才发现


golang程序初始化先于main函数执行,由runtime进行初始化,初始化顺序如下:


初始化导入的包(包的初始化顺序并不是按导入顺序(“从上到下”)执行的,runtime需要解析包依赖关系,没有依赖的包最先初始化,与变量初始化依赖关系类似,参见golang变量的初始化);

初始化包作用域的变量(该作用域的变量的初始化也并非按照“从上到下、从左到右”的顺序,runtime解析变量依赖关系,没有依赖的变量最先初始化,参见golang变量的初始化);

执行包的init函数;

所以为了达到效果,config包尽量不要依赖于其他包


节点间自动发现的初始机制

raft算法解决的是分布式共识问题,利用一系列机制来确保日志的一致性。而最开始的节点发现问题并没有解决,所以我需要设计一种机制来促使初始节点的自动发现。


这里总体借鉴ES的设计思路,将集群节点分为Master节点和Data节点


Master节点可以参与Leader的竞选,Data节点主要参与数据存储,一个节点既可以是Master节点,也可以是Data节点,这可以在配置文件中cluster.master 和cluster.Data 进行配置。


为了使自动发现成为可能,我们加入了种子节点的概念,种子节点可以通过cluster.seedNodes进行配置,它是一个数组,每个字符串代表了一个可访问的节点地址。


在节点启动时,开启心跳检查,如果发现自己不在集群中,会向种子节点们发送rpc查询,获取当前的Leader节点地址。如果有Leader节点地址,说明现在已经有Leader,则向该Leader节点发JoinLeader RPC调用;否则则根据自己是否是Master节点来决定自己是否要参与竞选还是不断向种子节点获取当前Leader的地址。


PS:这里要注意就算是参与竞选也不能无脑一直参与,而是得判断自己是否在集群当中,如果不在,则意味着自己根本不在集群,人家根本不知道当前节点存在,所以在这种情况下,竞选失败需要尝试tryJoin的逻辑


成功选举,但是种子节点发送失败,这就意味着后期其他节点节点加入时,Leader永远是无法被发现

新增一个节点但是该节点没有在clients中就无法发送心跳

Leader删除一个节点同时需要把对应client给删除,同时sendHeart需要判定当前节点是否存在nodes中,如果不存在,则停止发送信息


集群模块的测试问题

在完成了集群模块的功能后,发现测试成了一个问题。


它不像普通的程序那样简单的测试方法,它主要有以下难点:


集群模块需要多台机器进行运行测试

集群模块中涉及大量的并发编程和网络调用

Raft算法问题思考

由于Raft算法比较复杂,看论文看的晕乎乎的,看网上资料质量又参差不齐,所以在理解这个算法的时候踩了很多坑。其中一个就是term对于选举的影响,当时我忽略了在拉票请求中节点无论投不投票都会更新为更大的term。所以在后面思考问题的时候绕了进去,发现了算法不成立的地方(其中一个节点因为网络延时开始竞选,但此时其他节点又接受到Leader的日志更新,根据不给日志完整性低的节点投票的原则,当前节点陷入无限选举的情况),当时还想着能不能竞选失败去重新尝试加入主节点等其他方案。


明白了term更新的机制也就解开了疑惑,但随之而来还有一些问题,当一个Follower节点仅仅因为网络延时导致开始竞选,那么必然会导致新一轮的竞选,因为term变大的,尽管这个Leader可能不是那个网络时延的节点,但肯定是最完整的节点,这样容易在


raft算法的局限: 1、强领导模型对于写功能基本退化单机性能,量大任然会出现性能瓶颈,适得其反。 2、选举期间会集群将出现短暂不可用现象,影响时长与选举时间相关。


日志完整性问题

如果新领导者不包含这部分日志,这部分日志会覆盖,即“以领导者日志为准,实现各节点日志的一致”,需要我们注意的是,复制到大多数节点的日志项,是不会丢失和改变的,而只被成功复制到少数节点的日志项,可能会被覆盖,也可能最终会被提交。


这里的大多数并不保证提交的大多数,所以在判断日志完整性时,我们不该以Committed的日志Id为标准,而应该以最新插入的日志Id为标准。


这里为了需要有个机制来保证新领导者有大多数的committed:


领导者选举能保证新领导者一定包含这条committed的日志项,但这条日志项在新领导者上处于未提交状态,这时新领导者,会尝试从最新日志项开始,向其他节点复制日志,如果,某条uncommitted的日志项,被发现已经成功复制到大多数节点上,这时这条日志项将处于提交状态,并被应用到状态机,通知其他节点提交这条日志。


此时集群会短暂不可用


如何理解“大多数”

集群配置不是随时变化的,需要按照一定的算法,比如联合共识、单节点变更,来添加和移除节点,也就是集群当前的节点数是已知的


采用哪种数据分片方式?

hash


一致性hash


range hash


function hash+range hash


w1.png


最终还是参考MongoDB的分片方案——hashfunction+ hash range


分布式ID如何生成?

暂时采用UUID的解决方案


特征值如何选定?

特征值由用户选定


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
开发框架 监控 搜索推荐
GoFly快速开发框架集成ZincSearch全文搜索引擎 - Elasticsearch轻量级替代为ZincSearch全文搜索引擎
本文介绍了在项目开发中使用ZincSearch作为全文搜索引擎的优势,包括其轻量级、易于安装和使用、资源占用低等特点,以及如何在GoFly快速开发框架中集成和使用ZincSearch,提供了详细的开发文档和实例代码,帮助开发者高效地实现搜索功能。
202 0
|
7月前
|
SQL 搜索推荐 数据库
分布式搜索引擎_学习笔记_3
分布式搜索引擎_学习笔记_3
41 1
|
存储 搜索推荐 数据挖掘
深入探索Elasticsearch搜索引擎:高效搜索和分析的利器
在现代信息时代,数据量爆炸式增长,如何高效地搜索、分析和检索数据成为了一个重要的挑战。Elasticsearch作为一款分布式、实时搜索和分析引擎,为我们提供了强大的解决方案。本文将深入探讨Elasticsearch的基本概念、特点,以及如何在实际应用中应用它来实现高效的搜索和分析。
134 1
|
数据采集 搜索推荐 安全
谷歌搜索留痕快速收录怎么实现?
答案是:通过GPC爬虫池技术实现的。 在搜索引擎优化(SEO)领域,快速收录是许多网站主人追求的目标。 而在谷歌搜索引擎中,搜索留痕快速收录成为了一种重要的实现途径。 以下内容详细介绍了如何实现谷歌搜索留痕快速收录。
165 0
谷歌搜索留痕快速收录怎么实现?
|
搜索推荐 关系型数据库 MySQL
利用Xunsearch搭建搜索引擎、内容搜索实战
利用Xunsearch搭建搜索引擎、内容搜索实战
331 0
利用Xunsearch搭建搜索引擎、内容搜索实战
|
存储 缓存 搜索推荐
郁金香搜索引擎的方案
先介绍学心理学的时候记住的两个把妹秘籍:   1>巴甫洛夫把妹法:巴甫洛夫的狗的反射试验上学的时候大家都应该学过,天天给狗喂食的时候摇铃,后来不喂食只摇铃狗还是分泌唾液。应用到把妹这个非常有实际意义的事情上面就是:每天给妹子送早晨,等人家形成了习惯,突然不送了,人家就开始觉得不自在了,开始各种想这个男孩纸~~   2>吊桥效应:在吊桥上,由于危险的情境,人们会不自觉地心跳加快,错把由这种情境引起的心跳加快理解为对方使自己心动,才产生的生理反应,故而对对方滋生出爱情的情愫。   心理学是门很实用的学问吧[偷笑][偷笑]。
郁金香搜索引擎的方案
|
存储 自然语言处理 搜索推荐
|
存储 数据采集 自然语言处理
怎么快速的让网站被收录?搜索引擎的工作原理
要想在搜索引擎中有好的排名表现,网站收录是基础。另一方面,页面收录的数量级也代表了网站的整体质量。在我看来,要想收录百度网站,首先要了解搜索引擎的工作原理,这样才能迎合搜索规则,让网站收录达到理想状态。
怎么快速的让网站被收录?搜索引擎的工作原理
|
自然语言处理 搜索推荐 算法
深入搜索引擎原理
之前几段工作经历都与搜索有关,现在也有业务在用搜索,对搜索引擎做一个原理性的分享,包括搜索的一系列核心数据结构和算法,尽量覆盖搜索引擎的核心原理,但不涉及数据挖掘、NLP等。文章有点长,多多指点~~ # 一、搜索引擎引题 ## 搜索引擎是什么? 这里有个概念需要提一下。
7897 1
|
数据采集 消息中间件 前端开发
分布式爬虫和搜索的设计与实现
爬取网站,采用流程节点,用来处理摘要计算、关键字计算、相似度计算、热度计算。数据经过流程计算以后,落库,建立倒排索引。搜索根据关键词到倒排索引表可以快速搜索。 实现步骤 1.基础工作:收集一些网址,作为爬虫的入口。种子url表结构: { “_id” : ObjectId(“c54c4352310b3c”), “urlId” : “io563784uiodf7e96bb9i
1560 0