admin 管理员组

文章数量: 887021

生产

记一次线上Redis 报错 ERR max number of clients reached ,Redis 宕机生产事故

一、前提交代:

新需求,参与商城品牌首单价促销价格,需要在指定时间内,已购买过指定门槛品牌。
商城内关于商品价格展示接口都需校验改商品关联的促销活动是否参加 品牌首单价门槛促销,商品详情页、订单结算页、商品列表页、购物车预览页。

线上Redis 手动搭建在阿里云linux服务器内,单机版本。且此服务器部署了一台商城服务
注意这里

由于促销活动属于高并发,高流量业务,选择把门槛品牌,门槛购买时间等信息用String 数据结构存储Redis中 数据结构如下

Redis中 BD=2 
String类型  Key:promotion:brand:firstPrice:purchasedBrand:促销活动idValue:{"purchasedBrandList": [{"brandId": 9710,"brandName": "咿儿润"}],"purchasedEndTime": 1678247960000,"purchasedStartTime": 1677643160000,"buyStartTime": 1678247960000,"buyEndTime": 1678247960000,"status": 0
}

二、开发需求

开发需求期间发现前同事写的Redis 操作工具类无法选择指定DB进行存储,默认在db=0

    public <T> T get(String key, String modulePrefix, Class<T> t) {checkJedisPool(); // 检测JedisPool 是否为空 key = generateKey(key, modulePrefix);// 组装key前缀try (Jedis jedis = jedisPool.getResource()) {String valueStr = jedis.get(key);return parse(valueStr, t);}}private void checkJedisPool() {if (jedisPool == null) {throw new CacheException("jedisPool can not be null.");}}/*** 生成 key .** @param key* @param modulePrefix* @return*/private String generateKey(String key, String modulePrefix) {if (StringUtils.isBlank(key)) {throw new CacheException("key can not be null.");}if (StringUtils.isBlank(modulePrefix)) {return CacheInfo.MODULE_DEFAULT + ":" + key;}return modulePrefix + ":" + key;}

我写的工具类方法:

   public static String getString(String key, int db) {JedisPool pool = getPool();Jedis jds = null;boolean broken = false;String t = null;try {jds = pool.getResource();jds.select(db);t = jds.get(key);} catch (Exception e) {broken = true;logger.error("getString:", e);throw new RuntimeException(e);} finally {if (broken) {pool.returnBrokenResource(jds);} else if (jds != null) {pool.returnResource(jds);}}return t;}

需求开发完 测试环境,测试验证没问题,开始发版本。

三、发布新版本

我们的集群服务物理机中有一台部署的包专门提供后台管理人员使用,也就是内部人员用。一台专门跑xxl-job 定时任务。
发版平时的流程都为 先发后台机器、与定时任务机器,验证包启动无误再发 商城集群 另外两台。

发完后台机器、定时任务机器,没问题,我一如既往开始发布商城的服务器。
当发完后,半小时后,陆续有运营同学反馈管理后台某些页面加载报错,无法使用。

马上看机器日志,大量的Redis 无法连接错误报出,定时任务机器,商城机器组也开始报错。

我意识到是自己新写的业务代码有问题,马上回滚,但情急之中还是在思考,自己的写的Redis工具类是没问题的,经过了百万生产流量使用的。

把jar包构建好,准备发后台、定时任务机器,后台机器jar无法启动,提示redis 无法连接报错

 (error) ERR max number of clients reached

着急的我,还没等后台机器发完,我那时候就已经把 定时任务机器停止服务。这时候定时任务jar也无法启动,这下可急坏了,因为如果不在晚上12点把定时任务恢复的话就会导致大量定时任务无法运行,后续补任务,补数据可能出现数据重复。

这时,已经开始意识到是由于商品详情页、商品列表页、订单计算页计算价格大量请求Redis 判断是否符合门槛,导致redis过载的原因。

我想把商城机器组代码回滚到上一个版本。我停了一台商城机器,还剩一台机器对外提供商品购买,下单服务。

准备回滚代码,重新发布商城机器1,也无法启动,现在就只有一条机器在扛着流量,说实话,我自己肩膀被压得松软。

四、解决方案

自己的连接Redis查询工具类是肯定没问题,连接用完了也有归还连接词操作。
这时我仔细审查了一下前同事写的Redis工具类,发现连接用完,全都没有归还连接。。。。 这个工具类在 整个工程代码内大量使用。。。。无语死了

    public <T> T get(String key, String modulePrefix, Class<T> t) {checkJedisPool();key = generateKey(key, modulePrefix);try (Jedis jedis = jedisPool.getResource()) {String valueStr = jedis.get(key);return parse(valueStr, t);}}

我开始查资料,看Redis启动时的设置的最大连接是多少
我登入redis-cli 客户端,输入命令

收到的是 连接已经爆满

 (error) ERR max number of clients reached

后面临时修改,将最大连接数调大,查到资料Redis 连接数是取决于物理机最大文件可打开数

在linux 终端直接输入

 ps -ef |grep redis 
cat /proc/5129/limits
// 最大可用连接数
Max open files            10032                10032                files
redis 某时刻已经用了连接数
[root@iZwz9fp1ljg8ksssoyuo0eZ redis-5.0.5]# ll /proc/5129/fd | wc -l 
10008
[root@iZwz9fp1ljg8ksssoyuo0eZ redis-5.0.5]# 

就是说Redis 已经达到现有物理机配置的最大连接数。

1、修改物理机最大连接数后,需要重启机器。
2、商城只有这台redis宿主机在提供对外服务了,重启了就宕机了。

当时情况图:

晚上八点左右,流量有下降,我重启了N此终于把定时任务机器恢复了。

解决方案

现在已经确定是Redis 物理单机连接数小。
唯一办法就是重启Redis ,重置连接数。此时需要先把最后一台商城服务 停机,要不然Redis再次重启,又会被流量占满连接数。

为了尽量减低商城下单,浏览商品影响范围,选择了十一点后 停止商城服务。
1、停止所有与Redis 交互的服务。
2、重启Redis。
3、版本回退到上一个逻辑代码,重启服务。

后续优化

1、将Redis 迁移为阿里云 TariDB(企业版redis) 集群。可用连接数多,数据有保障
2、优化原有Redis 交互工具类,关闭连接。

参考资料

redis报-ERR max number of clients reached错误

解决Redis 连接池报错:ERR max number of clients reached

解决Redis 连接池报错:ERR max number of clients reached

本文标签: 生产