详解redis缓存与数据库一致性问题解决

数据库与缓存读写模式策略

写完数据库后是否需要马上更新缓存还是直接删除缓存?

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

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

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

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

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

场景一解决方案

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

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

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

在高并发下解决场景二要注意的问题

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

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

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

商品库存实体

package com.shux.inventory.entity;
/**
 **********************************************
 * 描述:
 * Simba.Hua
 * 2017年8月30日
 **********************************************
**/
public class InventoryProduct {
 private Integer productId;
 private Long InventoryCnt;

 public Integer getProductId() {
  return productId;
 }
 public void setProductId(Integer productId) {
  this.productId = productId;
 }
 public Long getInventoryCnt() {
  return InventoryCnt;
 }
 public void setInventoryCnt(Long inventoryCnt) {
  InventoryCnt = inventoryCnt;
 }

}

请求接口

/**
 **********************************************
 * 描述:
 * Simba.Hua
 * 2017年8月27日
 **********************************************
**/
public interface Request {
 public void process();
 public Integer getProductId();
 public boolean isForceFefresh();
}

数据更新请求

package com.shux.inventory.request;

import org.springframework.transaction.annotation.Transactional;

import com.shux.inventory.biz.InventoryProductBiz;
import com.shux.inventory.entity.InventoryProduct;

/**
 **********************************************
 * 描述:更新库存信息
 * 1、先删除缓存中的数据
 * 2、更新数据库中的数据
 * Simba.Hua
 * 2017年8月30日
 **********************************************
**/
public class InventoryUpdateDBRequest implements Request{
 private InventoryProductBiz inventoryProductBiz;
 private InventoryProduct inventoryProduct;

 public InventoryUpdateDBRequest(InventoryProduct inventoryProduct,InventoryProductBiz inventoryProductBiz){
  this.inventoryProduct = inventoryProduct;
  this.inventoryProductBiz = inventoryProductBiz;
 }
 @Override
 @Transactional
 public void process() {
  inventoryProductBiz.removeInventoryProductCache(inventoryProduct.getProductId());
  inventoryProductBiz.updateInventoryProduct(inventoryProduct);
 }
 @Override
 public Integer getProductId() {
  // TODO Auto-generated method stub
  return inventoryProduct.getProductId();
 }
 @Override
 public boolean isForceFefresh() {
  // TODO Auto-generated method stub
  return false;
 }

}

查询请求

package com.shux.inventory.request;

import com.shux.inventory.biz.InventoryProductBiz;
import com.shux.inventory.entity.InventoryProduct;

/**
 **********************************************
 * 描述:查询缓存数据
 * 1、从数据库中查询
 * 2、从数据库中查询后插入到缓存中
 * Simba.Hua
 * 2017年8月30日
 **********************************************
**/
public class InventoryQueryCacheRequest implements Request {
 private InventoryProductBiz inventoryProductBiz;
 private Integer productId;
 private boolean isForceFefresh;

 public InventoryQueryCacheRequest(Integer productId,InventoryProductBiz inventoryProductBiz,boolean isForceFefresh) {
  this.productId = productId;
  this.inventoryProductBiz = inventoryProductBiz;
  this.isForceFefresh = isForceFefresh;
 }
 @Override
 public void process() {
  InventoryProduct inventoryProduct = inventoryProductBiz.loadInventoryProductByProductId(productId);
  inventoryProductBiz.setInventoryProductCache(inventoryProduct);
 }
 @Override
 public Integer getProductId() {
  // TODO Auto-generated method stub
  return productId;
 }
 public boolean isForceFefresh() {
  return isForceFefresh;
 }
 public void setForceFefresh(boolean isForceFefresh) {
  this.isForceFefresh = isForceFefresh;
 }

}

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

package com.shux.inventory.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.shux.inventory.request.Request;
import com.shux.inventory.request.RequestQueue;
import com.shux.utils.other.SysConfigUtil;

/**
 **********************************************
 * 描述:请求处理线程池,初始化队列数及每个队列最多能处理的数量
 * Simba.Hua
 * 2017年8月27日
 **********************************************
**/
public class RequestProcessorThreadPool {
 private static final int blockingQueueNum = SysConfigUtil.get("request.blockingqueue.number")==null?10:Integer.valueOf(SysConfigUtil.get("request.blockingqueue.number").toString());
 private static final int queueDataNum = SysConfigUtil.get("request.everyqueue.data.length")==null?100:Integer.valueOf(SysConfigUtil.get("request.everyqueue.data.length").toString());
 private ExecutorService threadPool = Executors.newFixedThreadPool(blockingQueueNum);
 private RequestProcessorThreadPool(){
  for(int i=0;i<blockingQueueNum;i++){//初始化队列
   ArrayBlockingQueue<Request> queue = new ArrayBlockingQueue<Request>(queueDataNum);//每个队列中放100条数据
   RequestQueue.getInstance().addQueue(queue);
   threadPool.submit(new RequestProcessorThread(queue));//把每个queue交个线程去处理,线程会处理每个queue中的数据
  }
 }
 public static class Singleton{
  private static RequestProcessorThreadPool instance;
  static{
   instance = new RequestProcessorThreadPool();
  }
  public static RequestProcessorThreadPool getInstance(){
   return instance;
  }
 }
 public static RequestProcessorThreadPool getInstance(){
  return Singleton.getInstance();
 }
 /**
  * 初始化线程池
  */
 public static void init(){
  getInstance();
 }
}

请求处理线程

package com.shux.inventory.thread;

import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;

import com.shux.inventory.request.InventoryUpdateDBRequest;
import com.shux.inventory.request.Request;
import com.shux.inventory.request.RequestQueue;

/**
 **********************************************
 * 描述:请求处理线程
 * Simba.Hua
 * 2017年8月27日
 **********************************************
**/
public class RequestProcessorThread implements Callable<Boolean>{
 private ArrayBlockingQueue<Request> queue;
 public RequestProcessorThread(ArrayBlockingQueue<Request> queue){
  this.queue = queue;
 }
 @Override
 public Boolean call() throws Exception {
  Request request = queue.take();
  Map<Integer,Boolean> flagMap = RequestQueue.getInstance().getFlagMap();
  //不需要强制刷新的时候,查询请求去重处理
   if (!request.isForceFefresh()){
    if (request instanceof InventoryUpdateDBRequest) {//如果是更新请求,那就置为false
     flagMap.put(request.getProductId(), true);
    } else {
     Boolean flag = flagMap.get(request.getProductId());
     /**
     * 标志位为空,有三种情况
     * 1、没有过更新请求
     * 2、没有查询请求
     * 3、数据库中根本没有数据
     * 在最初情况,一旦库存了插入了数据,那就好会在缓存中也会放一份数据,
     * 但这种情况下有可能由于redis中内存满了,redis通过LRU算法把这个商品给清除了,导致缓存中没有数据
     * 所以当标志位为空的时候,需要从数据库重查询一次,并且把标志位置为false,以便后面的请求能够从缓存中取
     */
     if ( flag == null) {
      flagMap.put(request.getProductId(), false);
     }
     /**
     * 如果不为空,并且flag为true,说明之前有一次更新请求,说明缓存中没有数据了(更新缓存会先删除缓存),
     * 这个时候就要去刷新缓存,即从数据库中查询一次,并把标志位设置为false
     */
     if ( flag != null && flag) {
      flagMap.put(request.getProductId(), false);
     }
     /**
     * 这种情况说明之前有一个查询请求,并且把数据刷新到了缓存中,所以这时候就不用去刷新缓存了,直接返回就可以了
     */
     if (flag != null && !flag) {
      flagMap.put(request.getProductId(), false);
      return true;
     }
    }
   }
   request.process();
  return true;
 }
}

请求队列

package com.shux.inventory.request;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;

/**
 **********************************************
 * 描述:请求队列
 * Simba.Hua
 * 2017年8月27日
 **********************************************
**/
public class RequestQueue {
 private List<ArrayBlockingQueue<Request>> queues = new ArrayList<>();

 private Map<Integer,Boolean> flagMap = new ConcurrentHashMap<>();
 private RequestQueue(){

 }
 private static class Singleton{
  private static RequestQueue queue;
  static{
   queue = new RequestQueue();
  }
  public static RequestQueue getInstance() {
   return queue;
  }
 }

 public static RequestQueue getInstance(){
  return Singleton.getInstance();
 }
 public void addQueue(ArrayBlockingQueue<Request> queue) {
  queues.add(queue);
 }

 public int getQueueSize(){
  return queues.size();
 }
 public ArrayBlockingQueue<Request> getQueueByIndex(int index) {
  return queues.get(index);
 }

 public Map<Integer,Boolean> getFlagMap() {
  return this.flagMap;
 }
}

spring 启动初始化线程池类

package com.shux.inventory.listener;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import com.shux.inventory.thread.RequestProcessorThreadPool;

/**
 **********************************************
 * 描述:spring 启动初始化线程池类
 * Simba.Hua
 * 2017年8月27日
 **********************************************
**/
public class InitListener implements ApplicationListener<ContextRefreshedEvent>{

 @Override
 public void onApplicationEvent(ContextRefreshedEvent event) {
  // TODO Auto-generated method stub
  if(event.getApplicationContext().getParent() != null){
   return;
  }
  RequestProcessorThreadPool.init();
 }
}

异步处理请求接口

package com.shux.inventory.biz;
import com.shux.inventory.request.Request;

/**
 **********************************************
 * 描述:请求异步处理接口,用于路由队列并把请求加入到队列中
 * Simba.Hua
 * 2017年8月30日
 **********************************************
**/
public interface IRequestAsyncProcessBiz {
 void process(Request request);
}

异步处理请求接口实现

package com.shux.inventory.biz.impl;

import java.util.concurrent.ArrayBlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.shux.inventory.biz.IRequestAsyncProcessBiz;
import com.shux.inventory.request.Request;
import com.shux.inventory.request.RequestQueue;

/**
 **********************************************
 * 描述:异步处理请求,用于路由队列并把请求加入到队列中
 * Simba.Hua
 * 2017年8月30日
 **********************************************
**/
@Service("requestAsyncProcessService")
public class RequestAsyncProcessBizImpl implements IRequestAsyncProcessBiz {
 private Logger logger = LoggerFactory.getLogger(getClass());
 @Override
 public void process(Request request) {
  // 做请求的路由,根据productId路由到对应的队列
  ArrayBlockingQueue<Request> queue = getQueueByProductId(request.getProductId());
  try {
   queue.put(request);
  } catch (InterruptedException e) {
   logger.error("产品ID{}加入队列失败",request.getProductId(),e);
  }
 }

 private ArrayBlockingQueue<Request> getQueueByProductId(Integer productId) {
  RequestQueue requestQueue = RequestQueue.getInstance();
  String key = String.valueOf(productId);
  int hashcode;
  int hash = (key == null) ? 0 : (hashcode = key.hashCode())^(hashcode >>> 16);
  //对hashcode取摸
  int index = (requestQueue.getQueueSize()-1) & hash;
  return requestQueue.getQueueByIndex(index);
 }
}
package com.shux.inventory.biz.impl;
import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.shux.inventory.biz.InventoryProductBiz;
import com.shux.inventory.entity.InventoryProduct;
import com.shux.inventory.mapper.InventoryProductMapper;
import com.shux.redis.biz.IRedisBiz;

/**
 **********************************************
 * 描述
 * Simba.Hua
 * 2017年8月30日
 **********************************************
**/
@Service("inventoryProductBiz")
public class InventoryProductBizImpl implements InventoryProductBiz {
 private @Autowired IRedisBiz<InventoryProduct> redisBiz;
 private @Resource InventoryProductMapper mapper;
 @Override
 public void updateInventoryProduct(InventoryProduct inventoryProduct) {
  // TODO Auto-generated method stub
  mapper.updateInventoryProduct(inventoryProduct);
 }

 @Override
 public InventoryProduct loadInventoryProductByProductId(Integer productId) {
  // TODO Auto-generated method stub
  return mapper.loadInventoryProductByProductId(productId);
 }

 @Override
 public void setInventoryProductCache(InventoryProduct inventoryProduct) {
  redisBiz.set("inventoryProduct:"+inventoryProduct.getProductId(), inventoryProduct);

 }

 @Override
 public void removeInventoryProductCache(Integer productId) {
  redisBiz.delete("inventoryProduct:"+productId);

 }

 @Override
 public InventoryProduct loadInventoryProductCache(Integer productId) {
  // TODO Auto-generated method stub
  return redisBiz.get("inventoryProduct:"+productId);
 }
}

数据更新请求controller

package com.shux.inventory.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.shux.inventory.biz.IRequestAsyncProcessBiz;
import com.shux.inventory.biz.InventoryProductBiz;
import com.shux.inventory.entity.InventoryProduct;
import com.shux.inventory.request.InventoryUpdateDBRequest;
import com.shux.inventory.request.Request;
import com.shux.utils.other.Response;

/**
 **********************************************
 * 描述:提交更新请求
 * Simba.Hua
 * 2017年9月1日
 **********************************************
**/
@Controller("/inventory")
public class InventoryUpdateDBController {
 private @Autowired InventoryProductBiz inventoryProductBiz;
 private @Autowired IRequestAsyncProcessBiz requestAsyncProcessBiz;
 @RequestMapping("/updateDBInventoryProduct")
 @ResponseBody
 public Response updateDBInventoryProduct(InventoryProduct inventoryProduct){
  Request request = new InventoryUpdateDBRequest(inventoryProduct,inventoryProductBiz);
  requestAsyncProcessBiz.process(request);
  return new Response(Response.SUCCESS,"更新成功");
 }
}

数据查询请求controller

package com.shux.inventory.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.shux.inventory.biz.IRequestAsyncProcessBiz;
import com.shux.inventory.biz.InventoryProductBiz;
import com.shux.inventory.entity.InventoryProduct;
import com.shux.inventory.request.InventoryQueryCacheRequest;
import com.shux.inventory.request.Request;

/**
 **********************************************
 * 描述:提交查询请求
 * 1、先从缓存中取数据
 * 2、如果能从缓存中取到数据,则返回
 * 3、如果不能从缓存取到数据,则等待20毫秒,然后再次去数据,直到200毫秒,如果超过200毫秒还不能取到数据,则从数据库中取,并强制刷新缓存数据
 * Simba.Hua
 * 2017年9月1日
 **********************************************
**/
@Controller("/inventory")
public class InventoryQueryCacheController {
 private @Autowired InventoryProductBiz inventoryProductBiz;
 private @Autowired IRequestAsyncProcessBiz requestAsyncProcessBiz;
 @RequestMapping("/queryInventoryProduct")
 public InventoryProduct queryInventoryProduct(Integer productId) {
   Request request = new InventoryQueryCacheRequest(productId,inventoryProductBiz,false);
   requestAsyncProcessBiz.process(request);//加入到队列中
   long startTime = System.currentTimeMillis();
   long allTime = 0L;
   long endTime = 0L;
   InventoryProduct inventoryProduct = null;
   while (true) {
    if (allTime > 200){//如果超过了200ms,那就直接退出,然后从数据库中查询
     break;
    }
    try {
     inventoryProduct = inventoryProductBiz.loadInventoryProductCache(productId);
     if (inventoryProduct != null) {
      return inventoryProduct;
     } else {
      Thread.sleep(20);//如果查询不到就等20毫秒
     }
     endTime = System.currentTimeMillis();
     allTime = endTime - startTime;
    } catch (Exception e) {
    }
   }
   /**
   * 代码执行到这来,只有以下三种情况
   * 1、缓存中本来有数据,由于redis内存满了,redis通过LRU算法清除了缓存,导致数据没有了
   * 2、由于之前数据库查询比较慢或者内存太小处理不过来队列中的数据,导致队列里挤压了很多的数据,所以一直没有从数据库中获取数据然后插入到缓存中
   * 3、数据库中根本没有这样的数据,这种情况叫数据穿透,一旦别人知道这个商品没有,如果一直执行查询,就会一直查询数据库,如果过多,那么有可能会导致数据库瘫痪
   */
   inventoryProduct = inventoryProductBiz.loadInventoryProductByProductId(productId);
   if (inventoryProduct != null) {
    Request forcRrequest = new InventoryQueryCacheRequest(productId,inventoryProductBiz,true);
    requestAsyncProcessBiz.process(forcRrequest);//这个时候需要强制刷新数据库,使缓存中有数据
    return inventoryProduct;
   }
   return null;

  }
}

到此这篇关于详解redis缓存与数据库一致性问题解决的文章就介绍到这了,更多相关redis缓存与数据库一致性内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Redis整合Spring结合使用缓存实例

    一.Redis介绍 什么是Redis?       redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型).这些数据类型都支持push/pop.add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的.在此基础上,redis支持各种不同方式的排序.与memcached一样,为了保证效率,数据都是

  • 【Redis缓存机制】详解Java连接Redis_Jedis_事务

    Jedis事务 我们使用JDBC连接Mysql的时候,每次执行sql语句之前,都需要开启事务:在MyBatis中,也需要使用openSession()来获取session事务对象,来进行sql执行.查询等操作.当我们对数据库的操作结束的时候,是事务对象负责关闭数据库连接. 事务对象用于管理.执行各种数据库操作的动作.它能够开启和关闭数据库连接,执行sql语句,回滚错误的操作. 我们的Redis也有事务管理对象,其位于redis.clients.jedis.Transaction下. Jedis事

  • 详解redis与spring的整合(使用缓存)

    1.实现目标 通过redis缓存数据.(目的不是加快查询的速度,而是减少数据库的负担) 2.所需jar包 注意:jdies和commons-pool两个jar的版本是有对应关系的,注意引入jar包是要配对使用,否则将会报错.因为commons-pooljar的目录根据版本的变化,目录结构会变.前面的版本是org.apache.pool,而后面的版本是org.apache.pool2... 3.redis简介 redis是一个key-value存储系统.和Memcached类似,它支持存储的val

  • Redis缓存详解

    下面来正式分享今天的文章吧: .搭建Redis服务端,并用客户端连接 .封装缓存父类,定义Get,Set等常用方法 .定义RedisCache缓存类,执行Redis的Get,Set方法 .构造出缓存工厂调用方法 下面一步一个脚印的来分享: .搭建Redis服务端,并用客户端连接 首先,咋们去这个地址下载安装文件https://github.com/dmajkic/redis/downloads,我这里的版本是:redis-2.4.5-win32-win64里面有32位和64位的执行文件,我这里服

  • 详解redis缓存与数据库一致性问题解决

    数据库与缓存读写模式策略 写完数据库后是否需要马上更新缓存还是直接删除缓存? (1).如果写数据库的值与更新到缓存值是一样的,不需要经过任何的计算,可以马上更新缓存,但是如果对于那种写数据频繁而读数据少的场景并不合适这种解决方案,因为也许还没有查询就被删除或修改了,这样会浪费时间和资源 (2).如果写数据库的值与更新缓存的值不一致,写入缓存中的数据需要经过几个表的关联计算后得到的结果插入缓存中,那就没有必要马上更新缓存,只有删除缓存即可,等到查询的时候在去把计算后得到的结果插入到缓存中即可. 所

  • 详解Redis缓存穿透/击穿/雪崩原理及其解决方案

    目录 1.简介 2.缓存穿透 2.1描述 2.2解决方案 3.缓存击穿 3.1描述 3.2解决方案 4.缓存雪崩 4.1描述 4.1解决方案 5.布隆过滤器 5.1描述 5.2数据结构 5.3"一定不在集合中" 5.4"可能在集合中" 5.5"删除困难" 5.6为什么不使用HashMap呢? 1. 简介 如图所示,一个正常的请求 1.客户端请求张铁牛的博客. 2.服务首先会请求redis,查看请求的内容是否存在. 3.redis将请求结果返回给服

  • 详解Redis 缓存 + Spring 的集成示例

    <整合 spring 4(包括mvc.context.orm) + mybatis 3 示例>一文简要介绍了最新版本的 Spring MVC.IOC.MyBatis ORM 三者的整合以及声明式事务处理.现在我们需要把缓存也整合进来,缓存我们选用的是 Redis,本文将在该文示例基础上介绍 Redis 缓存 + Spring 的集成. 1. 依赖包安装 pom.xml 加入: <!-- redis cache related.....start --> <dependency

  • 详解Redis 缓存删除机制(源码解析)

    删除的范围 过期的 key 在内存满了的情况下,如果继续执行 set 等命令,且所有 key 都没有过期,那么会按照缓存淘汰策略选中的 key 过期删除 redis 中设置了过期时间的 key 会单独存储一份 typedef struct redisDb { dict *dict; // 所有的键值对 dict *expires; //设置了过期时间的键值对 // ... } redisDb; 设置有效期 Redis 中有 4 个命令可以给 key 设置过期时间,分别是 expire pexpi

  • 浅谈一下如何保证Redis缓存与数据库的一致性

    目录 1.四种同步策略: 2.更新缓存还是删除缓存 2.1 更新缓存 2.2 删除缓存 3.先操作数据库还是缓存 3.1 先删除缓存再更新数据库 3.2 先更新数据库再删除缓存 最终结论: 4.延时双删 4.1 采用读写分离的架构怎么办? 5.利用消息队列进行删除的补偿 1.四种同步策略: 想要保证缓存与数据库的双写一致,一共有4种方式,即4种同步策略: 先更新缓存,再更新数据库: 先更新数据库,再更新缓存: 先删除缓存,再更新数据库: 先更新数据库,再删除缓存. 从这4种同步策略中,我们需要作

  • 详解Java分布式系统中session一致性问题

    业务场景 在单机系统中,用户登陆之后,服务端会保存用户的会话信息,只要用户不退出重新登陆,在一段时间内用户可以一直访问该网站,无需重复登陆.用户的信息存在服务端的 session 中,session中可以存放服务端需要的一些用户信息,例如用户ID,所属公司companyId,所属部门deptId等等. 但是随着业务的发展,技术架构需要调整,原来的单机系统逐渐被更换,架构由单机扩展到分布式,甚至当下流行的微服务.虽然在用户端看来系统仍然是一个整体,但在技术端来说业务则被拆分成多个模块,各个模块之间

  • 详解Redis中的List类型

    本系列将和大家分享Redis分布式缓存,本章主要简单介绍下Redis中的List类型,以及如何使用Redis解决博客数据分页.生产者消费者模型和发布订阅等问题. Redis List的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用这个数据结构. List类型主要用于队列和栈,先进先出,后进先出等. 存储形式:key--LinkList<value> 首先先给大家Show一波Redis中与List类型相

  • 详解redis中的锁以及使用场景

    分布式锁 什么是分布式锁? 分布式锁是控制分布式系统之间同步访问共享资源的一种方式. 为什么要使用分布式锁? ​ 为了保证共享资源的数据一致性. 什么场景下使用分布式锁? ​ 数据重要且要保证一致性 如何实现分布式锁? 主要介绍使用redis来实现分布式锁 redis事务 redis事务介绍: ​ 1.redis事务可以一次执行多个命令,本质是一组命令的集合. ​ 2.一个事务中的所有命令都会序列化,按顺序串行化的执行而不会被其他命令插入 ​ **作用:**一个队列中,一次性.顺序性.排他性的执

  • 详解Redis中key的命名规范和值的命名规范

    数据库中得热点数据key命名惯例 表名:主键名:主键值:字段名 例如 user:id:0001:name 例如 user:id:0002:name 例如 order:id:s2002:price 上面的key对应的值则可以是 存放的方式 key value 优点 单独的key:value形式 order:id:s2002:price 2000 方便简单的操作,例如incr自增或自减 json格式 user:id:0001 {id:0001,name:"张三"} 方便一次性存和取数据,但

  • 详解Redis主从复制实践

    复制简介 Redis 作为一门非关系型数据库,其复制功能和关系型数据库(MySQL)来说,功能其实都是差不多,无外乎就是实现的原理不同.Redis 的复制功能也是相对于其他的内存性数据库(memcached)所具备特有的功能. Redis 复制功能主要的作用,是集群.分片功能实现的基础:同时也是 Redis 实现高可用的一种策略,例如解决单机并发问题.数据安全性等等问题. 服务介绍 在本文环境演示中,有一台主机,启动了两个 Redis 示例. 实现方式 Redis 复制实现方式分为下面三种方式:

随机推荐