> 技术文档 > 关于ElasticSearch以及ES和数据库同步问题_es数据同步

关于ElasticSearch以及ES和数据库同步问题_es数据同步


ElasticSearch

GPT:ElasticSearch是一个基于Lucene的分布式、高性能全文搜索和分析引擎,常被用于处理大规模结构化和非结构化数据。它具备强大的搜索、分析、聚合能力,广泛应用于日志分析、监控系统、商品搜索引擎等场景。
ES提供RESTful API接口,使用简单,支持强大的DSL查询语言,可以精细控制搜索逻辑。Kibana是Elastic官方提供的一个数据可视化工具,内置了一个功能叫Dev Tools,其中的Console控制台可以直接模拟API请求,执行增删查改、搜索聚合等操作,适合开发调试。
es负责数据的端口是9200,kibana的是5601
java代码上,可以继承一个ES提供的RsRepository,他继承了CrudRepository,跟mybatisplus一样已经提供了基础的增删改查。

public interface QuestionRepository extends ElasticsearchRepository<Question, String> {}

也可以用Spring提供的操作es的客户端对象EsRestTemplate,类似JDBC的Template模式。。都说前面那个更简单,后面这个更全能,其实我觉得后面这个更方便。

@Autowiredprivate ElasticsearchRestTemplate restTemplate;

ES的索引机制

ElasticSearch并不是遍历所有文档进行搜索,而是通过倒排索引的结构实现高效文本检索。
比如:
假设我们有三句话:

文档1:我 爱 编程文档2:我 爱 Java文档3:编程 真 有趣

普通的“正排索引”是按文档找词:

id1:文档1:我,爱,编程id2:文档2:我,爱,Javaid3:文档3:编程,真,有趣

要找有“编程”的文档,就遍历全部的文档的全部词去对比。
而倒排索引是按词找文档:

id1:我:文档1, 文档2 id2:爱:文档1, 文档2 id3:编程:文档1, 文档3 id4:Java:文档2 id5:真:文档3 id6:有趣:文档3 

要找有“编程”的文档,就直接找“编程”就知道有哪些文档里有了。

关于使用ES替代mysql存储
—ES比mysql更灵活,他的分词搜索比mysql的模糊查询更灵活。比如像搜索一个方案设计,mysql的模糊搜索就必须有连续的“方案设计”四个字,而不是“方案的设计”这样的。
—当然mysql也有全文索引的功能,他也是倒排索引,但是他的分词方式特别不行,就是他只能两个字两个字的固定分词,比如说固定分词这四个字,他就分成固定和分词就没了,相比之下ES的分词就灵活多了,ES虽然默认不支持中文,但是也有很多插件,比如加上ik分词器。然后mysql的还不支持高亮搜索,es的可以。而且es更快。

mysql其实也有基于倒排索引的全文索引,为什么还是要用ES?
—主要是因为mysql的全文索引的分词效果不好,好像只能两个字两个字的分词,而es有丰富的分词器,比如ik分词器,有smart和xx
—还有mysql的全文索引不支持搜索结果高亮
—如果一个列有全文索引,搜索的时候就一定会用,即使有性能更好的索引也不会用


ES的分词机制

最常用的就是IK分词器。IK是社区维护的中文分词插件,安装后可用于 ES 分词。
有两种模式:

模式 分词方式 示例 ik_smart 智能分词,粒度大 “好学生” → “好学生” ik_max_word 最细粒度,词全列出 “好学生” → “好学生”, “好学”, “学生”

—建立索引的时候应该让索引多一点,就用ik_max_word;
—而用户输入查询的时候应该智能一点,就用ik_smart。
—在业务上也有说法,比如说一个题库项目,在管理端添加题目的时候建议题目标题添加上与这个题目强关联的词,就算这个词很普遍可以省略也不要省略。比如标题:解释java中的hashmap。这个题目要是标题里面不加java,就叫解释hashmap也是完整的,但是这样es分词之后,搜索java就搜不出来这个题目了。


关于与数据库同步

存在ES的数据一般在数据库也得存一份,或者说ES存的东西就是从数据库抽出来的。那么数据库更新或者ES更新,这两者怎么保持同步。
分两步,一个是全量同步,一个是增量同步。
全量同步是最开始的时候,es还是空的,把需要的全部从数据库抽过来。
可以用Springboot提供的一个单次任务的机制,只要让一个Component实现了CommandLineRunner接口,重写他的run方法,启动的时候就会执行一次了,下次启动的时候把这个类的Component注解注释掉以后就不会重复执行了。全量同步的时候是过滤了isDelete的数据的。但是逻辑删除的对这个系统来说能看作是物理删除了,所以这里不会有数据不一致。
增量同步就是数据库更新的时候es跟着变。
因为每次变es会导致它的索引也变,在CRUD比较频繁的场景,每次都要动es是比较花时间的。可以利用updateTime这个字段,每分钟一次定时任务,在mysql里查updateTime在最近5分钟内的题目信息,(查询的时间一定要定时任务间隔的两倍以上)save到es就行了,es的保存和修改都是save,批量就saveall。删除是用了isDelete字段逻辑删除,所以mysql是什么样的就同步给es什么样的就行了。所以后面es里也会有逻辑删除的。逻辑删除的数据可以借鉴redis的过期删除的策略。把过期最久的删掉。(删除的时候也会更新updateTime)

动静分离存储
ES不适合频繁更新字段,对于点赞数这种经常变的动态数据,es没有存,要查的话可以先从es里面查到主键信息,再去mysql里面查。

增量同步优化
因为项目是分布式的,一个服务开了多个实例,这个增量同步的定时任务就可能出现线程安全问题,不过其实最多也就是覆盖,没有数据不一致的问题。但是为了不重复浪费性能,也可以加锁,用redis做简单的分布式锁,防止重复执行定时任务,只有拿到锁的机器可以执行定时任务,其他获取锁失败的也不阻塞也不重试,自动放弃就行了。

有什么别的更好的方法嘛?
Canal + Binlog 实现实时同步
可以用mysql提供的binlog日志搭配阿里巴巴开源的canal项目实现从mysql到es的实时边界推送。
Canal是阿里巴巴开源的binlog增量订阅和消费组件,可实现MySQL → ES的准实时数据同步。
工作原理:
1、Canal Server伪装成MySQL从节点。
2、实时监听主库的 binlog(数据库变更日志)。
3、解析binlog中的INSERT/UPDATE/DELETE事件。
4、推送给Canal Client,写入ES/Kafka/Redis等目标端。
—它核心逻辑就是靠binlog把数据收取到任意的目标存储里。它包括服务端和客户端,服务端就是canal把自己伪装成Mysql的一个从节点,并从主节点获取binlog,解析和存贮之后拱客户端使用,它会维护一个消息队列,客户端就能订阅消息队列,获取数据变更,就可以存储到自己的数据仓库里。他这个就不需要我定时任务来自己做了,不需要再去自己读数据库,数据转换,写入es,他自己就会实现了,肯定会快很多。