举个栗子:一个系统,有个历史add接口,受最大数量限制,使用memcache和mongo。


V4.0.1之前逻辑:

  1. 从cache取全部数据
  2. 按最大数量限制对数组做增改逻辑
  3. 如果大于24小时,则写入mongo
  4. 写入cache

统计次数:

  • cache取1
  • cache写1
  • mongo全量写1/24小时

大概流程图如下:
Add最初逻辑

初步分析:该逻辑在最初是可以稳定运行的,因为数据量不大,但随着时间的推移,使用量的上升,memcache不止该接口在用,其他服务也会写入大量的数据,问题就逐渐显露出来了。

问题:随着使用时间的增加,memcache爆了,会释放低频数据(即使设置了30天有效期),然后用户的历史数据就变成了24小时之前的数据,造成上次同步到MongoDB后的时间节点开始的历史数据丢失情况发生。如果memcache挂了重启了,全部用户将丢失写入MongoDB前的全部数据。可用性不稳定,持久性堪忧。


于是有了V4.0.1版本。

该版本逻辑变更如下:

  1. 从cache删除全部数据
  2. MongoDB取出全部数据,按最大数量限制对数组做增改逻辑,update upsert全量数据更新到MongoDB
  3. 从MongoDB取全部数据
  4. 写入cache

统计次数:

  • cache删1
  • cache取1
  • cache写1
  • mongo 全量取2
  • mongo 全量写1

大概流程图如下:
4.0.1逻辑

瓶颈点:MongoDB写压力增大,写频率高了之后,因为每次都是读写全量数据,数据变大后,可能触发MongoDB增加块和数据迁移,慢查询触发时间是3秒,只要网络传输或者MongoDB性能稍有问题,立马就是慢查询,影响业务。持久性保证了,然而性能和前一个版本相比,用脚趾头想也知道会下降不少,最关键的是,全部驻地公用一个中央MongoDB,性能上的问题迟早会显现出来,当然,这个版本之后,性能问题暴露后,用了MongoDB下沉的策略,将压力转嫁到了驻地本地的MongoDB,至此,该函数已经可以稳定提供服务,然而,这肯定不是最优解。


V5.0.0初版方案:

  1. MongoDB取next和下标可用数组:没有则一次性创建最大数量的初始化数据,加入当前Add数据写入MongoDB;有数据则直接执行增删改逻辑,按数组下标update更新MongoDB数组指定元素数据
  2. MongoDB取全部数据
  3. 过滤后将有效数据写入cache

统计次数:

  • MongoDB取2 一次取next,一次取全量
  • MongoDB写1
  • cache写1

大概流程图如下:
5.0.0初版逻辑

分析说明:没有数据则一次性创建最大数量的初始化数据,避免了后续数据变大的情况造成的块增加和数据迁移的情况发生。之后再增记录,只对初始化好的数组子元素进行增改,避免了全量数据更新。以此降低了MongoDB的写入压力。该方案,主要是为了降低MongoDB层面压力,优化写入性能。


在测试同学打压的过程中,琢磨着还可以优化,毕竟全量读也很蠢,所以尝试调优逻辑,将MongoDB的压力分摊到cache上:

  1. 从cache取有效数据
    如果没有有效数据,从MongoDB全量取一次,过滤处理得到有效数据,还没有就初始化有效数据为空
  2. MongoDB取next和下标可用数组:没有则一次性创建最大数量的初始化数据,加入当前Add数据写入MongoDB;有数据则直接执行增删改逻辑,按数组下标update更新MongoDB数组指定元素数据,返回新增的那条数据
  3. cache对有效数据进行增加后写入cache

统计次数:

  • cache取1
    仅在不存在数据的情况,MongoDB取全量1
  • MongoDB取next 1
  • MongoDB写1
  • cache写1

大概流程图如下:
V5.0.0最终版历史模型

对比分析:相对未优化版本,将MongoDB全量取的逻辑限制在cache取不到数据的情况下,减少了平时的MongoDB全量取。多了一步cache取。MongoDB增记录后返回增加的那条记录,叠加逻辑复用给cache,确保cache只保存有效数据。

最终版本优势总结:
通过代码层的逻辑调优,避免了MongoDB中的全量读写,大幅降低了MongoDB的压力。Cache层调优后,仅在Cache没数据的情况下回读MongoDB数据后处理完毕回写Cache。逻辑中仅MongoDB更新方式的变更,性能就比旧接口提升了40%。

核心思路:用第一次的空间占用换后续的时间和效率,杜绝全量大数组更新,将更新锁定到最小范围。尽可能减少瓶颈点(MongoDB)的读写。

回顾:如果最初的逻辑Cache层是持久可靠的,就没必要这么折腾了。比如用了Redis的话,第一种逻辑就差不多OK了,但每次的大数据量回写MongoDB还是会存在超时的可能。架构实现是在实际生产过程中逐步调优的,上面讲的只是一个不断优化的思路而已,道途漫漫,且行且看。

Related Posts: 一个历史模型的演变 :

avatar