1. 程式人生 > >Redis同時實現快取和處理併發問題

Redis同時實現快取和處理併發問題

前面兩篇部落格一篇是實現了redis做快取,原理是在啟動類中開啟@EnableCaching註解,之後在需要快取的地方使用@Cacheable和@CacheEvict註解;另一篇是實現了redis處理併發操作,原理是使用jedis的setnx命令操作。現在希望同時實現這兩個功能,即可以在查詢時使用快取,也可以在更新時處理併發,這裡綜合前兩篇部落格即可:

一、專案:

結構:

1、pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.redis</groupId>
	<artifactId>redislock</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.4.1.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<!-- redis -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-redis</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.1.1</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.21</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.33</version>
		</dependency>
	</dependencies>

</project>

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<Product> getList() {
		List<Product> 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<Product> 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;
	}

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、測快取:

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個併發量,執行該測試類測試。