一个抢购并发项目优化记录

梦康 2022-11-19 21:14:29 27

一个老同学突然找我说有他们公司项目并发太高,性能有问题,想找人帮忙处理下,当时我在杭州加班,没时间。一个月后还是没解决,说让我帮看看。发现了很多问题。简单记录下

jedis 连接池使用不当

在 jedis 初始化的时候,没有传入密码,每次从线程池里拿一个实例出来都要执行一次 auth (阻塞 4ms),这是我通过 arthas 发现的。
然后修改了线程的初始化的方式,初始化的时候就传入密码,再冲线程池拿的时候就不用传入密码了。

滥用 synchronized 锁

所有的 jedis 的方法前面都加了 synchronized 这导致了很强的阻塞问题,可能是为了防止 redis set 的时候有并发问题?

这里为了防止对原有逻辑有所干扰,只能新增不带锁的方法,在能明确不需要同步锁的地方调用。大大提高了系统的并发能力

因为连接池的问题,导致每个方法至少阻塞 4ms 高并发下性能特别差。又因为 synchronized 的原因,每个进程 qps 怎么都上不去。

减少 java 实例数(降本增效)

因为上面的问题,导致他们原来的做法是在一台机器上启动多个java 进程,这样可以绕过上面的 synchronized 的瓶颈问题,但是增加了很多堆内存的申请,对内存是一个极大的浪费

解决了前面两个问题,就可以减少一些 java 是进程数了,同时每个进程进行一些配置:

  1. 增加各个 java 进程的线程池的配置
  2. 增加 tomcat 单个实例的最大线程数
  3. 增加 jvm 堆内存,原来设置的过小

减小锁的颗粒度

原来最耗时的是用户下单逻辑,原来一直有超卖和拿锁失败,等待时间特别长的问题。我也第一次做支付相关的工作不知道是否完善,最终通过分为三次拿锁:

  1. 校验用户购买资格
  2. 拿用户钱包锁(用户维度 redis 锁 redisson 实现)
  3. 抢下单资格,抢商品库存锁(商品维度 redis 锁)
  4. 下单逻辑
  5. 异常回滚,抢商品库存锁(商品维度 redis 锁)
  6. 中间一些缓存 + 并行

快速定位一些异常问题的手段

  1. arthas 定位是什么线程被 block 然后定位是 synchronized 锁
  2. 给项目增加了 trace id 埋点,然后使用 sls 收集日志