一、需求背景
目前订单搜索是通过从solr里面换取订单id,然后再从数据库查询,这种方案随着订单量的增加,查询效率非常低下,并且有一部分查询条件solr里面还没有存储,进一步加大了数据库的查询压力,数据同步方案耦合在系统代码里面,效率低下。<br /> 随着订单量的增加,我们目前的搜索方案压力越来越大,超时情况越来越多,查询超时、导出超时,客户体验逐步下降,为了解决现有搜索存在的问题,决定迁移到es,将所有订单列表的搜索全部切换到es进行搜索,第一步先覆盖页面触发的所有订单列表的搜索,后续再逐步替换散落在代码里的一些列表的查询。<br /> 订单迁移es对交易中心至关重要,可以避免大部分sql超时,查询慢的问题,提示用户体验并且降低数据库压力,为后续更加复杂的搜索打下基础,甚至对其他应用后续接入提供了借鉴。
二、方案选择
初始计划:一开始计划的是线下采用canal同步工具,线上采用dts同步<br /> 在开发过程中由于需要关联多张表,并且是一对多的关系,当时备选方案有嵌套文档和父子文档两种形式,经过调研,canal支持父子文档形式,故按照父子文档形式设计了索引,按照父子文档查询方式可以同时查询出父文档和子文档,但是在准备上beta环境时发现dts不支持父子文档,为了验证查询效率,先使用的canal按照父子文档同步了数据,发现查询效率不能满足要求,相应时间无法降到100ms以内,最终放弃了父子文档形式的索引,改为单索引。<br /> 单索引创建又有两种方案
- 重新建一张表,按照需要的字段组成新的表,然后单表映射单个索引,这样就可以采用dts进行同步,但是表的初始化是个难题,由于需要多张表的部分字段组合成一个新的表,在不停机情况下,无法安全的将数据初始化,并且将新表的数据生成加入代码逻辑里面,这种方案风险过大,最终放弃。
采用多表聚合映射到一个索引的方式,这个方案经过咨询dts给出的结论是支持,但是存在数据丢失风险,到这里彻底放弃dts同步工具,决定采用canal进行同步
至此,同步方案和索引创建方案完全确定,采用多表组合成宽表,为了加快搜索速度,按照买家和卖家两个维度创建了两个索引,分别按照买家id和卖家id路由,这样查询就可以从一个分片查询,加快查询速度,通过索引别名方式进行访问,当需要切换索引时,只需要修改索引别名即可。
管理后台计划是按照日期路由分片,但是由于canal目前不支持,后续再改造canal支持这个功能,目前管理后台查询不路由,速度稍慢。
路由算法
shard = hash(routing) % number_of_primary_shards三、dts vs canal
dts
1. 基于Canal开源产品,获取数据库增量日志数据。 什么是Canal, 请点击
2. 典型管理系统架构,manager(web管理)+node(工作节点)
a. manager运行时推送同步配置到node节点
b. node节点将同步状态反馈到manager上
3. 基于zookeeper,解决分布式状态调度的,允许多node节点之间协同工作.
canal的实现原理
- canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议
- MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
canal 解析 binary log 对象(原始为 byte 流)
其实就是将自己伪装成从库,然后获取binlog日志,解析后使用 protobuf 3.0交互协议实现服务端与客户端的交互
但是在使用的过程中发现canal的一些不足,自己着手改造代码使之满足我们的业务场景。
- 路由问题:canal不支持路由功能,同步增加新的配置项并解析,支持了路由功能
- 全量同步翻页问题:由于全量时通过sql进行查询,默认的是按照offset方式进行翻页,当数据量大的时候,翻页到后面会出现慢sql,最终甚至无法查询,这里优化为按照id区间查询,避免慢sql
- 自动切片问题:这个是在触发全量的时候,为了加快同步速度,需要多线程进行处理,按照给定的id区间,查询到最小id和最大id(这里是为了避免给出的id有空档,导致分片不均匀),然后按照线程数平均分配,进行同步,为了防止一次报错导致线程停止,对异常catch,然后输出当前批次的id范围,通过日志单独处理这个id区间,避免一次出错需要全量同步的情况
- 同名字段更新问题:由于我们是通过join方式关联的多张表,会有一些同名字段,虽然已经取了别名,但是canal里面由于没有区分是哪张表的更新,导致将同名字段同时做了修改,这里通过判断更新的表,然后只更新对应表的字段,避免这个问题
- 告警机制:由于采用开源框架,不确定是否有缺陷或者其他可能会出现的问题,这里针对报错和延时时间做了告警,信息发送钉钉消息同步,并且加入了环境标签,控制通知是否开启
- 其他一些改动:增加一些关键日志,一些耗时统计等
四、数据安全保障
- 数据一致性:是上搜索一个必须要保证的问题,由于存在各种可能,包括程序bug、网络、服务器等各种存在问题的可能,首先一点是加入了各种通知,包括异常通知和延迟通知,及时发现同步失败或者是延迟比较大的数据,全量同步接口提供了按照id范围同步的功能,可以针对部分报错进行重新同步。还需要定期进行一次全量同步,防止数据丢失。
- 服务稳定性:稳定性包括了es服务器的稳定性和我们同步工具canal的稳定性,es服务器的稳定性的话,由于我们购买了阿里云的服务,有各种健全的监控和售后服务,一但出现问题可以及时得到处理,我们自己部署的canal服务这个需要运维同学的关注,也提供了服务的监控功能,能及时发现服务器问题。
数据延迟:数据从产生到能被搜索有几个延迟点,一条数据从产生到最后可以被搜索的过程:1、数据库事务提交后产生binlog日志;2、canal服务端收到binlog日志;3、canal客户端收到推送解析同步到es;4、es计算并存储刷盘;5、搜索。数据延迟比较敏感的是下单操作,用户下单后进入订单列表需要能够看到最新订单,我们将下单动作单独处理,下单用api直接写入es,其他的更新操作通过canal同步到es。
从数据产生到最后刷盘每个过程都会产生一定的延迟,目前统计得到的延迟时间大部分都是1s内,少数会延迟比较大。
目前一个尚未解决的问题:数据延迟,现在有少数数据延迟时间会比较长,延迟时间计算是通过当前服务器时间-es时间(binlog里记录变更发生的时间戳,精确到秒)得到,没有找到延迟过大的根本原因。
五、建议
针对整个开发过程遇到的种种问题,工具层面已经得到了解决,上面也列出了修改的地方,但是对于业务层面,由于订单索引是多表组合的一个索引,不是全部的字段,一开始整理的时候字段没有整理完整,缺少字段导致重新同步了几次索引,这个后面需要接es的同学吸取教训吧,如果不是表的所有字段都同步的话,一定要首先整理需要用的所有字段,多对几遍,防止出现纰漏。<br /> 字段类型问题:一个keyword和text的选择问题,如果是一个简单的字符串这种,不需要分词的就使用keyword类型,需要分词的使用text类型