记一次因redis使用引起的生产事故
由于个人接触过的项目数量较少,且偏向B2B的较多,在B2C的项目上缺少一定的经验,也为此次的坑埋下了隐患。
一、项目背景:
B2C的商城项目,但从功能上来说并不复杂,且初期没有活动、折扣、抢购、秒杀等功能,只是简单的商品购买与管理流程。
参照了之前做过的商城项目,沿用了相似的框架,由于规模并不大,因此只是采用了spring boot+mybatis作为常规的开发框架,使用redis作为缓存,初期使用jedis连接,在前期遇到问题后变更为lettuce。
二、遇到问题:
可以说是确实的生产事故,项目上线即夭折。
前期错误地预估了商城的商品量,导致在代码以及缓存的处理上存在一些问题,在并发较高(实际上刚开始并不高)的情况下,直接导致大量请求无法获取到redis的连接,且一直处于等待,在持续几分钟后,进程直接挂掉。
奇怪的是,在遇到问题后的性能测试阶段,即使并发量不高,且遇到无法拿到连接的情况,立即停止压测,redis的连接也没有被归还,导致连接池一直被占用,系统迟迟无法恢复正常状态。
1、商城端存在大数据量反序列化
在经过测试以及检查代码后,发现商城端,用户访问频率较高的接口,对redis缓存有大量的取操作,这本身的影响并不大,但在数据量较大的情况下,对取出的数据进行了反序列化操作,这一步实际上是相当耗时的。
2、大集合的removeAll()操作
在经过一行一行的debug后,发现代码中存在两个list的removeAll操作竟然耗时数秒钟的时间,在百度后发现,在list的量级到达一定时,removeAll确实存在一定的效率问题(百度结果是10W级别的list去removeAll 1W级别的list,但实际上应该没有那么多的数据,但确实耗时较长,数据量还有待考证)。
解决方案:在数据量无法减少的情况下,将一个listA转换为setA,对另一个listB进行迭代,如果setA.contains(B),那么从listB中remove,大概是那么一个处理逻辑。
Set<Long> noStockItemSet = new HashSet<>(noStockItemIds);
//采用Iterator迭代器进行数据的操作
Iterator iter = onShelfItemIds.iterator();
while (iter.hasNext()) {
if (noStockItemSet.contains(iter.next())) {
iter.remove();
}
}
3、存在hgetall操作
由于前期没有考虑到数据量的问题,因此redis中存在的一个hash结构的key,其中有万级别的hashkey,这本身问题并不大,但是每一个hashkey对应的value占用的大小产生了问题,本身大概每个value在10kb左右,数据量小的情况下并不会出现问题,但是当数据量大起来,并且使用hgetall操作时,操作就会相当耗时。
当另一个相关的缓存被击穿时,方法会去掉用hgetall,从redis中全量取得该hash结构的values,并且构建另一个缓存以供下一次查询,请求并发较高时,所有与redis有关的操作全部在等待,直接导致进程挂掉。
本系统进程挂掉的原因,应当主要是在第3点,没有考虑到大数据量从redis的存取,以及高并发时的处理。
但是在实际测试的过程中,偶尔会出现redis连接没有归还,且配置了redis超时时间的情况,进程久久无法恢复正常,最后还需要定位该问题的原因。
三、尝试解决:
实际上系统最后已知的问题,还是在于缓存失效时,同一时间大量请求并发进入,导致大部分请求都直接访问数据库或者去调用第三方接口,而在这些步骤之后又会去全量更新redis的某个key,所以同一时间redis数据库以及http的请求都卡在那边,然后系统直接崩溃了。
还是由于自己没有太多toC项目以及这种情况的处理。
最后的解决方案其实并没有特别完善,但是暂时可以避免系统崩溃的情况发生。
1、优化代码
代码上的优化其实不用多说,主要是一些反序列化的地方,能改则改,也能稍微提升一点速度。
2、加入本地缓存
利用ConcurrentHashMap加入了最基础的本地缓存,能够稍微减缓redis的压力,并在系统启动时预热一部分数据,添加到本地与redis缓存中。
3、优化redis处理
原本redis在处理上存在一些问题,设计之初,在后台修改过相关数据后,会直接删除redis中对应的key,想让用户访问时从数据库或其他地方取出值后再重新维护redis中的值,这也直接导致了问题的发生。
之后暂时改成了定时任务去维护redis中的对应的key,使得redis或者本地缓存中,必有一方是存在数据的,在用户使用的过程中无需从数据库或其他请求去获取,但是问题是会导致系统的实时性出现一些问题。

浙公网安备 33010602011771号