这是2021年十月中旬时出现的问题,一次压力测试中,平均一个接口同时被3000个线程请求,最终导致很多线程等待超时,影响压测结果。
压测环境部署了两台服务,共用一台redis。
分布式锁的实现方式是:
在获取锁时,使用lua脚本在redis中创建一个String类型的key,key的value是时间戳。
在释放锁时,使用lua脚本删除这个key。
并发情况时,线程用setnx查看是否存在,已经存在key时,读取value,对比当前的时间戳是否超过了10秒,超过10秒则删除上一个key并且执行获取锁的脚本,否则等待80ms,然后继续setnx。
问题反馈给我之后,我用py脚本在开发环境执行500个并发,发现居然也出现了超时。
经过检查平均一次请求耗时大约在11ms~15ms之间,而超时时间是10秒,明显不合理。
由于之前一点分布式锁方面的问题都没有接触过,所以尝试修改部分代码,查看不同情况下的现象。
经过一周多的修改与测试,最终发现问题出在“等待80ms”这一行上。
需要先了解线程饥饿概念
线程饥饿
业务线程占用了线程池内所有的线程资源后又向线程池提交了新的任务申请空闲线程,
而业务线程需要等待这些新任务执行完之后才释放资源,
新的任务又由于线程池没有足够的资源而根本没机会执行。
并发情况时,除第一个获取锁的线程外,其他线程几乎同时等待80ms,这个现象可以看做为其他线程都进入了一个BlockingQueue,无序的每次弹出一个线程。这种现象导致总是会有一些线程,从没有参与过竞争锁就已经等待10秒而超时了。因为每次竞争都是间隔80ms。
所以其实不是由于竞争激烈引发超时,而是竞争间隔引起的线程饥饿。
因此修改等待时间:
try {
//防止饥饿线程
int anInt = new Random().nextInt(30) + 40;
Thread.sleep(anInt);
} catch (InterruptedException e) {
//保存数据以及销毁对象
}
记录以提醒自己在处理并发情况的代码必须提高警惕。