前言
事故发生在20221104,当晚动作为注册中心升级,结果导致数据库宕机,造成生产环境影响4小时,由于中间有二十大,所以给出了技术方案,但一直没动,直到20221207才完全修复。
技术架构
本系统为自研注册中心,启动流程如下:
- 服务启动,从数据库里拉取全量权限数据,并把数据放到redis里;
- 注册中心根据日志主动去call提供者,查看是否还在,以防止把不正确的提供者地址返回给消费者;
消费者消费流程如下:
- 先与F5建立连接,F5负载均衡到注册中心;
- 注册中心拿dubbo://...里面的数据去redis里查看,查到有权限就返回提供者,如果没有权限就返回无权限;
- 当提供者和消费者提交申请并提供者和注册中心都同意时,会去更新redis里的缓存;
PS:redis之前出现过问题,具体发生了啥可以看dubbo注册中心问题,生产真实案例复盘
当晚发生了啥
21:30,通知所有开发者注册中心开始升级,20台机器依次重启(因为性能问题,没用docker,k8s那些东西,要手动启动tomcat),花了差不多45分钟
22:00,所有消费者权限通过,开始重新发布、订阅
事故开始
22:10,有业务开发表示,注册中心的管理页面登录超时,我们这个页面只对内部开发使用,所以是直接查数据库,当时判断是数据库卡了一下,毕竟之前就有过select * from 表名 where 带索引的主键id = ?这么一个sql跑了10分钟的事(PS:这个bug还在处理,下一篇文章就写这个了)
22:15,最后一台注册中心重启完毕,但消费者表示,错误的提供者数据返回给了他们
22:17,运维和我讲,数据库不太正常(当时的图找不到了,就找了一个后期的),注意连接数,20台机器,最低1500个连接数,也就是说平均一台75个,有系统调优经验的都能看出来,这数量绝对不对
22:30,主库进行2次扩容后发生宕机,备库启动,扛了一会备库宕机,数据库组件宕机,但这时业务那边开始大批量启动,产生了许多垃圾数据
05:00,临时改了代码,服务器启动时不会去数据库里拿,redis里的数据本地跑脚本去更新,这才启动起来
事故分析
排查阶段一
数据库连接数这么多,首先mybatis线程池100%是肯定的,减小最多线程数就好,但为啥会这么高?
连接数这么高,代表队列里有很多任务在等待执行,也就是说在执行的sql太慢了,那就是慢sql,效果有些好转。
排查阶段二
ok,数据库慢sql导致的解决了,但以后还会出问题,所以要对连接池大小进行处理,大家设置时可能根据感觉、经验去设置,这次为了保险起见我们找DBA,TA进行压测和评估后设置,提高核心线程数,降低最大线程数。
排查阶段三(超级亮点,超级大坑!!!)
代码改好了,开始进行压测,忽然发现有几次数据库连接莫名其妙的还是会上去,之前的排查已经相当到位了,那问题还会出现在哪里呢???
执行
select connection_id() from dual;
发现,有一堆事务!!!不可能啊,从数据库里拉出来数据,再放到redis里,撑死200个,但查出来几千个,绝对有问题,后面就要看当时都在做什么事,会这么多。
当时所有动作:
- 注册从数据库拉出来数据,放到redis里(已排查)
- 监控页面上点点点(不可能有人能点暴数据库把)
- 服务调用时,发送日志给监控中心,监控中心存储到hbase
好了,现在第三点最可以,然后去看代码直接一个大震惊!!!
你没有看错,Transactional事务注解,直接加到了类上面,但代码里对数据库没有进行任何操作。那为什么对hbase进行事务操作,数据库会有影响???
以下内容截图自CSDN
@Transactional的实现原理
该注解是通过JDBC的事务 + Spring的AOP动态代理来完成的.
1、 事务开始时,通过AOP机制,生成一个代理connection对象,
2、 并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。
3、 在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,
4、 执行所有数据库命令。
5、 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,
6、 然后关闭该代理 connection 对象
请注意,根据jdbc事务和aop完成......
也就是说,只要加了Transactional,就算里面拉坨屎,数据库都会上个事务!!!
问题解决
总结
事务不能乱加!!!
事务不能乱加!!!
事务不能乱加!!!