巧用双索引避免es出现索引不存在的问题
问题说明:
企业级门户APP,有移动端组织架构,员工可以在app端查询公司用户信息,支持员工在通讯录中多字段搜索(姓名/工号/手机号/邮箱/......),模糊搜索。我们这边是使用elasticsearch来存储员工信息,
以便于实现这种搜索功能。
正常把员工信息导入到es中,搜索也不会有问题。但是我们的产品是个企业级产品,专门服务于不同的企业客户的,解决客户各式各样的需求。比如,正常员工的字段有姓名/工号手机号/邮箱/性别等,这也是我们产品中标准的字段,无法满足客户的个性化需求,例如客户需求:
- 客户A:我们有职级字段,怎么办
- 客户B:我们有昵称字段,怎么办
- 客户C:我们有爱好字段,怎么办
- ...........
如上所述,不同企业有不同的字段,为了满足客户可以自定义属于他们自己的字段,我们设计了个性化字段
的功能。
个性化字段:企业可以在自己的租户下自定义此租户下员工字段,增加代码的灵活性(这个设计以后有机会在写一篇)
在个性化字段中,可以控制某些字段是否能够在es中搜索,要支持es中搜索,那么就需要把字段初始化在es的索引中,当添加字段的时候就会到对应索引中增加字段。为了防止意外,我们提供了web端操作的界面,管理员可以手动重置整个租户的索引以及数据。在重建重新初始化期间,如果恰好用户正在使用搜索功能,可能会出现索引不存在
或者员工搜索不到数据
等问题。
本文就是来说明下在es初始化期间怎么保证数据能够正常访问,方案:以空间换时间,引入双索引的来处理
思路来源:在看《redis设计与实现》一书的时候,其中说明redis对字典的哈希表执行rehash的时候就是有两个hash表交换处理的,分别是ht[0]和ht[1]
双索引设计
系统中默认两个索引index0和index1,如果正在提供服务的索引是index0 ,那么在下次初始化重建的时候就会初始化index1, 待初始化完成后则index1对外提供服务,删除索引index0。如此反复循环。
1. 名词解释
下面单独是说明0租户下的索引设计,多租户下区分各个redis key和es索引即可
-
elasticsearch中有两个索引,一个index0 (员工索引0,默认),另一个index1(员工索引1)
-
ajisun:elastic:employee:init0(有过期时间的单key,判断此租户是否正在初始化中,)
-
ajisun:elastic:employee,0(hash key,存储0租户正在使用的索引)
2. 索引重建步骤:
-
通过 redis key
ajisun:elastic:employee:init0
来判断当前租户有没有正在初始化中,如果有,则忽略此次操作,如果没有初始化,请看第2步。 -
通过redis hash key
ajisun:elastic:employee,{tenantId}
获取当前租户使用的es索引=oldIndex -
如果oldIndex为空,就在redis中设置租户默认索引redis.hshPut("ajisun:elastic:employee", 0, "index0"), 那么即将初始化重建的新索引newIndex是 “index1”
-
如果oldIndex不为空,那么当前正在提供服务的索引是oldIndex,即将要初始化重建的新索引newIndex是非oldIndex(如果oldIndex是
index0
,那非oldIndex是index1
,反之一样) -
然后设置此租户索引正在初始化的key redis.strSet("ajisun:elastic:employee:init0", newIndex, 5L, TimeUnit.MINUTES); 加过期时间防止应用奔溃,导致状态是一直是初始化中
-
初始化新的索引newIndex,添加数据.
-
删除初始化状态的redis key,redis.delKey("ajisun:elastic:employee:init0");
-
改变redis中租户默认索引为newIndex, redis.hshPut("ajisun:elastic:employee",0, newIndex);
-
删除elasticsearch中的旧索引oldIndex。
流程图如下:
3. 数据搜索
搜索数据的就比较简单,直接在redis.hshGet("ajisun:elastic:employee",0) 中查找到0租户目前正在使用索引,如果为空则使用默认的索引index0
搜索。
4. 数据修改/存储
主要就是如果索引在初始化中,则直接把数据的修改/存储 操作新的索引上
-
redis.hshGet("ajisun:elastic:employee",0) 中获取0租户正在使用的索引。
-
如果为空默认索引是index0
-
如果索引不为空,则获取redis缓存中redis.strGet("ajisun:elastic:employee:init0") 0租户是否为空
-
如果不为空,则说明正在初始化中,则保存数据的索引使用初始化中的索引
以上就是针对es索引初始化期间影响正常使用的解决方案,其实没有难度的,只是提供一种思考方式,有问题欢迎提出交流。
关注不迷路
MySQL高级相关更多内容,如事务,锁,MVCC,读写分离,分库分表等还在持续更新中,欢迎关注催更。
我是阿纪,用输出倒逼输入而持续学习,持续分享技术系列文章,以及全网值得收藏好文,欢迎关注公众号,做一个持续成长的技术人。