实现缓存与数据库双写一致性保障

pox文件:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.5.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.1.43</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestone</id>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestone</id>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

  Application:

import java.util.HashSet;
import java.util.Set;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.embedded.ServletListenerRegistrationBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import com.roncoo.eshop.inventory.listener.InitListener;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

@EnableAutoConfiguration  //自动载入应用程序所需的所有Bean
@SpringBootApplication  //启动的类
@ComponentScan  //扫描包
@MapperScan("com.roncoo.eshop.inventory.mapper")
//启动入口函数
public class Application {
 
	//构建数据源
    @Bean
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource dataSource() {
        return new DataSource();
    }

     //构建MyBatis的入口类:SqlSessionFactory
    @Bean
    public SqlSessionFactory sqlSessionFactoryBean1() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource());
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mybatis/*.xml"));
        
        return sqlSessionFactoryBean.getObject();
    }
    //构建事务管理器
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
    
    @Bean
	public JedisCluster JedisClusterFactory() {
		Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
		jedisClusterNodes.add(new HostAndPort("192.168.31.19", 7003));
		jedisClusterNodes.add(new HostAndPort("192.168.31.19", 7004));
		jedisClusterNodes.add(new HostAndPort("192.168.31.227", 7006));
		JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes);
		return jedisCluster;
	}

    //注册监听器    线程池+内存队列初始化
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    public ServletListenerRegistrationBean servletListenerRegistrationBean(){
    	ServletListenerRegistrationBean servletListenerRegistrationBean=
    			new ServletListenerRegistrationBean();
    	//添加listener
    	servletListenerRegistrationBean.setListener(new InitListener());
    	return servletListenerRegistrationBean;
    }
    
    //SpringBoot 启动入口的方法
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

  

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部的队列中

读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个jvm内部的队列中

一个队列对应一个工作线程

每个工作线程串行拿到对应的操作,然后一条一条的执行

这样的话,一个数据变更的操作,先执行,删除缓存,然后再去更新数据库,但是还没完成更新

此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成

这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,
那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可

待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中

如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值
 
具体实现步骤:
1、线程池+内存队列初始化
ServletContextListener里面做,listener,会跟着整个web应用的启动,就初始化,类似于线程池初始化的构建
spring boot应用,Application,搞一个listener的注册
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import com.roncoo.eshop.inventory.thread.RequestProcessorThreadPool;

//系统初始化监听器
public class InitListener implements ServletContextListener{
	@Override
	public void contextInitialized(ServletContextEvent sce){
		// 初始化工作线程池和内存队列
		RequestProcessorThreadPool.init();
	}
	@Override
	public void contextDestroyed(ServletContextEvent sce) {
		
	}
}
 请求接口:
public interface Request {
    void process();
    Integer getProductId();
    boolean isForceRefresh();
}

  请求队列:

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

//请求内存队列
public class RequestQueue {
    //内存队列
	private List<ArrayBlockingQueue<Request>> queues=
			new ArrayList<ArrayBlockingQueue<Request>>();
	
	//标示位map
	private Map<Integer, Boolean> flagMap =new ConcurrentHashMap<Integer,Boolean>();
	
	//采用线程安全的方式实现单例
	//静态内部类的方法,去初始化单例
	private static class Singleton{
		private static RequestQueue instance;
		static{
			instance=new RequestQueue();
		}
		public static RequestQueue getInstance(){
			return instance;
		}
	}
	//jvm的机制,保证多线程并发安全
	//内部类的初始化	,一定只发生一次,不管多少个线程并发去初始化
	public static RequestQueue getInstance(){
		return Singleton.getInstance();
	}
	
	//添加一个内存队列
	public void addQueue(ArrayBlockingQueue<Request> queue){
		this.queues.add(queue);
	}
	
	//获取内存队列的数量
	public int queueSize(){
		return queues.size();
	}
	
	//获取内存队列
	public ArrayBlockingQueue<Request> getQueue(int index){
		return queues.get(index);
	}
	
	public Map<Integer, Boolean> getFlagMap(){
		return flagMap;
	}
}

  执行请求的工作线程:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import com.roncoo.eshop.inventory.request.ProductInventoryCacheRefreshRequest;
import com.roncoo.eshop.inventory.request.ProductInventoryDBUpdateRequest;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.request.RequestQueue;
import java.util.Map;
//执行请求的工作线程
public class RequestProcessorThread implements Callable<Boolean>  {
   //自己监控的内存队列
	private ArrayBlockingQueue<Request> queue;
	
	//初始化的方法
	public RequestProcessorThread(ArrayBlockingQueue<Request> queue) {
		this.queue = queue;
	}
	
	//请求处理封装
	public Boolean call() throws Exception{
		try {
			while(true) {
			// ArrayBlockingQueue
			// Blocking就是说明,如果队列满了,或者是空的,那么都会在执行操作的时候,阻塞住
			Request request=queue.take();
			boolean forceRfresh=request.isForceRefresh();
			
			//先做读请求的去重
			if(!forceRfresh){
				RequestQueue requestQueue=RequestQueue.getInstance();
				Map<Integer,Boolean> flagMap=requestQueue.getFlagMap();
				
				if(request instanceof ProductInventoryDBUpdateRequest){
					// 如果是一个更新数据库的请求,那么就将那个productId对应的标识设置为true
					flagMap.put(request.getProductId(), true);
				}else if(request instanceof ProductInventoryCacheRefreshRequest){
					Boolean flag=flagMap.get(request.getProductId());
					
					//如果flag是null
					if(flag==null){
						flagMap.put(request.getProductId(), false);
					}
					
					// 如果是缓存刷新的请求,那么就判断,如果标识不为空,而且是true,就说明之前有一个这个商品的数据库更新请求
					if(flag!=null&&flag){
						flagMap.put(request.getProductId(), false);
					}
					
					// 如果是缓存刷新的请求,而且发现标识不为空,但是标识是false
					// 说明前面已经有一个数据库更新请求+一个缓存刷新请求了
					if(flag!=null&&!flag){
						// 对于这种读请求,直接就过滤掉,不要放到后面的内存队列里面去了
						return true;
					}
				}
			}
			// 执行这个request操作
		     request.process();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}
}

  请求处理的线程池:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.request.RequestQueue;

//请求处理线程池:单例
public class RequestProcessorThreadPool {
	    // 在实际项目中,你设置线程池大小是多少,每个线程监控的那个内存队列的大小是多少
		// 都可以做到一个外部的配置文件中
		// 直接写死了
	
	//线程池
	private ExecutorService threadPool=Executors.newFixedThreadPool(10);
	
	//构造函数
	public RequestProcessorThreadPool(){
		RequestQueue requestQueue =RequestQueue.getInstance();
		for(int i=0;i<10;i++){
			ArrayBlockingQueue<Request> queue=new ArrayBlockingQueue<Request>(100);
			requestQueue.addQueue(queue);
			threadPool.submit(new RequestProcessorThread(queue));	
		}
	}
	
	//静态内部类的方式去初始化线程的方式
	public static class Singleton{
		private static RequestProcessorThreadPool instance;
		static{
			instance=new RequestProcessorThreadPool();
		}
		public static RequestProcessorThreadPool getInstance(){
			return instance;
		}
	}
	
	//jvm的方式去保证多线程并发安全
    //内部类的初始化,一定只发生一次,不管多少个线程并发初始化
	public static RequestProcessorThreadPool getInstance(){
		return Singleton.getInstance();
	}
	//初始化的便捷方法
	public static void init(){
		getInstance();
	}
}

  请求的响应:

//请求的响应
public class Response {
     public static final String SUCCESS = "success";
     public static final String FAILURE = "failure";
     
     private String status;
 	 private String message;
     
     public Response() {
		
	 }
	
	 public Response(String status) {
		this.status = status;
	 }
	
	 public Response(String status, String message) {
		this.status = status;
		this.message = message;
	 }
	
	 public String getStatus() {
	 	return status;
	 }
	 public void setStatus(String status) {
		this.status = status;
	 }
	 public String getMessage() {
		return message;
	 }
	 public void setMessage(String message) {
		this.message = message;
	 }
}

  2、两种请求对象封装

import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.service.ProductInventoryService;

/**
 * 比如说一个商品发生了交易,那么就要修改这个商品对应的库存
 * 此时就会发送请求过来,要求修改库存,那么这个可能就是所谓的data update request,数据更新请求
 * cache aside pattern
 * (1)删除缓存
 * (2)更新数据库
 * **/
//请求对象的封装,删除redis的缓存,修改数据库中的库存
public class ProductInventoryDBUpdateRequest implements Request{
	// 商品库存
	private ProductInventory productInventory;
	
	//商品库存Service
	private ProductInventoryService productInventoryService;
	
	public ProductInventoryDBUpdateRequest(ProductInventory productInventory,
			ProductInventoryService productInventoryService){
		this.productInventory=productInventory;
		this.productInventoryService=productInventoryService;	
	}
	@Override
	public void process(){
		// 删除redis中的缓存
		productInventoryService.removeProductInventoryCache(productInventory); 
		// 修改数据库中的库存
		productInventoryService.updateProductInventory(productInventory);  
	}
	
	//获取商品id
	public Integer getProductId(){
		return productInventory.getProductId();
	}
	
	@Override
	public boolean isForceRefresh() {
		return false;
	}
}

  

import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.service.ProductInventoryService;

//重新加载商品库存的缓存(请求对象的封装)
public class ProductInventoryCacheRefreshRequest implements Request {
     //商品id
	private Integer productId;
	//商品库存Service
	private ProductInventoryService productInventoryService;
	
	//是否强制刷新缓存
	private boolean forceRefresh;
	
	public ProductInventoryCacheRefreshRequest(Integer productId,
			ProductInventoryService productInventoryService,
			boolean forceRefresh) {
		this.productId=productId;
		this.productInventoryService=productInventoryService;
		this.forceRefresh=forceRefresh;
	}
	
	@Override
	public void process() {
		// 从数据库中查询最新的商品库存数量
		ProductInventory productInventory = productInventoryService.findProductInventory(productId);
		
		// 将最新的商品库存数量,刷新到redis缓存中去
		productInventoryService.setProductInventoryCache(productInventory); 
	}
	
	//返回商品的id
	public Integer getProductId() {
		return productId;
	}

	public boolean isForceRefresh() {
		return forceRefresh;
	}
}

  

3、请求异步执行Service封装

import java.util.concurrent.ArrayBlockingQueue;
import org.springframework.stereotype.Service;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.request.RequestQueue;
import com.roncoo.eshop.inventory.service.RequestAsyncProcessService;

//请求异步处理的service实现
@Service("requestAsyncProcessService")  
public class RequestAsyncProcessServiceImpl implements RequestAsyncProcessService{
    //做请求的的路由,根据每个请求的商品id,路由到对应的内存队列中
	@Override
	public void process(Request request) {
		try {
			// 做请求的路由,根据每个请求的商品id,路由到对应的内存队列中去
			ArrayBlockingQueue<Request> queue = getRoutingQueue(request.getProductId());
			// 将请求放入对应的队列中,完成路由操作
			queue.put(request);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	//获取路由到的内存队列
	private ArrayBlockingQueue<Request> getRoutingQueue(Integer productId){
		RequestQueue requestQueue=RequestQueue.getInstance();
		//获取productId的hash值
		String key=String.valueOf(productId);
		int h;
		int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
		
		// 对hash值取模,将hash值路由到指定的内存队列中,比如内存队列大小8
		// 用内存队列的数量对hash值取模之后,结果一定是在0~7之间
		// 任何一个商品id都会被固定路由到同样的一个内存队列中去的
		int index=(requestQueue.queueSize()-1)&hash;
		
		return requestQueue.getQueue(index);
	}
}

  

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.roncoo.eshop.inventory.dao.RedisDAO;
import com.roncoo.eshop.inventory.mapper.ProductInventoryMapper;
import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.service.ProductInventoryService;

//商品库存Service实现类
@Service("productInventoryService")  
public class ProductInventoryServiceImpl implements ProductInventoryService  {
	@Resource
	private ProductInventoryMapper productInventoryMapper;
	@Resource
	private RedisDAO redisDAO;
	
	@Override
	public void updateProductInventory(ProductInventory productInventory) {
		productInventoryMapper.updateProductInventory(productInventory); 
	}
	
	@Override
	public void removeProductInventoryCache(ProductInventory productInventory){
		String key="product:inventory:"+productInventory.getProductId();
		redisDAO.delete(key);
	}
	
	//根据商品id查询商品库存
	public ProductInventory  findProductInventory(Integer productId) {
		return productInventoryMapper.findProductInventory(productId);
	}
	//设置商品库存的缓存
	public void setProductInventoryCache(ProductInventory productInventory){
		String key = "product:inventory:" + productInventory.getProductId();
		redisDAO.set(key, String.valueOf(productInventory.getInventoryCnt()));  
	}
	
	// 获取商品库存的缓存
	public ProductInventory getProductInventoryCache(Integer productId){
		Long inventoryCnt = 0L;
		String key="product:inventory:" + productId;
		String result=redisDAO.get(key);
		if(result!=null&&!"".equals(result)){
			try {
				inventoryCnt = Long.valueOf(result);
				return new ProductInventory(productId, inventoryCnt);
			} catch (Exception e) {
				e.printStackTrace(); 
			}
		}
		return null;	
	}
}

  

import com.roncoo.eshop.inventory.request.Request;

//请求异步执行的server
public interface RequestAsyncProcessService {
   void process(Request request);
}

  

import com.roncoo.eshop.inventory.model.ProductInventory;

public interface ProductInventoryService {
	/**
	 * 更新商品库存
	 * @param productInventory 商品库存
	 */
	void updateProductInventory(ProductInventory productInventory);
	
	/**
	 * 删除Redis中的商品库存的缓存
	 * @param productInventory 商品库存
	 */
	void removeProductInventoryCache(ProductInventory productInventory);
	
	/**
	 * 根据商品id查询商品库存
	 * @param productId 商品id 
	 * @return 商品库存
	 */
	ProductInventory findProductInventory(Integer productId);
	
	/**
	 * 设置商品库存的缓存
	 * @param productInventory 商品库存
	 */
	void setProductInventoryCache(ProductInventory productInventory);
	
	/**
	 * 获取商品库存的缓存
	 * @param productId
	 * @return
	 */
	ProductInventory getProductInventoryCache(Integer productId);
}

  控制器:

import com.roncoo.eshop.inventory.model.ProductInventory;
import com.roncoo.eshop.inventory.request.ProductInventoryCacheRefreshRequest;
import com.roncoo.eshop.inventory.request.ProductInventoryDBUpdateRequest;
import com.roncoo.eshop.inventory.request.Request;
import com.roncoo.eshop.inventory.service.ProductInventoryService;
import com.roncoo.eshop.inventory.service.RequestAsyncProcessService;
import com.roncoo.eshop.inventory.vo.Response;

//商品库存Controller
/*
 *(1)一个更新商品库存的请求过来,然后此时会先删除redis中的缓存,然后模拟卡顿5秒钟
 *(2)在这个卡顿的5秒钟内,我们发送一个商品缓存的读请求,因为此时redis中没有缓存,就会来请求将数据库中最新的数据刷新到缓存中
 *(3)此时读请求会路由到同一个内存队列中,阻塞住,不会执行
 *(4)等5秒钟过后,写请求完成了数据库的更新之后,读请求才会执行
 *(5)读请求执行的时候,会将最新的库存从数据库中查询出来,然后更新到缓存中
 * */
public class ProductInventoryController {
	@Resource
	private RequestAsyncProcessService requestAsyncProcessService;
	@Resource
	private ProductInventoryService productInventoryService;
	
	//更新商品库存
	@RequestMapping("/updateProductInventory")
	@ResponseBody
	public Response updateProductInventory(ProductInventory productInventory){
		Response response=null;
		try{
			Request request=new ProductInventoryDBUpdateRequest(
					productInventory, productInventoryService);
			requestAsyncProcessService.process(request);
			response = new Response(Response.SUCCESS);
		}
	    catch (Exception e) {
		e.printStackTrace();
		response = new Response(Response.FAILURE);
	  }
	  return response;
	} 
	//获取商品库存
	@RequestMapping("/getProductInventory")
	@ResponseBody
	public ProductInventory getProductInventory(Integer productId){
		ProductInventory productInventory = null;
		try{
			Request request = new ProductInventoryCacheRefreshRequest(
					productId, productInventoryService, false);
			requestAsyncProcessService.process(request);
			
			requestAsyncProcessService.process(request);
			
			// 将请求扔给service异步去处理以后,就需要while(true)一会儿,在这里hang住
			// 去尝试等待前面有商品库存更新的操作,同时缓存刷新的操作,将最新的数据刷新到缓存中
			long startTime = System.currentTimeMillis();
			long endTime = 0L;
			long waitTime = 0L;
			
			// 等待超过200ms没有从缓存中获取到结果
			while(true){
				if(waitTime>200){
					break;
				}
				// 尝试去redis中读取一次商品库存的缓存数据
				productInventory =productInventoryService.getProductInventoryCache(productId);
				
				// 如果读取到了结果,那么就返回
				if(productInventory != null) {
					return productInventory;
				}
				
				// 如果没有读取到结果,那么等待一段时间
				else {
					Thread.sleep(20);
					endTime = System.currentTimeMillis();
					waitTime = endTime - startTime;
				}
			}
			// 直接尝试从数据库中读取数据
		   productInventory = productInventoryService.findProductInventory(productId);
		   if(productInventory != null) {
			    // 将缓存刷新一下
				productInventoryService.setProductInventoryCache(productInventory); 
			    return productInventory;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return new ProductInventory(productId, -1L);  
	}
}

  

public interface RedisDAO {

	void set(String key, String value);
	
	String get(String key);
	
	void delete(String key);
	
}

  

import javax.annotation.Resource;

import org.springframework.stereotype.Repository;

import redis.clients.jedis.JedisCluster;

import com.roncoo.eshop.inventory.dao.RedisDAO;

@Repository("redisDAO")
public class RedisDAOImpl implements RedisDAO {

	@Resource
	private JedisCluster jedisCluster;
	
	@Override
	public void set(String key, String value) {
		jedisCluster.set(key, value);
	}

	@Override
	public String get(String key) {
		return jedisCluster.get(key);
	}
	
	@Override
	public void delete(String key) {
		jedisCluster.del(key);
	}
}

  

6、读请求去重优化

如果一个读请求过来,发现前面已经有一个写请求和一个读请求了,那么这个读请求就不需要压入队列中了

因为那个写请求肯定会更新数据库,然后那个读请求肯定会从数据库中读取最新数据,然后刷新到缓存中,自己只要hang一会儿就可以从缓存中读到数据了

7、空数据读请求过滤优化

可能某个数据,在数据库里面压根儿就没有,那么那个读请求是不需要放入内存队列的,而且读请求在controller那一层,直接就可以返回了,不需要等待

如果数据库里都没有,就说明,内存队列里面如果没有数据库更新的请求的话,一个读请求过来了,就可以认为是数据库里就压根儿没有数据吧

如果缓存里没数据,就两个情况,第一个是数据库里就没数据,缓存肯定也没数据; 第二个是数据库更新操作过来了,先删除了缓存,此时缓存是空的,但是数据库里是有的

但是的话呢,我们做了之前的读请求去重优化,用了一个flag map,只要前面有数据库更新操作,flag就肯定是存在的,你只不过可以根据true或false,判断你前面执行的是写请求还是读请求

但是如果flag压根儿就没有呢,就说明这个数据,无论是写请求,还是读请求,都没有过

那这个时候过来的读请求,发现flag是null,就可以认为数据库里肯定也是空的,那就不会去读取了

或者说,我们也可以认为每个商品有一个最最初始的库存,但是因为最初始的库存肯定会同步到缓存中去的,有一种特殊的情况,就是说,商品库存本来在redis中是有缓存的

但是因为redis内存满了,就给干掉了,但是此时数据库中是有值得

那么在这种情况下,可能就是之前没有任何的写请求和读请求的flag的值,此时还是需要从数据库中重新加载一次数据到缓存中的

原文地址:https://www.cnblogs.com/sunliyuan/p/11354531.html