京东逛一逛项目是我们在去年孵化打造的一款基于UGC体系的图文内容产品,由于开发初期时间紧、任务重,我们采用了敏捷开发的模式,进行了快速开发、快速迭代,在初期很好的满足开发任务,也确保了项目如期交付和上线,同时也保证了线上平稳运行。
敏捷开发模式大大减少了我们项目的开发上线时间,但是随着需求产品需求的进一步深化,我们不得不采取了很多折衷的方案来满足需求,这些方案虽然满足了快速上线的要求,但是系统整体的架构设计上不可避免的遗留了部分历史债。
随着用户发布晒单数据量的累积,用户发布的文字内容从十万级别达到了百万级别,内容关联的图片、视频、商品信息也早达到了千万级别,这时候运营查询时开始出现了慢查询、关键字查询失效(因为查询时间过长被JED弹性库kill掉)的问题。
这些问题在项目架构设计时也预料到,但是没想到数据量膨胀的比意料中要快,也没想到问题出现的这么快,因此我们着手开始进行系统性的优化。
在数据存储方面,我们选用了JED弹性数据库作为存储库,HIVE则作为T+1的离线数据库,JIMDB作为缓存数据库。
对于前台场景,基本上都是按照特定的属性查询、聚合数据,比如说直接根据晒单的主键ID查询,根据用户Pin查询数据,以及一些业务场景下的数据直接存储到JIMDB中,只需要针对这些属性建立组合索引,是完全可以应对千万级别数据的。
但是对于运营后台,需要支持任意属性组合查询、联表查询、关键字模糊查询,这时对于底层存储引擎基于Mysql的JED来说,在千万级别的数据量查询上就会心有余而力不足,当然这也是SQL型数据库不擅长的范畴。同时因为基于ShardingSphere实现分库的JED弹性库对于一些复杂SQL支持的并不完全,因此很多产品侧提出的查询能力无法实现。
在项目初始的架构设计方面,我们拆分成了三个系统:SOA服务层、通用能力组件和CMS运营管理系统。
SOA服务层:主要提供业务相关的服务能力
通用能力组件:提供存储、查询能力,同时对其他模块提供服务,主要通过JSF接口和JMQ消息提供服务
CMS运营管理系统:针对运营人员,提供管理、审核、查询能力
项目简单架构如下图:
通用能力组件和其他两个系统,主要通过杰夫接口进行RPC通信。出现查询问题的是CMS运营系统,但其实查询的瓶颈是在数据库查询阶段,准确的说是对于Mysql的查询出现了大量的查询超时、慢查询问题,从而引发了CMS运营系统的查询返回失败。
经过调研和对比不同组件,我们决定引入Elasticsearch作为项目的查询引擎,以满足运营同学对于关键词查询、多条件组合查询的要求,同时也可以解决JED弹性库由于分库分表导致联表查询不支持、复杂SQL不支持的情况。
首先需要改造的是数据写入流程,经过调研Elasticsearch7.5版本的写入TPS可以达到7w左右,这种优异的写入性能完全可以满足我们系统的数据写入。基于此,我们对系统的写入做出了如下改造:
1. 原有写入接口改造,JED数据库写入成功以后,写入ES(但不要求必须成功)。
2. 新增定时任务,定时同步昨日增量数据到ES,延时保证数据库和ES数据一致性。
3. 写入接口改造上线后,当前存量数据一次性从JED弹性库写入ES,防止数据遗漏。
这样设计和我们的业务形态也有一定关系,首先我们不会要求数据库数据和ES索引数据的强一致性,因为不涉及交易流程,同时ES索引数据只提供运营人员查询、审核时使用,其次对于ES的使用,我们更倾向于以一种查询引擎的方式织入系统中,而不是作为一种数据存储源(虽然ES在读写方面的性能非常强大,但其并不拥有SQL型数据库的ACID性质)。
系统原有查询架构如下图,CMS运营后台通过查询入参,传输到通用能力组件,将查询入参转换为系统内查询器,然后转化为动态SQL交给Mybatis进行查询数据库。
这里的查询入参和查询器是我们为了满足CMS多条件组合查询,设计的一套转换适配流程,支持调用方根据我们提供的查询条件进行组合调用,对于不同的字段,限定了不同的查询能力,比如说对于字符串类型的字段A,提供”等于“的精准查询和Like模糊查询,对于整数类型的数字B,则可以提供大于小于和Between区间查询。
通过查询器内部的代码逻辑,将不同的查询入参转化为不同类型的动态SQL,最后生成完整的动态SQL交给Mybatis框架执行,简化流程如下图:
引入ES查询能力后,我们准备在原有的查询流程上进行改造,对于原本的查询入参不需要进行改动,而是基于查询器新增ES查询转换器,这样可以尽可能小的减少工程量,同时可以做到查询能力的平滑过渡,当然我们也做了开关可以直接控制查询数据源是选择Mysql还是ES。
对于ES查询转换器,其实就是基于ES High Level REST Client提供的API进行封装,然后将查询入参进行适配转化为ES查询的DSL,合理利用Filter、Must和Must_Not查询提升效率,后续也可以支持”OR“条件转换为Should查询,极大的提升了查询能力。
改造以后,我们解决了因为Mysql对于模糊查询能力较弱导致的慢SQL问题,同时也提升了查询速度,解决了CMS运营系统使用中出现的问题,最重要的是,ES解决了JED弹性库对于联表查询、复杂SQL不支持的问题,为系统后续的持续改造和扩展打下了基础。
系统为了快速上线可以做出一些设计上的舍弃,但是核心的架构设计需要细心打磨,比如说本例中的查询入参&查询器的设计,当时我们也是耗费了比较多的精力在这块进行设计,这样才能做到本次引入ES查询引擎,我们只进行了单系统的内部改造,同时让下游系统无感知。
对于数据的增长速度要有一定的预估,特别是对于需要模糊查询、多条件查询的系统,还是要在架构设计时就引入能满足其增长要求的数据库、查询引擎。吸取了本次改造的经验和教训,希望我们在日后工作中对于架构设计会考虑的更加周全。