如果缓存服务在本地的ehcache中都读取不到数据。
这个时候就意味着,需要重新到源头的服务中去拉去数据,拉取到数据之后,赶紧先给nginx的请求返回,同时将数据写入ehcache和redis中
分布式重建缓存的并发冲突问题
重建缓存:数据在所有的缓存中都不存在了(LRU算法弄掉了),就需要重新查询数据写入缓存,重建缓存
分布式的重建缓存,在不同的机器上,不同的服务实例中,去做上面的事情,就会出现多个机器分布式重建去读取相同的数据,然后写入缓存中
发生分布式重建缓存的并发冲突问题图片:
 
1、流量均匀分布到所有缓存服务实例上
应用层nginx,是将请求流量均匀地打到各个缓存服务实例中的,可能会部署多实例在不同的机器上
2、应用层nginx的hash,固定商品id,走固定的缓存服务实例
分发层的nginx的lua脚本,一堆应用层nginx的地址列表,对每个商品id做一个hash,然后对应用nginx数量取模
将每个商品的请求固定分发到同一个应用层nginx上面去
在应用层nginx里,发现自己本地lua shared dict缓存中没有数据的时候,就采取一样的方式,对product id取模,然后将请求固定分发到同一个缓存服务实例中去
这样的话,就不会出现说多个缓存服务实例分布式的去更新那个缓存了
3、源信息服务发送的变更消息,需要按照商品id去分区,固定的商品变更走固定的kafka分区,也就是固定的一个缓存服务实例获取到
缓存服务,是监听kafka topic的,一个缓存服务实例,作为一个kafka consumer,就消费topic中的一个partition
所以你有多个缓存服务实例的话,每个缓存服务实例就消费一个kafka partition
所以这里,一般来说,你的源头信息服务,在发送消息到kafka topic的时候,都需要按照product id去分区
也就时说,同一个product id变更的消息一定是到同一个kafka partition中去的,也就是说同一个product id的变更消息,一定是同一个缓存服务实例消费到的
 
4、自己写的简易的hash分发,与kafka的分区,可能并不一致!!!
自己写的简易的hash分发策略,是按照crc32去取hash值,然后再取模的
不知道你的kafka producer的hash策略是什么,很可能说跟我们的策略是不一样的
数据变更的消息所到的缓存服务实例,跟我们的应用层nginx分发到的那个缓存服务实例也许就不在一台机器上了
这样的话,在高并发,极端的情况下,可能就会出现冲突
分布式的缓存重建并发冲突问题:

6、基于zookeeper分布式锁的解决方案
分布式锁,如果你有多个机器在访问同一个共享资源,那么这个时候,如果你需要加个锁,让多个分布式的机器在访问共享资源的时候串行起来,多个不同机器上的服务共享的锁,就是分布式锁
分布式锁当然有很多种不同的实现方案,redis分布式锁,zookeeper分布式锁
zk,做分布式协调这一块,还是很流行的,大数据应用里面,hadoop,storm,都是基于zk去做分布式协调
zk分布式锁的解决并发冲突的方案
(1)变更缓存重建以及空缓存请求重建,更新redis之前,都需要先获取对应商品id的分布式锁
(2)拿到分布式锁之后,需要根据时间版本去比较一下,如果自己的版本新于redis中的版本,那么就更新,否则就不更新
(3)如果拿不到分布式锁,那么就等待,不断轮询等待,直到自己获取到分布式的锁

zk分布式锁的原理
通过去创建zk的一个临时node,来模拟给摸一个商品id加锁
zk会给你保证说,只会创建一个临时node,其他请求过来如果再要创建临时node,就会报错,NodeExistsException

我们的所谓上锁,其实就是去创建某个product id对应的一个临时node
如果临时node创建成功了,那么说明我们成功加锁了,此时就可以去执行对redis立面数据的操作
如果临时node创建失败了,说明有人已经在拿到锁了,在操作reids中的数据,那么就不断的等待,直到自己可以获取到锁为止
基于zk client api,去封装上面的这个代码逻辑
释放一个分布式锁,去删除掉那个临时node就可以了,就代表释放了一个锁,那么此时其他的机器就可以成功创建临时node,获取到锁
pom:
  1. <dependency>
  2. <groupId>org.apache.zookeeper</groupId>
  3. <artifactId>zookeeper</artifactId>
  4. <version>3.4.5</version>
  5. </dependency>
  1. import java.util.concurrent.CountDownLatch;
  2.  
  3. import org.apache.zookeeper.CreateMode;
  4. import org.apache.zookeeper.WatchedEvent;
  5. import org.apache.zookeeper.Watcher;
  6. import org.apache.zookeeper.Watcher.Event.KeeperState;
  7. import org.apache.zookeeper.ZooDefs.Ids;
  8. import org.apache.zookeeper.ZooKeeper;
  9.  
  10. public class ZooKeeperSession {
  11.  
  12. private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
  13.  
  14. private ZooKeeper zookeeper;
  15.  
  16. public ZooKeeperSession() {
  17. // 去连接zookeeper server,创建会话的时候,是异步去进行的
  18. // 所以要给一个监听器,说告诉我们什么时候才是真正完成了跟zk server的连接
  19. try {
  20. this.zookeeper = new ZooKeeper(
  21. "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181",
  22. 50000,
  23. new ZooKeeperWatcher());
  24. // 给一个状态CONNECTING,连接中
  25. System.out.println(zookeeper.getState());
  26.  
  27. try {
  28. // CountDownLatch
  29. // java多线程并发同步的一个工具类
  30. // 会传递进去一些数字,比如说1,2 ,3 都可以
  31. // 然后await(),如果数字不是0,那么久卡住,等待
  32.  
  33. // 其他的线程可以调用coutnDown(),减1
  34. // 如果数字减到0,那么之前所有在await的线程,都会逃出阻塞的状态
  35. // 继续向下运行
  36.  
  37. connectedSemaphore.await();
  38. } catch(InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41.  
  42. System.out.println("ZooKeeper session established......");
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }
  46. }
  47.  
  48. /**
  49. * 获取分布式锁
  50. * @param productId
  51. */
  52. public void acquireDistributedLock(Long productId) {
  53. String path = "/product-lock-" + productId;
  54.  
  55. try {
  56. zookeeper.create(path, "".getBytes(),
  57. Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
  58. System.out.println("success to acquire lock for product[id=" + productId + "]");
  59. } catch (Exception e) {
  60. // 如果那个商品对应的锁的node,已经存在了,就是已经被别人加锁了,那么就这里就会报错
  61. // NodeExistsException
  62. int count = 0;
  63. while(true) {
  64. try {
  65. Thread.sleep(20);
  66. zookeeper.create(path, "".getBytes(),
  67. Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
  68. } catch (Exception e2) {
  69. e2.printStackTrace();
  70. count++;
  71. continue;
  72. }
  73. System.out.println("success to acquire lock for product[id=" + productId + "] after " + count + " times try......");
  74. break;
  75. }
  76. }
  77. }
  78.  
  79. /**
  80. * 释放掉一个分布式锁
  81. * @param productId
  82. */
  83. public void releaseDistributedLock(Long productId) {
  84. String path = "/product-lock-" + productId;
  85. try {
  86. zookeeper.delete(path, -1);
  87. } catch (Exception e) {
  88. e.printStackTrace();
  89. }
  90. }
  91.  
  92. /**
  93. * 建立zk session的watcher
  94. * @author Administrator
  95. *
  96. */
  97. private class ZooKeeperWatcher implements Watcher {
  98.  
  99. public void process(WatchedEvent event) {
  100. System.out.println("Receive watched event: " + event.getState());
  101. if(KeeperState.SyncConnected == event.getState()) {
  102. connectedSemaphore.countDown();
  103. }
  104. }
  105.  
  106. }
  107.  
  108. /**
  109. * 封装单例的静态内部类
  110. * @author Administrator
  111. *
  112. */
  113. private static class Singleton {
  114.  
  115. private static ZooKeeperSession instance;
  116.  
  117. static {
  118. instance = new ZooKeeperSession();
  119. }
  120.  
  121. public static ZooKeeperSession getInstance() {
  122. return instance;
  123. }
  124.  
  125. }
  126.  
  127. /**
  128. * 获取单例
  129. * @return
  130. */
  131. public static ZooKeeperSession getInstance() {
  132. return Singleton.getInstance();
  133. }
  134.  
  135. /**
  136. * 初始化单例的便捷方法
  137. */
  138. public static void init() {
  139. getInstance();
  140. }
  141.  
  142. }

  

 实现思路:

1、主动更新

监听kafka消息队列,获取到一个商品变更的消息之后,去哪个源服务中调用接口拉取数据,更新到ehcache和redis中

先获取分布式锁,然后才能更新redis,同时更新时要比较时间版本

2、被动重建

直接读取源头数据,直接返回给nginx,同时推送一条消息到一个队列,后台线程异步消费

后台现成负责先获取分布式锁,然后才能更新redis,同时要比较时间版本

  1. // 加代码,在将数据直接写入redis缓存之前,应该先获取一个zk的分布式锁

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  1. ZooKeeperSession zkSession = ZooKeeperSession.getInstance();
  2. zkSession.acquireDistributedLock(productId);
  3.  
  4. // 获取到了锁
  5. // 先从redis中获取数据
  6. ProductInfo existedProductInfo = cacheService.getProductInfoFromReidsCache(productId);
  7.  
  8. if(existedProductInfo != null) {
  9. // 比较当前数据的时间版本比已有数据的时间版本是新还是旧
  10. try {
  11. Date date = sdf.parse(productInfo.getModifiedTime());
  12. Date existedDate = sdf.parse(existedProductInfo.getModifiedTime());
  13.  
  14. if(date.before(existedDate)) {
  15. System.out.println("current date[" + productInfo.getModifiedTime() + "] is before existed date[" + existedProductInfo.getModifiedTime() + "]");
  16. return;
  17. }
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println("current date[" + productInfo.getModifiedTime() + "] is after existed date[" + existedProductInfo.getModifiedTime() + "]");
  22. } else {
  23. System.out.println("existed product info is null......");
  24. }
  25.  
  26. cacheService.saveProductInfo2ReidsCache(productInfo);
  27.  
  28. // 释放分布式锁
  29. zkSession.releaseDistributedLock(productId);

  

  1. if(productInfo == null) {
  2. // 就需要从数据源重新拉去数据,重建缓存,但是这里先不讲
  3. String productInfoJSON = "{\"id\": 2, \"name\": \"iphone7手机\", \"price\": 5599, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1, \"modified_time\": \"2017-01-01 12:01:00\"}";
  4. productInfo = JSONObject.parseObject(productInfoJSON, ProductInfo.class);
  5. // 将数据推送到一个内存队列中
  6. RebuildCacheQueue rebuildCacheQueue = RebuildCacheQueue.getInstance();
  7. rebuildCacheQueue.putProductInfo(productInfo);
  8. }

  

  1. import java.util.concurrent.ArrayBlockingQueue;
  2.  
  3. /**
  4. * 重建缓存的内存队列
  5. *
  6. */
  7. public class RebuildCacheQueue {
  8.  
  9. private ArrayBlockingQueue<ProductInfo> queue = new ArrayBlockingQueue<ProductInfo>(1000);
  10.  
  11. public void putProductInfo(ProductInfo productInfo) {
  12. try {
  13. queue.put(productInfo);
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. }
  18.  
  19. public ProductInfo takeProductInfo() {
  20. try {
  21. return queue.take();
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. }
  25. return null;
  26. }
  27.  
  28. /**
  29. * 内部单例类
  30. */
  31. private static class Singleton {
  32.  
  33. private static RebuildCacheQueue instance;
  34.  
  35. static {
  36. instance = new RebuildCacheQueue();
  37. }
  38.  
  39. public static RebuildCacheQueue getInstance() {
  40. return instance;
  41. }
  42.  
  43. }
  44.  
  45. public static RebuildCacheQueue getInstance() {
  46. return Singleton.getInstance();
  47. }
  48.  
  49. public static void init() {
  50. getInstance();
  51. }
  52.  
  53. }

  

  1. import java.text.SimpleDateFormat;
  2. import java.util.Date;
  3. import com.roncoo.eshop.cache.model.ProductInfo;
  4. import com.roncoo.eshop.cache.service.CacheService;
  5. import com.roncoo.eshop.cache.spring.SpringContext;
  6. import com.roncoo.eshop.cache.zk.ZooKeeperSession;
  7.  
  8. /**
  9. * 缓存重建线程
  10. */
  11. public class RebuildCacheThread implements Runnable {
  12.  
  13. private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  14.  
  15. public void run() {
  16. RebuildCacheQueue rebuildCacheQueue = RebuildCacheQueue.getInstance();
  17. ZooKeeperSession zkSession = ZooKeeperSession.getInstance();
  18. CacheService cacheService = (CacheService) SpringContext.getApplicationContext()
  19. .getBean("cacheService");
  20.  
  21. while(true) {
  22. ProductInfo productInfo = rebuildCacheQueue.takeProductInfo();
  23.  
  24. zkSession.acquireDistributedLock(productInfo.getId());
  25.  
  26. ProductInfo existedProductInfo = cacheService.getProductInfoFromReidsCache(productInfo.getId());
  27.  
  28. if(existedProductInfo != null) {
  29. // 比较当前数据的时间版本比已有数据的时间版本是新还是旧
  30. try {
  31. Date date = sdf.parse(productInfo.getModifiedTime());
  32. Date existedDate = sdf.parse(existedProductInfo.getModifiedTime());
  33.  
  34. if(date.before(existedDate)) {
  35. System.out.println("current date[" + productInfo.getModifiedTime() + "] is before existed date[" + existedProductInfo.getModifiedTime() + "]");
  36. continue;
  37. }
  38. } catch (Exception e) {
  39. e.printStackTrace();
  40. }
  41. System.out.println("current date[" + productInfo.getModifiedTime() + "] is after existed date[" + existedProductInfo.getModifiedTime() + "]");
  42. } else {
  43. System.out.println("existed product info is null......");
  44. }
  45.  
  46. cacheService.saveProductInfo2ReidsCache(productInfo);
  47. }
  48. }
  49.  
  50. }

  启动线程:

  1. new Thread(new RebuildCacheThread()).start();

  

分布式缓存重建并发冲突和zookeeper分布式锁解决方案的更多相关文章

  1. Apache Ignite——集合分布式缓存、计算、存储的分布式框架

    Apache Ignite内存数据组织平台是一个高性能.集成化.混合式的企业级分布式架构解决方案,核心价值在于可以帮助我们实现分布式架构透明化,开发人员根本不知道分布式技术的存在,可以使分布式缓存.计 ...

  2. 分布式缓存之Memcache

    〇.为什么要用分布式缓存 1.软件从单机到分布式 走向分布式第一步就是解决:多台机器共享登录信息的问题. 例如:现在有三台机器组成了一个Web的应用集群,其中一台机器用户登录,然后其他另外两台机器共享 ...

  3. 5个强大的Java分布式缓存框架推荐

    在开发中大型Java软件项目时,很多Java架构师都会遇到数据库读写瓶颈,如果你在系统架构时并没有将缓存策略考虑进去,或者并没有选择更优的 缓存策略,那么到时候重构起来将会是一个噩梦.本文主要是分享了 ...

  4. 一个技术汪的开源梦 —— 公共组件缓存之分布式缓存 Redis 实现篇

    Redis 安装 & 配置 本测试环境将在 CentOS 7 x64 上安装最新版本的 Redis. 1. 运行以下命令安装 Redis $ wget http://download.redi ...

  5. [.NET领域驱动设计实战系列]专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现

    一.引言 在上一专题中,商家发货和用户确认收货功能引入了消息队列来实现的,引入消息队列的好处可以保证消息的顺序处理,并且具有良好的可扩展性.但是上一专题消息队列是基于内存中队列对象来实现,这样实现有一 ...

  6. .NET Core应用中使用分布式缓存及内存缓存

    .NET Core针对缓存提供了很好的支持 ,我们不仅可以选择将数据缓存在应用进程自身的内存中,还可以采用分布式的形式将缓存数据存储在一个“中心数据库”中.对于分布式缓存,.NET Core提供了针对 ...

  7. JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案

    JAVA系统架构高并发解决方案 分布式缓存 分布式事务解决方案

  8. ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁

    前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...

  9. Zookeeper + Guava loading cache 实现分布式缓存

    1. 概述 项目中,创建的活动内容存入redis,然后需要用到活动内容的地方,从redis去取,然后参与计算. 活动数据的一个特点是更新不频繁.数据量不大.因为项目部署一般是多机器.多实例,除了red ...

随机推荐

  1. vue.js动态绑定input的checked

    不管<input type='radio checked='true''>  你的checked属性值是true或者false,他都会选中. 其实原理是这样的,复选框里只要有checked ...

  2. iptables 限制端口

    限制端口 #!/bin/bashiptables -P INPUT ACCEPTiptables -P OUTPUT ACCEPTiptables -Fiptables -P INPUT DROPip ...

  3. [技术博客]基于动态继承类、WebDriver的浏览器兼容性测试框架搭建

    问题背景 观察使用selenium进行自动化测试的过程,我们可以将它概述为: 启动测试进程,在该进程中构建WebDriver 启动浏览器进程,将它与WebDriver建立连接 使用WebDriver向 ...

  4. js中for..of..的使用和迭代器

    for..of是ES6中引入的新特性,它主要的作用是:循环一个可迭代的对象. 它可以循环遍历,数组.字符串.Set对象等等,先来看两个简单的例子: 遍历字符串 let str = 'Hello' fo ...

  5. IIS 7中添加匿名访问FTP站点

    1. 开启FTP和IIS服务: 2.打开IIS 管理器: 我电脑上是IIS 7.5 ,所以选择第一个并点击打开哦. 如果你想知道自己IIS的版本,打开帮助菜单: 3. 新建FTP站点: 4. 填写站点 ...

  6. 使用trace文件分析ANR

    2017年02月07日 12:32:45 不死鸟JGC 阅读数 13886更多 分类专栏: Android   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链 ...

  7. ospf的路由更新和撤销总结

    首先ospf 的报文有:hello报文,主要作用ospf 邻居建立及维护.dd报文,主要作用主从选举,序列号主从的确认,mtu的协商(可选).lsr 报文,主要作用向邻居请求lsa.lsu报文,主要作 ...

  8. 查找算法(7)--Hash search--哈希查找

    1.哈希查找 (1)什么是哈希表(Hash) 我们使用一个下标范围比较大的数组来存储元素.可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用 ...

  9. ThinkPHP5中模型关联关系一对一,一对多

    TP5 返回json反斜杠前面转义了class XinDai extends Controller{ public function index(){ $res = [ ['logo'=>'/i ...

  10. Classic BAdi and New BAdi

    Former Member Classic BAdi and New BAdi ... 2007年04月27日 04:43 | 1.5k Views Hi all, I have a question ...