记一次因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 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使用引起的生产事故)