redis系列之数据库与缓存数据一致性解决方案

数据库与缓存读写模式策略
写完数据库后是否需要马上更新缓存还是直接删除缓存?

(1)、如果写数据库的值与更新到缓存值是一样的,不需要经过任何的计算,可以马上更新缓存,但是如果对于那种写数据频繁而读数据少的场景并不合适这种解决方案,因为也许还没有查询就被删除或修改了,这样会浪费时间和资源

(2)、如果写数据库的值与更新缓存的值不一致,写入缓存中的数据需要经过几个表的关联计算后得到的结果插入缓存中,那就没有必要马上更新缓存,只有删除缓存即可,等到查询的时候在去把计算后得到的结果插入到缓存中即可。

所以一般的策略是当更新数据时,先删除缓存数据,然后更新数据库,而不是更新缓存,等要查询的时候才把最新的数据更新到缓存

数据库与缓存双写情况下导致数据不一致问题

场景一
当更新数据时,如更新某商品的库存,当前商品的库存是100,现在要更新为99,先更新数据库更改成99,然后删除缓存,发现删除缓存失败了,这意味着数据库存的是99,而缓存是100,这导致数据库和缓存不一致。

场景一解决方案

这种情况应该是先删除缓存,然后在更新数据库,如果删除缓存失败,那就不要更新数据库,如果说删除缓存成功,而更新数据库失败,那查询的时候只是从数据库里查了旧的数据而已,这样就能保持数据库与缓存的一致性。

场景二
在高并发的情况下,如果当删除完缓存的时候,这时去更新数据库,但还没有更新完,另外一个请求来查询数据,发现缓存里没有,就去数据库里查,还是以上面商品库存为例,如果数据库中产品的库存是100,那么查询到的库存是100,然后插入缓存,插入完缓存后,原来那个更新数据库的线程把数据库更新为了99,导致数据库与缓存不一致的情况

场景二解决方案
遇到这种情况,可以用队列的去解决这个问,创建几个队列,如20个,根据商品的ID去做hash值,然后对队列个数取摸,当有数据更新请求时,先把它丢到队列里去,当更新完后在从队列里去除,如果在更新的过程中,遇到以上场景,先去缓存里看下有没有数据,如果没有,可以先去队列里看是否有相同商品ID在做更新,如果有也把查询的请求发送到队列里去,然后同步等待缓存更新完成。
这里有一个优化点,如果发现队列里有一个查询请求了,那么就不要放新的查询操作进去了,用一个while(true)循环去查询缓存,循环个200MS左右,如果缓存里还没有则直接取数据库的旧数据,一般情况下是可以取到的。

在高并发下解决场景二要注意的问题
(1)读请求时长阻塞
 由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时间内返回,该解决方案最大的风险在于可能数据更新很频繁,导致队列中挤压了大量的更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库,像遇到这种情况,一般要做好足够的压力测试,如果压力过大,需要根据实际情况添加机器。
(2)请求并发量过高
 这里还是要做好压力测试,多模拟真实场景,并发量在最高的时候QPS多少,扛不住就要多加机器,还有就是做好读写比例是多少
(3)多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过nginx服务器路由到相同的服务实例上
(4)热点商品的路由问题,导致请求的倾斜
某些商品的读请求特别高,全部打到了相同的机器的相同丢列里了,可能造成某台服务器压力过大,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是很大,但是确实有可能某些服务器的负载会高一些。

数据库与缓存数据一致性解决方案流程图

数据库与缓存数据一致性解决方案对应代码
商品库存实体

  1. 1 package com.shux.inventory.entity;
  2. 2 /**
  3. 3 **********************************************
  4. 4 * 描述:
  5. 5 * Simba.Hua
  6. 6 * 2017年8月30日
  7. 7 **********************************************
  8. 8 **/
  9. 9 public class InventoryProduct {
  10. 10 private Integer productId;
  11. 11 private Long InventoryCnt;
  12. 12
  13. 13 public Integer getProductId() {
  14. 14 return productId;
  15. 15 }
  16. 16 public void setProductId(Integer productId) {
  17. 17 this.productId = productId;
  18. 18 }
  19. 19 public Long getInventoryCnt() {
  20. 20 return InventoryCnt;
  21. 21 }
  22. 22 public void setInventoryCnt(Long inventoryCnt) {
  23. 23 InventoryCnt = inventoryCnt;
  24. 24 }
  25. 25
  26. 26 }
  27. 27

请求接口

  1. 1 /**
  2. 2 **********************************************
  3. 3 * 描述:
  4. 4 * Simba.Hua
  5. 5 * 2017年8月27日
  6. 6 **********************************************
  7. 7 **/
  8. 8 public interface Request {
  9. 9 public void process();
  10. 10 public Integer getProductId();
  11. 11 public boolean isForceFefresh();
  12. 12 }

数据更新请求

  1. 1 package com.shux.inventory.request;
  2. 2
  3. 3 import org.springframework.transaction.annotation.Transactional;
  4. 4
  5. 5 import com.shux.inventory.biz.InventoryProductBiz;
  6. 6 import com.shux.inventory.entity.InventoryProduct;
  7. 7
  8. 8 /**
  9. 9 **********************************************
  10. 10 * 描述:更新库存信息
  11. 11 * 1、先删除缓存中的数据
  12. 12 * 2、更新数据库中的数据
  13. 13 * Simba.Hua
  14. 14 * 2017年8月30日
  15. 15 **********************************************
  16. 16 **/
  17. 17 public class InventoryUpdateDBRequest implements Request{
  18. 18 private InventoryProductBiz inventoryProductBiz;
  19. 19 private InventoryProduct inventoryProduct;
  20. 20
  21. 21 public InventoryUpdateDBRequest(InventoryProduct inventoryProduct,InventoryProductBiz inventoryProductBiz){
  22. 22 this.inventoryProduct = inventoryProduct;
  23. 23 this.inventoryProductBiz = inventoryProductBiz;
  24. 24 }
  25. 25 @Override
  26. 26 @Transactional
  27. 27 public void process() {
  28. 28 inventoryProductBiz.removeInventoryProductCache(inventoryProduct.getProductId());
  29. 29 inventoryProductBiz.updateInventoryProduct(inventoryProduct);
  30. 30 }
  31. 31 @Override
  32. 32 public Integer getProductId() {
  33. 33 // TODO Auto-generated method stub
  34. 34 return inventoryProduct.getProductId();
  35. 35 }
  36. 36 @Override
  37. 37 public boolean isForceFefresh() {
  38. 38 // TODO Auto-generated method stub
  39. 39 return false;
  40. 40 }
  41. 41
  42. 42 }

查询请求

  1. 1 package com.shux.inventory.request;
  2. 2
  3. 3 import com.shux.inventory.biz.InventoryProductBiz;
  4. 4 import com.shux.inventory.entity.InventoryProduct;
  5. 5
  6. 6 /**
  7. 7 **********************************************
  8. 8 * 描述:查询缓存数据
  9. 9 * 1、从数据库中查询
  10. 10 * 2、从数据库中查询后插入到缓存中
  11. 11 * Simba.Hua
  12. 12 * 2017年8月30日
  13. 13 **********************************************
  14. 14 **/
  15. 15 public class InventoryQueryCacheRequest implements Request {
  16. 16 private InventoryProductBiz inventoryProductBiz;
  17. 17 private Integer productId;
  18. 18 private boolean isForceFefresh;
  19. 19
  20. 20 public InventoryQueryCacheRequest(Integer productId,InventoryProductBiz inventoryProductBiz,boolean isForceFefresh) {
  21. 21 this.productId = productId;
  22. 22 this.inventoryProductBiz = inventoryProductBiz;
  23. 23 this.isForceFefresh = isForceFefresh;
  24. 24 }
  25. 25 @Override
  26. 26 public void process() {
  27. 27 InventoryProduct inventoryProduct = inventoryProductBiz.loadInventoryProductByProductId(productId);
  28. 28 inventoryProductBiz.setInventoryProductCache(inventoryProduct);
  29. 29 }
  30. 30 @Override
  31. 31 public Integer getProductId() {
  32. 32 // TODO Auto-generated method stub
  33. 33 return productId;
  34. 34 }
  35. 35 public boolean isForceFefresh() {
  36. 36 return isForceFefresh;
  37. 37 }
  38. 38 public void setForceFefresh(boolean isForceFefresh) {
  39. 39 this.isForceFefresh = isForceFefresh;
  40. 40 }
  41. 41
  42. 42
  43. 43 }

spring启动时初始化队列线程池

  1. 1 package com.shux.inventory.thread;
  2. 2
  3. 3 import java.util.concurrent.ArrayBlockingQueue;
  4. 4 import java.util.concurrent.ExecutorService;
  5. 5 import java.util.concurrent.Executors;
  6. 6
  7. 7 import com.shux.inventory.request.Request;
  8. 8 import com.shux.inventory.request.RequestQueue;
  9. 9 import com.shux.utils.other.SysConfigUtil;
  10. 10
  11. 11 /**
  12. 12 **********************************************
  13. 13 * 描述:请求处理线程池,初始化队列数及每个队列最多能处理的数量
  14. 14 * Simba.Hua
  15. 15 * 2017年8月27日
  16. 16 **********************************************
  17. 17 **/
  18. 18 public class RequestProcessorThreadPool {
  19. 19 private static final int blockingQueueNum = SysConfigUtil.get("request.blockingqueue.number")==null?10:Integer.valueOf(SysConfigUtil.get("request.blockingqueue.number").toString());
  20. 20 private static final int queueDataNum = SysConfigUtil.get("request.everyqueue.data.length")==null?100:Integer.valueOf(SysConfigUtil.get("request.everyqueue.data.length").toString());
  21. 21 private ExecutorService threadPool = Executors.newFixedThreadPool(blockingQueueNum);
  22. 22 private RequestProcessorThreadPool(){
  23. 23 for(int i=0;i<blockingQueueNum;i++){//初始化队列
  24. 24 ArrayBlockingQueue<Request> queue = new ArrayBlockingQueue<Request>(queueDataNum);//每个队列中放100条数据
  25. 25 RequestQueue.getInstance().addQueue(queue);
  26. 26 threadPool.submit(new RequestProcessorThread(queue));//把每个queue交个线程去处理,线程会处理每个queue中的数据
  27. 27 }
  28. 28 }
  29. 29 public static class Singleton{
  30. 30 private static RequestProcessorThreadPool instance;
  31. 31 static{
  32. 32 instance = new RequestProcessorThreadPool();
  33. 33 }
  34. 34 public static RequestProcessorThreadPool getInstance(){
  35. 35 return instance;
  36. 36 }
  37. 37 }
  38. 38 public static RequestProcessorThreadPool getInstance(){
  39. 39 return Singleton.getInstance();
  40. 40 }
  41. 41 /**
  42. 42 * 初始化线程池
  43. 43 */
  44. 44 public static void init(){
  45. 45 getInstance();
  46. 46 }
  47. 47 }

请求处理线程

  1. 1 package com.shux.inventory.thread;
  2. 2
  3. 3 import java.util.Map;
  4. 4 import java.util.concurrent.ArrayBlockingQueue;
  5. 5 import java.util.concurrent.Callable;
  6. 6
  7. 7 import com.shux.inventory.request.InventoryUpdateDBRequest;
  8. 8 import com.shux.inventory.request.Request;
  9. 9 import com.shux.inventory.request.RequestQueue;
  10. 10
  11. 11 /**
  12. 12 **********************************************
  13. 13 * 描述:请求处理线程
  14. 14 * Simba.Hua
  15. 15 * 2017年8月27日
  16. 16 **********************************************
  17. 17 **/
  18. 18 public class RequestProcessorThread implements Callable<Boolean>{
  19. 19 private ArrayBlockingQueue<Request> queue;
  20. 20 public RequestProcessorThread(ArrayBlockingQueue<Request> queue){
  21. 21 this.queue = queue;
  22. 22 }
  23. 23 @Override
  24. 24 public Boolean call() throws Exception {
  25. 25 Request request = queue.take();
  26. 26 Map<Integer,Boolean> flagMap = RequestQueue.getInstance().getFlagMap();
  27. 27 //不需要强制刷新的时候,查询请求去重处理
  28. 28 if (!request.isForceFefresh()){
  29. 29 if (request instanceof InventoryUpdateDBRequest) {//如果是更新请求,那就置为false
  30. 30 flagMap.put(request.getProductId(), true);
  31. 31 } else {
  32. 32 Boolean flag = flagMap.get(request.getProductId());
  33. 33 /**
  34. 34 * 标志位为空,有三种情况
  35. 35 * 1、没有过更新请求
  36. 36 * 2、没有查询请求
  37. 37 * 3、数据库中根本没有数据
  38. 38 * 在最初情况,一旦库存了插入了数据,那就好会在缓存中也会放一份数据,
  39. 39 * 但这种情况下有可能由于redis中内存满了,redis通过LRU算法把这个商品给清除了,导致缓存中没有数据
  40. 40 * 所以当标志位为空的时候,需要从数据库重查询一次,并且把标志位置为false,以便后面的请求能够从缓存中取
  41. 41 */
  42. 42 if ( flag == null) {
  43. 43 flagMap.put(request.getProductId(), false);
  44. 44 }
  45. 45 /**
  46. 46 * 如果不为空,并且flag为true,说明之前有一次更新请求,说明缓存中没有数据了(更新缓存会先删除缓存),
  47. 47 * 这个时候就要去刷新缓存,即从数据库中查询一次,并把标志位设置为false
  48. 48 */
  49. 49 if ( flag != null && flag) {
  50. 50 flagMap.put(request.getProductId(), false);
  51. 51 }
  52. 52 /**
  53. 53 * 这种情况说明之前有一个查询请求,并且把数据刷新到了缓存中,所以这时候就不用去刷新缓存了,直接返回就可以了
  54. 54 */
  55. 55 if (flag != null && !flag) {
  56. 56 flagMap.put(request.getProductId(), false);
  57. 57 return true;
  58. 58 }
  59. 59 }
  60. 60 }
  61. 61 request.process();
  62. 62 return true;
  63. 63 }
  64. 64
  65. 65 }

请求队列

  1. 1 package com.shux.inventory.request;
  2. 2
  3. 3 import java.util.ArrayList;
  4. 4 import java.util.List;
  5. 5 import java.util.Map;
  6. 6 import java.util.concurrent.ArrayBlockingQueue;
  7. 7 import java.util.concurrent.ConcurrentHashMap;
  8. 8
  9. 9 /**
  10. 10 **********************************************
  11. 11 * 描述:请求队列
  12. 12 * Simba.Hua
  13. 13 * 2017年8月27日
  14. 14 **********************************************
  15. 15 **/
  16. 16 public class RequestQueue {
  17. 17 private List<ArrayBlockingQueue<Request>> queues = new ArrayList<>();
  18. 18
  19. 19 private Map<Integer,Boolean> flagMap = new ConcurrentHashMap<>();
  20. 20 private RequestQueue(){
  21. 21
  22. 22 }
  23. 23 private static class Singleton{
  24. 24 private static RequestQueue queue;
  25. 25 static{
  26. 26 queue = new RequestQueue();
  27. 27 }
  28. 28 public static RequestQueue getInstance() {
  29. 29 return queue;
  30. 30 }
  31. 31 }
  32. 32
  33. 33 public static RequestQueue getInstance(){
  34. 34 return Singleton.getInstance();
  35. 35 }
  36. 36 public void addQueue(ArrayBlockingQueue<Request> queue) {
  37. 37 queues.add(queue);
  38. 38 }
  39. 39
  40. 40 public int getQueueSize(){
  41. 41 return queues.size();
  42. 42 }
  43. 43 public ArrayBlockingQueue<Request> getQueueByIndex(int index) {
  44. 44 return queues.get(index);
  45. 45 }
  46. 46
  47. 47 public Map<Integer,Boolean> getFlagMap() {
  48. 48 return this.flagMap;
  49. 49 }
  50. 50 }

spring 启动初始化线程池类

  1. 1 package com.shux.inventory.listener;
  2. 2
  3. 3 import org.springframework.context.ApplicationListener;
  4. 4 import org.springframework.context.event.ContextRefreshedEvent;
  5. 5
  6. 6 import com.shux.inventory.thread.RequestProcessorThreadPool;
  7. 7
  8. 8 /**
  9. 9 **********************************************
  10. 10 * 描述:spring 启动初始化线程池类
  11. 11 * Simba.Hua
  12. 12 * 2017年8月27日
  13. 13 **********************************************
  14. 14 **/
  15. 15 public class InitListener implements ApplicationListener<ContextRefreshedEvent>{
  16. 16
  17. 17 @Override
  18. 18 public void onApplicationEvent(ContextRefreshedEvent event) {
  19. 19 // TODO Auto-generated method stub
  20. 20 if(event.getApplicationContext().getParent() != null){
  21. 21 return;
  22. 22 }
  23. 23 RequestProcessorThreadPool.init();
  24. 24 }
  25. 25 }

异步处理请求接口

  1. 1 package com.shux.inventory.biz;
  2. 2
  3. 3 import com.shux.inventory.request.Request;
  4. 4
  5. 5 /**
  6. 6 **********************************************
  7. 7 * 描述:请求异步处理接口,用于路由队列并把请求加入到队列中
  8. 8 * Simba.Hua
  9. 9 * 2017年8月30日
  10. 10 **********************************************
  11. 11 **/
  12. 12 public interface IRequestAsyncProcessBiz {
  13. 13 void process(Request request);
  14. 14 }
  15. 15

异步处理请求接口实现

  1. 1 package com.shux.inventory.biz.impl;
  2. 2
  3. 3 import java.util.concurrent.ArrayBlockingQueue;
  4. 4
  5. 5 import org.slf4j.Logger;
  6. 6 import org.slf4j.LoggerFactory;
  7. 7 import org.springframework.stereotype.Service;
  8. 8
  9. 9 import com.shux.inventory.biz.IRequestAsyncProcessBiz;
  10. 10 import com.shux.inventory.request.Request;
  11. 11 import com.shux.inventory.request.RequestQueue;
  12. 12
  13. 13
  14. 14 /**
  15. 15 **********************************************
  16. 16 * 描述:异步处理请求,用于路由队列并把请求加入到队列中
  17. 17 * Simba.Hua
  18. 18 * 2017年8月30日
  19. 19 **********************************************
  20. 20 **/
  21. 21 @Service("requestAsyncProcessService")
  22. 22 public class RequestAsyncProcessBizImpl implements IRequestAsyncProcessBiz {
  23. 23 private Logger logger = LoggerFactory.getLogger(getClass());
  24. 24 @Override
  25. 25 public void process(Request request) {
  26. 26 // 做请求的路由,根据productId路由到对应的队列
  27. 27 ArrayBlockingQueue<Request> queue = getQueueByProductId(request.getProductId());
  28. 28 try {
  29. 29 queue.put(request);
  30. 30 } catch (InterruptedException e) {
  31. 31 logger.error("产品ID{}加入队列失败",request.getProductId(),e);
  32. 32 }
  33. 33 }
  34. 34
  35. 35 private ArrayBlockingQueue<Request> getQueueByProductId(Integer productId) {
  36. 36 RequestQueue requestQueue = RequestQueue.getInstance();
  37. 37 String key = String.valueOf(productId);
  38. 38 int hashcode;
  39. 39 int hash = (key == null) ? 0 : (hashcode = key.hashCode())^(hashcode >>> 16);
  40. 40 //对hashcode取摸
  41. 41 int index = (requestQueue.getQueueSize()-1) & hash;
  42. 42 return requestQueue.getQueueByIndex(index);
  43. 43 }
  44. 44
  45. 45
  46. 46
  47. 47 }
  1. 1 package com.shux.inventory.biz.impl;
  2. 2
  3. 3 import javax.annotation.Resource;
  4. 4
  5. 5 import org.springframework.beans.factory.annotation.Autowired;
  6. 6 import org.springframework.stereotype.Service;
  7. 7
  8. 8 import com.shux.inventory.biz.InventoryProductBiz;
  9. 9 import com.shux.inventory.entity.InventoryProduct;
  10. 10 import com.shux.inventory.mapper.InventoryProductMapper;
  11. 11 import com.shux.redis.biz.IRedisBiz;
  12. 12
  13. 13 /**
  14. 14 **********************************************
  15. 15 * 描述
  16. 16 * Simba.Hua
  17. 17 * 2017年8月30日
  18. 18 **********************************************
  19. 19 **/
  20. 20 @Service("inventoryProductBiz")
  21. 21 public class InventoryProductBizImpl implements InventoryProductBiz {
  22. 22 private @Autowired IRedisBiz<InventoryProduct> redisBiz;
  23. 23 private @Resource InventoryProductMapper mapper;
  24. 24 @Override
  25. 25 public void updateInventoryProduct(InventoryProduct inventoryProduct) {
  26. 26 // TODO Auto-generated method stub
  27. 27 mapper.updateInventoryProduct(inventoryProduct);
  28. 28 }
  29. 29
  30. 30 @Override
  31. 31 public InventoryProduct loadInventoryProductByProductId(Integer productId) {
  32. 32 // TODO Auto-generated method stub
  33. 33 return mapper.loadInventoryProductByProductId(productId);
  34. 34 }
  35. 35
  36. 36 @Override
  37. 37 public void setInventoryProductCache(InventoryProduct inventoryProduct) {
  38. 38 redisBiz.set("inventoryProduct:"+inventoryProduct.getProductId(), inventoryProduct);
  39. 39
  40. 40 }
  41. 41
  42. 42 @Override
  43. 43 public void removeInventoryProductCache(Integer productId) {
  44. 44 redisBiz.delete("inventoryProduct:"+productId);
  45. 45
  46. 46 }
  47. 47
  48. 48 @Override
  49. 49 public InventoryProduct loadInventoryProductCache(Integer productId) {
  50. 50 // TODO Auto-generated method stub
  51. 51 return redisBiz.get("inventoryProduct:"+productId);
  52. 52 }
  53. 53
  54. 54 }

数据更新请求controller

  1. 1 package com.shux.inventory.biz.impl;
  2. 2
  3. 3 import javax.annotation.Resource;
  4. 4
  5. 5 import org.springframework.beans.factory.annotation.Autowired;
  6. 6 import org.springframework.stereotype.Service;
  7. 7
  8. 8 import com.shux.inventory.biz.InventoryProductBiz;
  9. 9 import com.shux.inventory.entity.InventoryProduct;
  10. 10 import com.shux.inventory.mapper.InventoryProductMapper;
  11. 11 import com.shux.redis.biz.IRedisBiz;
  12. 12
  13. 13 /**
  14. 14 **********************************************
  15. 15 * 描述
  16. 16 * Simba.Hua
  17. 17 * 2017年8月30日
  18. 18 **********************************************
  19. 19 **/
  20. 20 @Service("inventoryProductBiz")
  21. 21 public class InventoryProductBizImpl implements InventoryProductBiz {
  22. 22 private @Autowired IRedisBiz<InventoryProduct> redisBiz;
  23. 23 private @Resource InventoryProductMapper mapper;
  24. 24 @Override
  25. 25 public void updateInventoryProduct(InventoryProduct inventoryProduct) {
  26. 26 // TODO Auto-generated method stub
  27. 27 mapper.updateInventoryProduct(inventoryProduct);
  28. 28 }
  29. 29
  30. 30 @Override
  31. 31 public InventoryProduct loadInventoryProductByProductId(Integer productId) {
  32. 32 // TODO Auto-generated method stub
  33. 33 return mapper.loadInventoryProductByProductId(productId);
  34. 34 }
  35. 35
  36. 36 @Override
  37. 37 public void setInventoryProductCache(InventoryProduct inventoryProduct) {
  38. 38 redisBiz.set("inventoryProduct:"+inventoryProduct.getProductId(), inventoryProduct);
  39. 39
  40. 40 }
  41. 41
  42. 42 @Override
  43. 43 public void removeInventoryProductCache(Integer productId) {
  44. 44 redisBiz.delete("inventoryProduct:"+productId);
  45. 45
  46. 46 }
  47. 47
  48. 48 @Override
  49. 49 public InventoryProduct loadInventoryProductCache(Integer productId) {
  50. 50 // TODO Auto-generated method stub
  51. 51 return redisBiz.get("inventoryProduct:"+productId);
  52. 52 }
  53. 53
  54. 54 }

数据查询请求controller

  1. 1 package com.shux.inventory.controller;
  2. 2
  3. 3 import org.springframework.beans.factory.annotation.Autowired;
  4. 4 import org.springframework.stereotype.Controller;
  5. 5 import org.springframework.web.bind.annotation.RequestMapping;
  6. 6
  7. 7 import com.shux.inventory.biz.IRequestAsyncProcessBiz;
  8. 8 import com.shux.inventory.biz.InventoryProductBiz;
  9. 9 import com.shux.inventory.entity.InventoryProduct;
  10. 10 import com.shux.inventory.request.InventoryQueryCacheRequest;
  11. 11 import com.shux.inventory.request.Request;
  12. 12
  13. 13 /**
  14. 14 **********************************************
  15. 15 * 描述:提交查询请求
  16. 16 * 1、先从缓存中取数据
  17. 17 * 2、如果能从缓存中取到数据,则返回
  18. 18 * 3、如果不能从缓存取到数据,则等待20毫秒,然后再次去数据,直到200毫秒,如果超过200毫秒还不能取到数据,则从数据库中取,并强制刷新缓存数据
  19. 19 * Simba.Hua
  20. 20 * 2017年9月1日
  21. 21 **********************************************
  22. 22 **/
  23. 23 @Controller("/inventory")
  24. 24 public class InventoryQueryCacheController {
  25. 25 private @Autowired InventoryProductBiz inventoryProductBiz;
  26. 26 private @Autowired IRequestAsyncProcessBiz requestAsyncProcessBiz;
  27. 27 @RequestMapping("/queryInventoryProduct")
  28. 28 public InventoryProduct queryInventoryProduct(Integer productId) {
  29. 29 Request request = new InventoryQueryCacheRequest(productId,inventoryProductBiz,false);
  30. 30 requestAsyncProcessBiz.process(request);//加入到队列中
  31. 31 long startTime = System.currentTimeMillis();
  32. 32 long allTime = 0L;
  33. 33 long endTime = 0L;
  34. 34 InventoryProduct inventoryProduct = null;
  35. 35 while (true) {
  36. 36 if (allTime > 200){//如果超过了200ms,那就直接退出,然后从数据库中查询
  37. 37 break;
  38. 38 }
  39. 39 try {
  40. 40 inventoryProduct = inventoryProductBiz.loadInventoryProductCache(productId);
  41. 41 if (inventoryProduct != null) {
  42. 42 return inventoryProduct;
  43. 43 } else {
  44. 44 Thread.sleep(20);//如果查询不到就等20毫秒
  45. 45 }
  46. 46 endTime = System.currentTimeMillis();
  47. 47 allTime = endTime - startTime;
  48. 48 } catch (Exception e) {
  49. 49 }
  50. 50 }
  51. 51 /**
  52. 52 * 代码执行到这来,只有以下三种情况
  53. 53 * 1、缓存中本来有数据,由于redis内存满了,redis通过LRU算法清除了缓存,导致数据没有了
  54. 54 * 2、由于之前数据库查询比较慢或者内存太小处理不过来队列中的数据,导致队列里挤压了很多的数据,所以一直没有从数据库中获取数据然后插入到缓存中
  55. 55 * 3、数据库中根本没有这样的数据,这种情况叫数据穿透,一旦别人知道这个商品没有,如果一直执行查询,就会一直查询数据库,如果过多,那么有可能会导致数据库瘫痪
  56. 56 */
  57. 57 inventoryProduct = inventoryProductBiz.loadInventoryProductByProductId(productId);
  58. 58 if (inventoryProduct != null) {
  59. 59 Request forcRrequest = new InventoryQueryCacheRequest(productId,inventoryProductBiz,true);
  60. 60 requestAsyncProcessBiz.process(forcRrequest);//这个时候需要强制刷新数据库,使缓存中有数据
  61. 61 return inventoryProduct;
  62. 62 }
  63. 63 return null;
  64. 64
  65. 65 }
  66. 66 }

解决Redis中数据不一致问题的更多相关文章

  1. 使用Sqoop,最终导入到hive中的数据和原数据库中数据不一致解决办法

            Sqoop是一款开源的工具,主要用于在Hadoop(Hive)与传统的数据库(mysql.postgresql...)间进行数据的传递,可以将一个关系型数据库(例如 : MySQL , ...

  2. 如何解决Redis中的key过期问题

    最近我们在Redis集群中发现了一个有趣的问题.在花费大量时间进行调试和测试后,通过更改key过期,我们可以将某些集群中的Redis内存使用量减少25%. Twitter内部运行着多个缓存服务.其中一 ...

  3. BlockingQueue阻塞队列(解决多线程中数据安全问题 可用于抢票,秒杀)

    案例:一个线程类中 private static BlockingQueue<Map<String, String>> dataQueue = new LinkedBlocki ...

  4. 解决InputStream中数据读取不完整问题

    转载:https://blog.csdn.net/lilidejing/article/details/37913627 当需要用到InputStream获取数据时,这时就需要读取InputStrea ...

  5. Redis 中如何保证数据的不丢失,Redis 中的持久化是如何进行

    Redis 中数据的持久化 前言 AOF 持久化 什么是 AOF 持久化 为什么要后记录日志呢 AOF 的潜在风险 AOF 文件的写入和同步 AOF 文件重写机制 AOF 的数据还原 RDB 持久化 ...

  6. SpringBoot 结合 Spring Cache 操作 Redis 实现数据缓存

    系统环境: Redis 版本:5.0.7 SpringBoot 版本:2.2.2.RELEASE 参考地址: Redus 官方网址:https://redis.io/ 博文示例项目 Github 地址 ...

  7. (Redis基础教程之六)如何使用Redis中的List

    如何在ubuntu18.04上安装和保护redis 如何连接到Redis数据库 如何管理Redis数据库和Keys 如何在Redis中管理副本和客户端 如何在Redis中管理字符串 如何在Redis中 ...

  8. redis中关于过期键的删除策略

    我们已经了解到了Redis是一种内存数据库,Redis中数据都是以key-value的形式存储在内存中.由Redisserver来维护和管理这部分内存,内存是何足珍贵,不须要的数据或者是已经使用过的无 ...

  9. Redis面试题记录--缓存双写情况下导致数据不一致问题

    转载自:https://blog.csdn.net/lzhcoder/article/details/79469123 https://blog.csdn.net/u013374645/article ...

随机推荐

  1. Qt导入CMakeLists.txt后无法调试

    问题: Qt导入CMakeLists.txt后无法单步调试 解决方法: 在CMakeLists.txt后加入一句: SET(CMAKE_BUILD_TYPE DEBUG)

  2. 深度对比Apache CarbonData、Hudi和Open Delta三大开源数据湖方案

    摘要:今天我们就来解构数据湖的核心需求,同时深度对比Apache CarbonData.Hudi和Open Delta三大解决方案,帮助用户更好地针对自身场景来做数据湖方案选型. 背景 我们已经看到, ...

  3. xlrd、xlwt常用命令

    # -*- coding: utf-8 -*- import xlrd import xlwt from datetime import date,datetime   def read_excel( ...

  4. Git系列:常用命令

    一.背景 作为一名程序员,怎么能不懂Git那些常用命令呢?于是花费一点时间来总结Git命令.关于安装的话,就不讲了. 二.常用命令 1.配置全局的用户名称和用户邮箱 git config --glob ...

  5. linux上性能调优常用命令及简介

    1.综合命令:nmon.top:topas(aix) d :磁盘相关 c:cpu相关 m:内存相关 2.磁盘 2.1 测试顺序写性能dd if=/dev/zero of=/cdr/test.data ...

  6. 关于maven下,lombok的安装

    1.首先下载lombok的jar包,可至https://mvnrepository.com/下载 2.双击即会自动扫描eclipse.exe,如图: 选择eclipse.exe,点击install/u ...

  7. K8s之实践Pod深入理解

      K8s之实践Pod深入理解 1.同一pod下的nginx+php+mysql nginx+php+mysql.yaml文件 --- apiVersion: v1 kind: Secret meta ...

  8. Flink的DataSource三部曲之一:直接API

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. Java_三大特征相关

    重写 子类通过重写父类的方法, 可以用自身的行为替换父类的行为 重写的三个要点: "==" :方法名, 形参列表, 返回值类型相同 "<=" : 返回值类 ...

  10. ESP8266交叉编译环境变量设置

    在build目录下执行sudo cp -r xtensa-lx106-elf /opt 修改xtensa-lx106-elf目录权限:这一步非常重要,否则在后续编译中很容易出现没有权限问题:sudo ...