Redis同时实现缓存和处理并发问题

前面两篇博客一篇是实现了redis做缓存,原理是在启动类中开启@EnableCaching注解,之后在需要缓存的地方使用@Cacheable和@CacheEvict注解;另一篇是实现了redis处理并发操作,原理是使用jedis的setnx命令操作。现在希望同时实现这两个功能,即可以在查询时使用缓存,也可以在更新时处理并发,这里综合前两篇博客即可:

一、项目:

结构:

Redis同时实现缓存和处理并发问题_第1张图片

1、pom:


	4.0.0
	com.redis
	redislock
	0.0.1-SNAPSHOT
	
		org.springframework.boot
		spring-boot-starter-parent
		1.4.1.RELEASE
	
	
		
			org.springframework.boot
			spring-boot-starter-web
		
		
		
		
			org.springframework.boot
			spring-boot-starter-redis
		
		
		
			org.mybatis.spring.boot
			mybatis-spring-boot-starter
			1.1.1
		
		
			mysql
			mysql-connector-java
			5.1.21
		

		
			com.alibaba
			fastjson
			1.2.33
		
	

2、properties配置文件:


#mysql

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/produce?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=wtyy
#mybatis
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml


spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.password=
spring.redis.database=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=0

3、RedisUtil封装Jedis操作:

package com.product.util;

import javax.xml.ws.BindingType;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
//import org.apache.log4j.chainsaw.Main;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @Description:redis工具类
 * @ClassName:
 * @date 2016年10月31日 上午11:25:06
 */
@SuppressWarnings("unused")
@Component
public class RedisUtil {
	private static final String IP = "127.0.0.1"; // ip
	private static final int PORT = 6379; // 端口
	// private static final String AUTH=""; // 密码(原始默认是没有密码)
	private static int MAX_ACTIVE = 1024; // 最大连接数
	private static int MAX_IDLE = 200; // 设置最大空闲数
	private static int MAX_WAIT = 10000; // 最大连接时间
	private static int TIMEOUT = 10000; // 超时时间
	private static boolean BORROW = true; // 在borrow一个事例时是否提前进行validate操作
	private static JedisPool pool = null;
	private static Logger logger = Logger.getLogger(RedisUtil.class);
	/**
	 * 初始化线程池
	 */
	static {
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(MAX_ACTIVE);
		config.setMaxIdle(MAX_IDLE);
		config.setMaxWaitMillis(MAX_WAIT);
		config.setTestOnBorrow(BORROW);
		pool = new JedisPool(config, IP, PORT, TIMEOUT);
	}

	/**
	 * 获取连接
	 */
	public static synchronized Jedis getJedis() {
		try {
			if (pool != null) {
				return pool.getResource();
			} else {
				return null;
			}
		} catch (Exception e) {
			logger.info("连接池连接异常");
			return null;
		}

	}

//	// 加锁
//	public boolean tryLock(String key){
//		Jedis jedis = null;
//		jedis = getJedis();
//
//	    String result = jedis.setex(key,1000,"1");
//
//
//	    if("OK".equals(result)){
//
//	        return true;
//	    }
//	 
//	    return false;
//	}
//
//	// 释放锁
//	public void  releaseLock(String key){
//		Jedis jedis = null;
//		jedis = getJedis();
//	    jedis.del(key);
//	    
//	}
	
	/**
	 * @Description:设置失效时间
	 * @param @param key
	 * @param @param seconds
	 * @param @return
	 * @return boolean 返回类型
	 */
	public static void disableTime(String key, int seconds) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			jedis.expire(key, seconds);

		} catch (Exception e) {
			logger.debug("设置失效失败.");
		} finally {
			getColse(jedis);
		}
	}

	public static boolean exists(String key) {
		boolean flag = false;
		Jedis jedis = null;
		try {
			jedis = getJedis();
			flag = jedis.exists(key);
		} catch (Exception e) {
			logger.debug("设置失效失败.");
		} finally {
			getColse(jedis);
		}
		return flag;
	}

	/**
	 * @Description:插入对象
	 * @param @param key
	 * @param @param obj
	 * @param @return
	 * @return boolean 返回类型
	 */
	public static boolean addObject(String key, Object obj) {

		Jedis jedis = null;
		String value = JSONObject.toJSONString(obj);
		try {
			jedis = getJedis();
			jedis.set(key, value);
			return true;
		} catch (Exception e) {
			logger.debug("插入数据有异常.");
			return false;
		} finally {
			getColse(jedis);
		}
	}

	/**
	 * @Description:存储key~value
	 * @param @param key
	 * @param @param value
	 * @return void 返回类型
	 */

	public static Long addValue(String key, String value) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			//String code = jedis.set(key, value);
			return jedis.setnx(key, value);
		
		} catch (Exception e) {
			logger.debug("插入数据有异常.");
			return null;
		} finally {
			getColse(jedis);
		}
		
	}

	/**
	 * @Description:删除key
	 * @param @param key
	 * @param @return
	 * @return boolean 返回类型
	 */
	public static boolean delKey(String key) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			Long code = jedis.del(key);
			if (code > 1) {
				return true;
			}
		} catch (Exception e) {
			logger.debug("删除key异常.");
			return false;
		} finally {
			getColse(jedis);
		}
		return false;
	}

	/**
	 * @Description: 关闭连接
	 * @param @param jedis
	 * @return void 返回类型
	 */

	public static void getColse(Jedis jedis) {
		if (jedis != null) {
			jedis.close();
		}
	}

}

4、serviceImpl:

package com.product.serviceImpl;

import java.util.Date;

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

import com.product.util.RedisUtil;

import com.product.mapper.ProductMapper;
import com.product.mapper.UserOrderMapper;
import com.product.module.Product;
import com.product.module.UserOrder;
import com.product.service.OrderService;
import com.product.service.ProductService;

@Service("orderService")
public class OrderServiceImpl implements OrderService {

	@Autowired
	private ProductService productService;

	@Autowired
	private UserOrderMapper userOrderMapper;

	// 用户下订单,返回订单id
	@Override
	public Integer order(String productId, String userId) {

		// 逻辑操作
		// 先判断productId是否存在
		Product product = productService.getByProductId(productId);

		if (product == null) {
			return null;
		}
		// 是否有库存
		Integer id = product.getId();
		Integer total = product.getTotal();
		System.out.println("下单前库存" + total);
        
		UserOrder order = new UserOrder();
		if (total <= 0) {
			return null;
		}
       
		order.setCreatetime(new Date());
		order.setProductid(productId);
		order.setUserid(userId);
		int add = userOrderMapper.addOrder(order);
		if (add > 0) {
			// 创建订单成功,库存--
			total--;
			System.out.println("下单后库存" + total);
			productService.updateTotal(id, total);
		
			// return order.getId();
		}

		return null;

	}


	@Override
	public Integer getCountByProductId(String productId) {
		return userOrderMapper.getCountByProductId(productId);
	}

}

package com.product.serviceImpl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.product.mapper.ProductMapper;
import com.product.module.Product;
import com.product.service.ProductService;

@Service("productService")
public class ProductServiceImpl implements ProductService{
	@Autowired
	private ProductMapper productMapper;

	
	@Override
	public Product getByProductId(String productId) {
		Product product = productMapper.getByProductId(productId);
		return product;
	}
	
	
	@Override
	public Integer getCountByProductId(String productId) {
		return productMapper.getCountByProductId(productId);
	}

	@Override
	public int updateTotal(Integer id, Integer total) {
		return productMapper.updateTotal(id,total);
	}


	
	@Cacheable(cacheNames="product", key="'product'+#productId")
	@Override
	public Product cacheGetByProductId(String productId) {
		Product product = productMapper.getByProductId(productId);
		System.out.println("输出则没有走缓存");
		return product;
	}
	
	@CacheEvict(cacheNames="product", key="'product'+#productId",condition="#productId!=''")
	@Override
	public void del(String productId) {
		
	}


	@Cacheable(cacheNames="proList")
	@Override
	public List getList() {
		List list =productMapper.selectAll();
		System.out.println("查询结合,输出则没有走缓存");
		return list;
	}
	

	@CacheEvict(cacheNames="proList")
	@Override
	public void delList() {
		
	}


}

5、controller:


package com.product.controller;

import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.product.module.Product;

import com.product.service.OrderService;
import com.product.service.ProductService;

import com.product.util.RedisUtil;

@RequestMapping("/product")
@RestController
public class ProductController {

	@Autowired
	private OrderService orderService;

	@Autowired
	private ProductService productService;

	@Autowired
	private RedisUtil redisUtil;


	private String KEY = "productId";

	@RequestMapping("/order")
	public void order(@RequestParam String userId,
			@RequestParam String productId) {

		
		boolean lock = redisUtil.exists(KEY);
		if (!lock) {
			//
			
			Long result = redisUtil.addValue(KEY, productId);
			redisUtil.disableTime(KEY, 60);
			// redisUtil.disableTime(KEY, 5);
			// redisClient.expire(PRODUCT_LOCK, 60, String.valueOf(productId));
			if (result == 1) {
				System.err.println("不存在key,执行逻辑操作");
				orderService.order(productId, userId);
				redisUtil.delKey(KEY);
			}
		}else{
			System.out.println("存在该key,不允许执行");
		}
		
	}


	@RequestMapping("/getCache")
	public void getByProductId(@RequestParam String productId){
		Product product = productService.cacheGetByProductId(productId);
		System.out.println(product);
		
	}
	
	@RequestMapping("/delCache")
	public void del(@RequestParam String productId){
		System.out.println("删除商品缓存");
		productService.del(productId);
	}
	
	@RequestMapping("/getCacheList")
	public void getList(){
		List list = productService.getList();
		System.out.println("集合长度"+list.size());
	}
	
	@RequestMapping("/delCacheList")
	public void delList(){
		System.out.println("删除商品集合缓存");
		productService.delList();
	}
}

注:

(1)并发接口也可以这样写,以productId为key,随便写一个value:

@RequestMapping("/order")
	public void order(@RequestParam String userId,@RequestParam String productId){
 
		//是否有缓存
		boolean exists = redisUtil.exists(productId);
		if(!exists){
			//没有,加上
			Long sexNx = redisUtil.addValue(productId, "lock");
			if(sexNx == 1){
				orderService.order(productId,userId);
				redisUtil.delKey(productId);
			}
		}else{
			System.out.println(productId+"正在被抢购");
		}
		
	}

(2)缓存也可以用RedisUtil手动存进来,这种方法是不需要在启动类中开启@EnableCaching的,如:

@RequestMapping("/getProductInfo")
	public void getProductInfo(@RequestParam String productId){
		Product product = productService.getProductInfo(productId);
		System.err.println(product);
	}
@Override
	public Product getProductInfo(String productId) {
		String key = "productInfo"+productId;
		boolean exists = redisUtil.exists(key);
		Product product = null;
		if(exists){
			
			String productStr = redisUtil.getValue(key); 
			System.out.println("存在缓存"+productStr);
			product = JSON.parseObject(productStr,Product.class);
		}else{
			System.out.println("没有走缓存");
			 product = productMapper.getByProductId(productId);
			redisUtil.addObject(key, product);
			redisUtil.disableTime(key, 10);
		}
		return product;
	}

Redis同时实现缓存和处理并发问题_第2张图片

 

6、启动类:


package com.product;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@MapperScan("com.product.mapper")
@EnableCaching 
public class Start {
	public static void main(String[] args) {
		SpringApplication.run(Start.class, args);
	}

}

二、测试:

1、测缓存:

Redis同时实现缓存和处理并发问题_第3张图片

2、测同步:

另建一个测试工程:


package com.test.test;

import com.test.util.HttpRequestUtil;

public class ProductTest implements Runnable{
	public static void main(String[] args) {
		
		ProductTest productTest = new ProductTest();
      for(int i=0;i<50;i++){
    	 Thread thread = new Thread(productTest);
    	  thread.start();
      }
      
	
	}
	@Override
	public void run() {
		 String url = "http://localhost:8080/product/order";
   	  String productId = "abcd";
   	  String userId = "userid";
   	  String param = "userId="+userId+"&productId="+productId;
   	  HttpRequestUtil.sendPost(url, param);
	}
}

 模拟50个并发量,运行该测试类测试。

你可能感兴趣的:(redis)