并发更新,除了悲观锁/乐观锁,还有什么典型方案?
如果是多线程更新同一个对象或者数据结构,显而易见,可以加各种线程锁来实现。
日常工作中,更常见的是多台应用服务器(多个进程)同时更新DB,或者Redis,这种情况下,解决的办法都有哪些呢?
我们知道,如果是DB,可以使用DB自带的悲观锁(select ... for update),或者乐观锁(也就是给数据加版本号,CAS机制)。
但对于Redis,第1,没有悲观锁机制;第2,乐观锁机制,在大并发量场景下并不适用。那如果要并发更新,还有什么好的办法?
分布式锁 - 不靠谱
分布式锁本身如果要高性能,很多也是用Redis实现的。用Redis实现的分布式锁解决Redis的并发更新问题,没解决好并发更新,又引入了“分布式锁”这个大坑。
为什么说“分布式锁”是个大坑,后续再来讲。
2. 串行化
锁会造成数据的串行化访问,相当于是在DB/KV内部对请求进行排队。与其如此,不如在应用层面做排队,可靠性更强,性能也有保证。具体分为2种方式:
(1)异步写(异步并发更新)
如果并发更新的结果,不需要同步返回,那可以把所有的并发请求都写入到一个消息中间件(比如Kafka),然后单线程消费,避免并发问题。这个,在大数据场景中尤为实用,比如Spark/Flink,要多个Task并发更新Mysql/Redis。
(2)同步写(同步并发更新)
这就需要在应用服务器的内存中有一个队列,处理请求的线程把请求放入队列之后,不给客户端端返回,而是等待队列处理结果。
同样,单线程去取这个队列,然后更新DB/Redis,把结果放入另外一个Response队列,通知处理线程去取,返回给客户端。
这个办法有个前提:路由要做一致性Hash,保证同一个用户的请求,落入到同1台应用服务器,如果落到2台上面,虽然1台内部有队列是串行的,2台之间还是会并发更新DB/Redis,造成数据错乱。