Redis同時實現快取和處理併發問題
阿新 • • 發佈:2019-01-22
前面兩篇部落格一篇是實現了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個併發量,執行該測試類測試。