京东到家CMS系统演变

CMS即为内容管理系统,管理京东到家APP和H5的页面的各个资源位(图片),点击图片后的跳转页面的展示。

本文主要从技术角度来介绍系统的演变。

背景

传统CMS系统配置即所见,后台配置了资源,前端就一定展示,用户在任何地方看到的都一样。

京东到家CMS最大的特色是跟地理位置有关系,简单说就是:用户在不同地点看到的只是最近相关的资源。京东到家是以门店为维度来控制资源的展示与否:一个资源A关联多个门店,每个门店有一定的覆盖范围(可以是圆形也可以是多边形覆盖),用户所在的位置有被资源A关联的门店覆盖到,那么资源A被用户看见。如下:用户被2个门店覆盖,如果资源A有关联这两个门店的任何一个,那么用户将看到资源A。

image

第一阶段–初创CMS

开始的时候跟很多系统一样,都面临着资源紧张,和业务快速发展的双重压力;为了尽快推出系统来满足业务上的需要,所以整体设计简单,在当时的访问量不多的情况下也能满足需求。如下图:

  1. 前端调用网关,网关从定位系统获取到当前用户周围的门店列表,传入CMS
  2. CMS接口模块直接调用数据库信息,对资源关联的门店与传入门店列表进行匹配,符合就返回。

image

由图可见CMS分为2个模块:

  • 运营管理后台 用于运营配置信息,配置完就保存到数据库
  • API接口模块:接收网关传入的参数,从数据库中获取信息进行计算返回
    问题:每一次网关的访问,CMS都会请求数据库N次,在大访问量下必然导致数据库连接不够用,导致数据不能正常返回。

第二阶段–整体架构升级

构思

为了解决第一阶段问题,所以第二阶段第一考虑的就是:数据存入缓存Jimdb中,CMS的API模块不访问数据库,将从Jimdb中获取数据返回。

接口端放弃数据库,同时也放弃了访问数据库带来的灵活性(可以多张表关联、可以用SQL灵活计算过滤数据),当时就遇见下面的问题:

问题

网关传入到CMS的参数:区域(城市编号)、平台(Android\iOS\H5对应编号)、版本、当前定位系统给的门店列表。用过Redis的都知道,缓存是key : value 的形式;传入key,获取到对应的value返回;如果我们可以设计key为这4个参数的组合:区域平台版本号_门店列表 ;value为其对应的值;那么网关传入参数,CMS的API就直接从缓存中获取返回,是在是太理想了。

但是从网关传入CMS的参数可以知道,前3个参数的组合是在一个可算和理解的范围,但最后的门店列表的组合是难以估算的大;因为门店列表是定位系统实时返回,定位点个数巨大,一个定位点不同时间返回的门店列表也可能不一样(门店的状态在变化)。

解决问题

业务飞速发展,数据库已经是瓶颈和风险点,所以升级迫在眉睫,当时解决方案:

  • 放弃门店列表作为key的想法,以 区域平台版本_资源类型编号 来作为key(资源类型分为几种:焦点图、入口图、活动图等等); value为对应的多个资源,缓存结构如下:
    image

  • API端根据传入的参数的前3个,加上资源类型编号,组装为key从Jimdb中首先获取到value; 这个value会对应多个资源,不是每个资源都是满足传入的门店列表的:进行每个资源关联的门店列表 跟传入的门店列表进行匹配,有交集的门店说明资源显示,反之过滤掉。

  • 由于上面的缓存结构带来的另一个问题是:运营设置的时候是一个一个资源进行设置,当这个资源被修改后,需要找到对应的缓存key(区域平台版本_资源类型), 进行重新计算这个key对应的多个资源; 所以缓存计算必然耗时。因此我们就新引入一个模块(缓存计算模块)来计算;运营在后端修改就只保存入数据库,通过MQ告知计算模块哪个资源被修改了,计算模块从数据库拉取最新数据进行计算。

系统架构图

image

  1. 由于计算模块计算量较大,为防止极端情况下对数据库影响,所以计算模块访问Mysql从库,运营设置后台web访问Mysql主库。
  2. Web端修改后直接保存数据库,用MQ告知计算模块,运营不用在后台等待缓存更新

此次升级上线后,成功的度过了京东到家的第一个618和店庆日。

总结:此次升级解决了接口端直接访问数据库的风险;接口端的性能也能基本满足。
但此次缓存结构决定了,每次接口访问需要从Jimdb中获取相当多无效数据,然后再过滤;随着时间业务发展带来的数据量不断增大,此处带来了接口性能问题日益凸显。

第二阶段–优化升级

1. 性能优化

缓存结构改进,采用2步缓存:门店->资源ID列表;资源ID->资源信息。结构如下:

image

当接口端得到网关传入的门店ID,第一步获取到门店ID对应的资源ID列表,第二步把资源ID队列作为key用Redis的mget一次性获取到对应资源列表信息;再进行必要的内存计算就返回。不再像第二阶段的时候会先获取大量无效数据来过滤;直接提高了接口性能。

当然有利也有弊:带来的是缓存计算模块复杂度增加;因为运营后台设置一个资源,关联了N个门店;缓存计算的时候就需要反向重新计算这个N个门店对应的资源;但带来的是接口端的性能提升,从实践看这点牺牲是值得的。

2. 灾备增强

接口端对Jimdb是强依赖,为了解决Jimdb这个单点;系统引入了ES,这样计算模块把缓存数据双写到Jimdb 和ES;当Jimdb出现问题,系统接口端直接可以切换到ES,继续提供服务。

系统架构图

image

系统接下来的构思:

随着公司的发展,会在越来越多的城市开通,数据量将会越来越大,带来的挑战也越来越大,未来的CMS将会:

  1. 整体数据将按区域进行区分:数据库分库、缓存集群各个区域独立:防止单一数据存储带来的数据量过大和性能问题,以及降低出现问题后对系统的影响。

  2. 目前系统有相当多的资源位需要运营人工去设置,城市越多,需要的运营也会越多,效率较低;以后系统会结合大数据,来分析一个页面的资源位应该怎么配置给用户带来更好的体验和指引。系统逐渐智能化,最终替换人工带来的成本和效率的低下。

总结

京东到家CMS系统为京东到家APP和H5首页、活动页等重要页面提供基于地理位置的信息,系统的重要性不言而喻。在正确返回数据的同时,对性能也是很高的要求;从项目启动开始,在不同阶段的下,对系统进行优化调整,保证了京东到家业务的飞速发展。本文作者也在系统的演变过程中不断尝试,获得成长。

参考

名词解释

  • JSF: 京东的高性能服务框架,类似Dubbo
  • JMQ: 京东的消息队列框架,类似ActiveMQ
  • Jimdb: 京东的分布式缓存与高速key/value存储服务,类似Redis
  • ES: 京东的Elasticsearch

更多关于达达技术的文章,敬请关注达达技术公众号。
技术公众号