最近开发项目以及学习过程中对于并发问题的解决方式没有清晰的思路。遂学习之,先做个记录吧。
并发问题
并发遇到的常见问题就是,多个客户端同时操作同一个资源,会出现一些意想不到的异常情况。这里的‘操作’如果都是查询,那问题还不大,如果这些操作包含了修改、新增、删除等会涉及到服务端数据库改动的,那么就会产生问题。
并发的一般解决思路就是做控制。具体说,就是要在并发操作发生的环节里去做一些干预性的事儿,来让抢这个资源的请求们都消停一下,最终通过一定的干预让每个请求都能获得正常的响应,同时让这个被请求资源也舒舒服服的,避免发生不必要的意外zzz.
干预手段1
首先想到的是用数据库的锁策略,以mysql为例,InnoDB支持行级锁,在操作可能产生并发问题的资源时,可使用简单的语句控制,如可以开启事务start transaction,使用for update给资源加锁;具体的做法要结合使用的语言及框架查阅mysql官方文档,或百度之。for update作用于不同的表字段时加锁效果不同,对主键而言是行级锁,对普通字段则是表级锁,具体使用时还是要先做测试验证其加锁粒度。
干预手段2
网上看了一圈,发现有些文章说可以使用redis做并发控制,在这里按我的理解描述一下:
在后端使用redis作缓存,把每一个客户端过来的请求都缓存起来,并设置缓存过期时间,这样一来,由于redis的唯一性以及单线程原子性,同样的请求每次只能有一个被缓存,这个被缓存的请求就是幸运儿,可以对这个资源进行操作了。其他的请求则都被redis拦在了门外。
这个思路想一想其实就相当于让redis 当看大门的呗0.o 。在可能有并发问题出现的地方安置一个redis看门大爷,每次请求过来先去问redis大爷让不让进去(执行实际业务操作);也还相当于把redis当成一把锁,去锁相应的资源。
这个解决方案也存在一些局限或是问题:
- 复杂性: 服务端处理请求时变复杂了一些(问题不大)
- 唯一性判断问题: 根据请求判断唯一性,可能需要对请求做一些限制,或是截取请求的一部分。
- 这种解决方案会有局限,即无法解决不同请求产生的并发问题(只对不同用户同时进行同样的请求有效)
- 缓存过期时间问题:设置多久合适? 还是不设置,在完成请求后手动释放(删除redis表对应记录)
- 重试机制问题:被拒之门外的请求作何处理? 目前想的就是简单的返回给客户端一个提示 (eg:请稍等,过几秒再来吧?)
干预手段2.1
有了上面的思路,结合最近做的项目,又有了一个具体些的想法,就是用redis去锁数据库对应表的主键字段并自己设计加锁策略。对于操作mysql数据库来说,即将mysql表的唯一性字段(通常为主键)使用redis进行缓存,每当有请求过来时,首先去redis查询对应的主键字段有没有被锁住,(具体可使用redis的hset ?),若没有被锁则可以操作此资源,否则直接返回。(相当于行级锁)
存在的问题:
- 可靠性:若redis缓存挂掉,则不能控制并发。
- 死锁: 若两个及以上的请求同时操作多个行的资源可能会产生死锁问题。
- 系统开销: 使用redis无疑增加了系统开销。
- 局限性:只适合简单的并发修改等操作
总结
本文主要概括性的讲了并发问题的几个解决方案,即使用数据库层面的并发能力,使用redis进行并发控制的一些思路,具体的操作还需要动手实践,实践出真知。
并发问题是开发中很常见也是一直以来都很棘手的问题,在学习并发理论知识的同时,更需要多实践并在实践中积累经验,才能有针对性的解决不同场景下的并发问题。