39. 分布式缓存重建并发冲突问题以及zookeeper分布式锁解决方案
上篇文章整个三级缓存的架构已经走通了;
但是遇到一个问题,如果缓存服务在本地的ehcache中都读取不到数据,就意味着,需要重新到源头的服务数据库中去拉去数据,拉取到数据之后,先给nginx
的请求返回,同时将数据写入ehcache
和redis
中。在多个服务下,就会有重建缓存的并发冲突
问题!
分布式重建缓存并发冲突
问题由来
-
重建缓存
:数据在所有的缓存中都不存在了(比如LRU
算法清楚),就需要重新查询数据写入缓存,重建缓存 -
分布式的重建缓存
,在不同的机器上,不同的服务实例中,去做上面的重建缓存,就会出现多个机器分布式重建去读取相同的数据,然后写入缓存中。
分布式重建缓存的并发冲突问题…
解决方案
采用的方案和分发层nginx分流到应用层nginx原理一样。
- 之前已经实现流量均匀分布到所有缓存服务实例上;
应用层nginx
,是将请求流量均匀地打到各个缓存服务实例中的,当业务增长,my-eshop-cache
缓存服务,可能会部署多实例在不同的机器上
-
分发层nginx
根据商品idhash
后,发送请求到应用层nginx
,实现固定商品id,走固定的缓存服务实例
之前分流请求用
分发层nginx
的lua脚本
,通过应用层nginx
的地址列表,对每个商品id做一个hash,然后对应用nginx
数量取模,将每个商品的请求固定分发到同一个应用层nginx
上面去
-
当请求到
应用层nginx
,发现自己本地lua shared dict
缓存中没有数据的时候,采取一样的方式,对product id
取模,然后将请求固定分发到同一个缓存服务实例中去
。 -
这样的话,就不会出现说多个缓存服务实例分布式的去更新那个缓存了
-
源信息服务发送的变更消息,需要按照
商品id
去分区,固定的商品变更走固定的kafka分区
,也就是固定的一个缓存服务实例接受消息实现缓存重建。 -
缓存服务
,是监听kafka topic
的,一个缓存服务实例,作为一个kafka consumer
,就消费topic
中的一个partition
,所以你有多个缓存服务实例的话,每个缓存服务实例就消费一个kafka partition
所以这里,源头信息服务,在发送消息到
kafka topic
的时候,都需要按照product id
去分区。
- 也就时说,同一个
product id
变更的消息一定是到同一个kafka partition
中的,即同一个product id
的变更消息,一定是同一个缓存服务实例
消费到的
很简单,
kafka producer api
,里面send message
的时候,多加一个product id
参数就可以了。
- 问题是,自己写的简易的
hash分发
,与kafka的分区
,可能并不一致!!!
之前写的简易的hash分发策略,是按照crc32去取hash值,然后再取模的,
kafka producer
的hash策略很可能是不一样的,可能导致说,数据变更的消息所到的缓存服务实例,跟应用层nginx分发到的那个缓存服务实例也许就不在一台机器上了
- 这样的话,在高并发,极端的情况下,可能就会出现冲突,分布式的缓存重建并发冲突问题发生…
基于zookeeper分布式锁
介绍
-
分布式锁
:如果你有多个机器在访问同一个共享资源
,你需要加个锁,让多个分布式的机器在访问共享资源的时候串行
起来,这个锁,多个不同机器上的服务共享的锁,就是分布式锁。 -
分布式锁当然有很多种不同的实现方案,
redis分布式锁
,zookeeper分布式锁
; -
zk,做分布式协调这一块,还是很流行的,大数据应用里面,hadoop,storm,都是基于zk去做分布式协调
方案流程
zk分布式锁的解决并发冲突的方案
- 变更缓存重建以及空缓存请求重建,更新redis之前,都需要先获取对应
商品id的分布式锁
; - 拿到分布式锁之后,需要根据
时间版本
去比较一下,如果自己的版本新于redis中的版本,那么就更新,否则就不更新 - 如果拿不到分布式锁,那么就等待,
不断轮询
等待,直到自己获取到分布式的锁