需求背景
项目中有一个页面,需要在编辑后原地刷新页面。也就是编辑信息,点保存之后,前端会自动刷新页面。
技术方案
- 选择es作为查询数据源。
如果使用mysql作为查询数据源,需要join 3个表,而且where条件非常复杂,而且条件包含姓名左右模糊搜索(like '%xxx%'),性能肯定很差。
因此选择es作为查询数据源
- 点击保存之后,保存接口先把数据存到mysql,再推到es。
推es的方案,选择用RestHighLevelClient手动双写,而不是binlog。
因为一个es索引对应mysql多张表,如果用binlog,很难保证数据完整性。
另外,我们的写操作入口比较少,手动双写工作量不是很大。
- 保存操作完成之后,发布一个事件,监听器监听到事件后,异步把数据推送es。
1). 用spring的事务管理器,控制发布事件的时机在保存操作事务结束后,否则监听器可能查不到最新数据。
2). 发布事件的技术选型
- 如果发布器跟监听器在同一个微服务,可以用内存事件(java提供了观察者模式的api,参考java.util.EventListener)
- 如果发布器跟监听器在不同微服务,可以用mq
3). 用本地消息表保证事件at least once语义。
- 在保存操作的事务里,向本地消息表插入一行事件,状态初始化为init
- 监听器的逻辑,如果执行成功,把本地消息表的时间状态改成executed_success
- 监听器的逻辑,如果执行失败,把本地消息表的时间状态改成executed_fail
- 如果保存操作的事务成功了,那么事件在本地消息表肯定存在记录,增加一个job定时扫描本地消息表中状态为非init的记录,并重试。
4). 如果使用mq发布事件,如何保证消息一定发送成功?
- 如果mq支持事务消息(比如rocketmq),可以使用事务消息
- 如果mq不支持事务消息,可以用本地消息表
- 监听器的逻辑,大约需要1.5s,也就是说,保存操作的请求200后,还有约1.5s的数据延迟,如果这1.5s内,前端自动刷新页面,拿到的数据是不准的。
1). 使用RestHighLevelClient把数据推到es本质上是一个http请求,请求200,不代表es可以马上查询到数据。
2). 数据推到es后,数据只是存放在内存,等待一段时间,才会写入segment,然后才可以搜索到。
3). 这个时间间隔由es服务端的refresh_interval参数控制,这个参数默认是1s,SRE要求最低也是1s,否则可能把集群打挂。另外,客户端也可以设置写入索引后的刷新策略,RefreshPolicy.IMMEDIATE表示立即刷新。
4). 写入segment,相当于建立倒排索引,一个倒排索引分成多个segment。
5). 监听器的逻辑,有3部分耗时,这3部分耗时合计,绝大部分情况下在1.5s内
- 发送http请求,把数据推送es
- 1s的时间间隔,数据才写入segment
- 写入segment本身有耗时
es数据延迟解决方案
方案 | 技术实现 | 优点 | 缺点 |
---|---|---|---|
方案一 | 用户操作完成后,马上刷新页面,查es | 前后端实现简单,后续运维成本低 | 数据有延迟,产品不接受 |
方案二 | 用户操作完成后,前端sleep 1.5s后再刷新页面,查es | 后端实现简单 | 涉及的接口较多(当前页面有5个写接口,1个读接口),前端实现复杂 |
方案三 | 从es捞id,其他信息从mysql查 | 查询es的筛选条件,本身就是有延迟的字段,因此从es捞出的id是不对的 | |
方案四 | 保存接口执行完业务逻辑后,往redis set一个key,表示当前登录用户执行了保存操作,ttl设置为1.5s;查询接口执行业务逻辑前,判断key是否存在,若存在,从mysql查询,否则从es查询 | 可以解决数据延迟 | 需要维护mysql与es两套查询逻辑,日后需求有变更,两套方案都要改 |
方案无 | 保存接口执行完业务逻辑后,往redis set一个key,表示当前登录用户执行了保存操作,ttl设置为1.5s;查询接口执行业务逻辑前,判断key是否存在,若存在,sleep 1.5s | 可以解决数据延迟 | 保存接口与查询接口没有解耦,查询接口需要关注保存接口的逻辑 |
方案六 | 查询接口加一个isReadAfterWrite字段,标识本次请求是否是用户操作完成后的查询。如果isReadAfterWrite=true,后端sleep 1.5s,再查es;如果isReadAfterWrite=false,正常执行逻辑 | 可以解决数据延迟 |
综合考虑后,选择方案六
思考
- 为了保障数据一致性,就要牺牲部分耗时
- 接口的耗时不用追求绝对的最优,要在保障业务逻辑的可用性前提下,尽量耗时短
- 如果跟业务逻辑可用性冲突时,top1需要保障的是业务逻辑的可用性
- 如果一个页面需要查es,筛选条件应该是不可变的字段,比如姓名、国籍这种,这样才可以用es捞id,其他信息从mysql查