引子
当前大多数互联网公司都是运营导向型(呃…少数技术、产品导向型的公司,运营也是“大比重”的一部分),高效灵活的运营方式在产品级上的最佳体现便是“千人千面”,这个意味着需要细分各个运营纬度(e.g. 城市,标签画像,天气…)配置运营策略。如何建立一个集合多纬度,稳定且高效的策略配置中心是每个运营团队建设的方向。策略配置中心配置层实现的方式有多中,一般分为业务垂直型和配置集成型,这两个从开发、维护、使用上各有利弊(这部分以后详聊吧)。但对于运营配置中心的数据层所面临的挑战大同小异,本文将工作中遇到的问题归纳总结后,针对数据层的实现与演进做了一些分析和探讨。
数据存储结构选择
对于多种类、多样式的运营配置,数据存储结构往往是一个难题,我们的选择是:抽象通用逻辑,例如城市、人群、生效时段等通用逻辑;个性数据JSON化,面对业务的迭代、类型的多样,想要用固定的字段很难满足简单可扩展的特性,选择JSON化可以支持可扩展结构。因此,在数据库中的存储变为了N(固定字段)+1(JSON字段)。
数据流转
为了后续我们讨论数据层存取的演进,我们先来看看数据在运营系统中是如何流转的:
- 基础数据由运营配置生成,支持增删改查操作;
- 运营对数据的操作,对应产生审批流,审批通过配置才能生效;
- 用户域使用数据查询生效数据,经过判断/过滤/脱敏等操作展示到C端用户;
基本数据架构实现
针对低频、小流量的业务,直接使用数据库不失为一种精简且可靠的实现、现在mysql集群的性能足以支持上千QPS的业务。此时运营/用户之间的数据,通过数据库中的配置状态(status)隔离,比如:
- 运营新配置未经审核的数据落库完成(status = 0, 未审核状态), 此时,C端服务查询不可得;
- 新配置审核通过(status = 1, 生效状态),此时,C端服务正常使用, 如下图仅 ID:2 可使用;
- 运营需要对服务下线处理(status = 2, 下线状态),此时,C端服务查询不可得;
传统数据架构实现
接下来让我们考虑这样一个场景: 我们的用户量不断增加,系统从QPS、响应耗时、机器成本等角度暴露稳定性问题,总之系统优化已经提上日程。优化的思路,简单点,我们就从两个方向考虑:1. 技术性优化,2. 业务角度优化。实际上,在业务系统里这两者是兄弟,谁也离不开谁。我们来看,目前系统的响应耗时主要来自于数据库查询,整个请求压力也在数据库层面。那运营平台的数据有什么特点呢?我们来看看运营的策略: 北京金卡会员在3月份可以享受2次免单优惠!嗯…太划算了吧!!啧啧,可惜是我瞎编的策略,但是运营手段有几个特点:
- 时效性:持续一段时间,一天、一个星期、一个月?但是很少实时变化(不然,客诉应该很高吧,用户参与一个活动,规则一直变,啧啧,准备给客服加鸡腿吧…)
- 对象明确: 地点+用户画像 组成 我们认知中的活动对象,比如 北京+金卡会员。对象的明确是策略可靠的重要依据,也是活动收益的衡量标准之一,变化可能极小;
- 我们来看看查询运营配置的条件,活动类型 + 城市 + 用户画像 + 时间;那么只需要对活动类型做到清除划分,那活动查询是相当固定的。
那缓存不正是一个很好的方案么?现在需要解决的是:策略的生效/更新如何在缓存中同步。这里一种较好的实现方式是推拉结合,重点看下图中的1和2:
- 当且仅当redis缓存中没有命中配置时,查询sql。缓存结构是 city-type做为key,那缓存中无法命中的情况只有两个:1>.该配置从未读取过,相当于需要从数据库初始化缓存;2>.缓存已过期,需要重新加载。但是不论那种情况,对于相同配置的sql查询都将下降至分钟级别;
- 运营在活动过程中可能会有更改/下线等临时的操作,这个时候需要触发主动推更新至缓存;
Config-client-service数据架构实现
思考不歇,进步不止。我们再来考虑一下,是否还有进一步优化的空间?当然有,没错,本地缓存!一种面对瞬时并发的配置获取更高效的解决方式。那么针对这种解决方式,我们需要提供什么能力?
- 一个依赖的client:封装成client是具有一定必要性的:1>.首先当然是统一,有助于本地缓存的数据格式统一,有助于client-service的数据封装统一,一些请求干预策略的统一(trace/降级等);2>.减少接入成本,这个很重要,方便使用是一个服务,特别是中间件的生命。
- 二级缓存同步/过期策略:当新增一层本地缓存以后,整个配置系统现在带有二级缓存,如何维护缓存一致会成为一个挑战。
接下来我们一起来探讨一下整个实现,先上个图:
整个架构图所示,我们做了两级缓存:在 client端增加localCache, 建立配置中心服务,嵌入redis缓存,见<1>、<2>、<3>/<4>链路;但是对于实时策略,这个链路就不怎么友好了:试想,我们现在有一个策略变动是根据大数据实时计算,如何确保LocalCache实时生效?首先,我们可能会想到利用MQ通知client 将localCache失效,这确实可以解决问题,但是有些极端情况是不得不考虑的:一旦某一个机器消费MQ延时较大,甚至失败,那极有可能造成分钟级别的缓存不一致问题!在非实时变动情况下,这个只需要提前配置,并没有问题。但是在线上实时调整的策略,这个问题会被放大,导致多台机器不一致。所以我们通过配置类型提供了<2>、<3>/<4>链路,即跳过本地缓存!
至此,一个支持多策略、高并发、实时/非实时的统一配置存取中心诞生了。统一维护公司各类业务配置,防止分散的查询,在业务规范、问题定位上的好处也不言而喻!