在多节点的项目中,经常要涉及到某些方法加锁的控制。而这个时候,简单易用的synchronized已经不能满足多节点的部署结构。
之前在项目中,用的比较多的是数据库的更新锁:for udpate。但是这个有个缺点,就是对于本来就容易出现瓶颈的数据库,造成了更大的压力。同时,如果是锁表的语句,同时表数据量特别大,基本服务器直接宕机了。
所以,决定绕开数据库,直接使用Redis来实现分布式锁。查了下资料,找到一些文章,思路都一致:
http://www.jeffkit.info/2011/07/1000/
http://my.oschina.net/u/1995545/blog/366381
于是参考文章,通过Spring aop注解方法来实现对方法的多节点加锁。
之前的文章给了实现的代码。并没有什么难度,注解+AOP。
但是今天做压力测试的时候,发现这个大有问题。
测试环境:
1000线程,每个线程执行1次。(这种更接近真实的tomcat环境)
sleep时间和执行时间:
* 20ms 约等于cpu线程切换时间,59998
* 50ms 43177
* 100ms 20555
* 150ms 7014
* 200ms 2970 性能尚可
但是,如果设置200ms,有可能有些线程,从最开始阻塞不断sleep,到最后全部结束了,才拿到锁。如果程序不结束,那么该线程就一直堵死,无法预料究竟多久能拿到锁。如果去设置等待多久超时断开,那么频繁的失败,对于用户肯定是无法接受的。
于是又测试数据库的锁
一张70W数据的表,
通过pk行级锁,执行时间1163(需要被锁数据存在的情况下,才能加上对应的锁),而且如果是每个线程都是各自的数据行的话,相互不阻塞会更快.(SSD固态硬盘。。。)
通过其他列表锁,一秒执行一次的感觉,直接卡死,停止测试
所以,通过sleep来等待,并发高的情况下,将会导致某些线程失控。
但是wait notify,又是针对单节点下才能使用。
也可以做到最细粒度,然后再用redis加锁,这样,降低线程堵死的可能性。
总结:
要么使用阻塞的方式来调度线程,
要么就实现一个可以在分布式环境下的类似NIO的reactor模式来进行调度。
保证先入先出,即使有些慢的状态下,不至于先来的反而堵死,造成差的体验。
或者为每个请求设置超时时间,超时抛出异常。