
背景/需求
用户Session数据的特点和缓存具有一定的相似性:较高的读写性能要求、会失效,具有TTL。但不同点在于:缓存的数据允许丢失,在缓存数据丢失时可以通过加载DB中的数据以保障服务的可用性;而session一般不会落库到传统的关系型DB中,这意味着在数据失效前是不允许数据丢失的。
像很多公司一样,有赞最初把用户的session信息存储在codis集群中。在高并发的场景下,codis提供的存储方案可以很好的满足性能和吞吐量的要求;但是codis在有赞的内部的定位仅仅为缓存而非内存数据库,这意味着允许数据的丢失,从高可用的角度来看把session信息存储在codis中是不安全的。为此有赞搭建了一套自有的内存数据库,支持Key-Value存储,将session信息从codis迁移到KV中。
项目方案
数据迁移的方案选择一般有两种:一是停机迁移,这种方案的好处是操作简单,迁移速度快,但牺牲了服务的可用性。二是平滑迁移,在不影响服务可用性的情况下进行迁移,缺点是需要较长的时间,应用的逻辑需要改动。在服务可用性要求很高的互联网领域中,方案二成为了必选项。
平滑迁移一般分为三个阶段:1.数据双写 2. 灰度读阶段 3. 下掉双写代码
双写阶段
双写阶段需要将数据写入到codis和KV存储集群中,这是平滑迁移的第一个阶段。

在笔者的具体实践中,数据双写分为两个子阶段执行。
首先是进行异步写KV、同步写Codis,这一阶段主要是目标是:保证数据正常写入KV的同时,观察KV操作的性能和KV存储集群的稳定性。在有赞内部,可以通过调用链工具监控到具体操作的执行时间,同时对比原来操作codis写方法的时间的大小,从而判断是否进入下一个阶段。
第二个子阶段是同步写阶段,将数据同步写入到codis和KV存储集群中。同步写可以验证session服务最终写入接口的稳定性,为了做到实时可控(秒级别的响应),在有赞内部可以通过diamond或者Apollo中间件来控制同步写入KV Store的流量,即通过灰度的方式来切流,逐步切到100%。
灰度读阶段
在数据正常写入到KV之后,接下来便是灰度读阶段。

笔者也将灰度读分为三个子阶段进行。
第一个子阶段为异步读KV,同步读codis,并将读codis的结果传递给异步任务,在异步任务中比较codis和KV的结果是否一致。在做数据对比时要注意避免放大读codis的流量——在高并发的场景下,由于不是实时比较,读到的KV数据可能不一致。在这个阶段只需要做到粗略的对比,检验数据是否存在其他不一致的情况;同时观察读KV的接口性能以及是否需要优化。
第二个子阶段为实时比较,在这个阶段将两个数据源的数据进行实时比较,这个阶段目标是做到数据的强一致性。根据CAP理论,应用会损耗一部分的性能,为了规避风险,做到秒级别响应,笔者同样使用中间件进行灰度控制。
在第二个阶段完成之后,理论上已经可以保证数据的一致性了;随即可以进行第三个子阶段,将部分读流量切到KV Store,通过灰度的方式逐步切流。这个阶段仍然需要做好监控,一旦发现数据存在异样,建议第一时间关闭灰度的功能,排查出原因再决定是否继续切流。
下掉双写、灰度读代码,去除依赖
在灰度读KV的流量达到100%之后,此时session服务的数据读取已经全部依赖于KV Store,数据可以无需再写入到Codis,即应用可以摘抄掉codis存储。将应用中的读写codis的相关代码删掉即可,结束整个迁移过程。

网友评论