zoukankan      html  css  js  c++  java
  • 实现缓存与数据库双写一致性保障

    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的值,此时还是需要从数据库中重新加载一次数据到缓存中的

  • 相关阅读:
    thinkphp 学习1-模型
    apache 2.4目录权限
    标头 header()函数的用法
    PHP面试题一
    php学习
    如何执行超过一百兆(100MB)的sql脚本?
    jquery-numberformatter插件
    xpath 获取父级,和同级
    Vue el-table 行编辑验证、重置
    Vue 弹窗一个新增编辑页面
  • 原文地址:https://www.cnblogs.com/sunliyuan/p/11354531.html
Copyright © 2011-2022 走看看