一 安装windows版本redis
https://github.com/microsoftarchive/redis/releases
二 代码
package com.hang.redis.controller;
import com.hang.redis.business.QueryBusiness;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Controller
*
* @author Hang W
*/
@RestController
@RequestMapping("query")
@SuppressWarnings("all")
public class QueryController {
@Autowired
private QueryBusiness queryBusiness;
/**
* 根据key查询value
*
* @param key
* @return
*/
@GetMapping("name/{key}")
public String query(@PathVariable String key) {
return queryBusiness.query(key);
}
/**
* redis分布式锁
*
* @param key
* @return
*/
@GetMapping("stock/{key}")
public int getStock(@PathVariable String key) {
return queryBusiness.getStock(key);
}
}
package com.hang.redis.business.impl;
import com.hang.redis.business.QueryBusiness;
import com.hang.redis.utils.RedisUtils;
import com.hang.redis.utils.impl.RedisUtilsImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* Business
*
* @author Hang W
*/
@Component
@SuppressWarnings("all")
public class QueryBusinessImpl implements QueryBusiness {
private final static Logger logger = LoggerFactory.getLogger(RedisUtilsImpl.class);
private final static String STOCK = "noodles";
@Autowired
private RedisUtils redisUtils;
/**
* 根据key查询value
*
* @param key
* @return
*/
@Override
public String query(String key) {
return redisUtils.get(key);
}
/**
* 业务场景
* 从数据库查询库存,如果存在则减少库存
*
* @return
*/
@Override
public int getStock(String key) {
// lock
boolean flag = redisUtils.lock(STOCK, com.hang.redis.utils.StringUtils.getRequestId(), 120000);
int count = 0;
try {
if(flag) {
logger.info("QueryBusinessImpl.getStock Key: {}", key);
String value = redisUtils.get(key);
logger.info("QueryBusinessImpl.getStock Value: {}", value);
if(!StringUtils.isEmpty(value)) {
count = Integer.parseInt(value);
if(0 < count) {
count--;
redisUtils.set(key, "" + count);
} else {
return count;
}
} else {
return count;
}
// nulock
redisUtils.unlock(STOCK);
}
} catch (Exception e) {
logger.info("QueryBusinessImpl.getStock Exception: {}", e);
redisUtils.unlock(STOCK);
} finally {
redisUtils.unlock(STOCK);
}
return count;
}
}
redis
package com.hang.redis.utils.impl;
import com.hang.redis.utils.RedisUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* redis 实现类
*
* @author Hang W
*/
@Component
@SuppressWarnings("all")
public class RedisUtilsImpl implements RedisUtils {
private final static Logger logger = LoggerFactory.getLogger(RedisUtilsImpl.class);
private ThreadLocal<Map<String, Integer>> threadLocal = new ThreadLocal<Map<String, Integer>>();
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void set(String key, String value) {
logger.info("RedisUtilsImpl.set Key: {}, Value: {}", key, value);
redisTemplate.boundValueOps(key).set(value);
}
@Override
public void setnx(String key, String value) {
logger.info("RedisUtilsImpl.set Key: {}, Value: {}", key, value);
redisTemplate.boundValueOps(key).setIfAbsent(value, 10000, TimeUnit.MILLISECONDS);
}
@Override
public String get(String key) {
String value;
try{
logger.info("RedisUtilsImpl.get Key: {}", key);
value = redisTemplate.boundValueOps(key).get();
logger.info("RedisUtilsImpl.get Value: {}", value);
} catch (Exception e) {
logger.info("RedisUtilsImpl.get Exception: {}", e);
return "";
}
return value;
}
/**
* 分布式锁
* 1.全局key
* 2.唯一请求id —— 请求次数(可重入)
* 3.过期时间
* 4.非阻塞
* 5.看门狗
*
* @param lock
* @param requestId
* @param time
* @return
*/
@Override
public boolean lock(String lock, String requestId, long time) {
Boolean flag = false;
try{
Map<String, Integer> map = threadLocal.get();
logger.info("RedisUtilsImpl.lock ThreadLocal: {}", map);
if(CollectionUtils.isEmpty(map)) {
map = new HashMap<>();
map.put(requestId, 1);
threadLocal.set(map);
logger.info("RedisUtilsImpl.lock Key: {}, Value: {}, Time: {}", lock, requestId, time);
flag = redisTemplate.boundValueOps(lock).setIfAbsent(requestId, time, TimeUnit.SECONDS);
logger.info("RedisUtilsImpl.lock Return: {}", flag);
// 非阻塞
if(!flag) {
new Thread(() -> {
while(true) {
Boolean f = redisTemplate.boundValueOps(lock).setIfAbsent(requestId, time, TimeUnit.SECONDS);
logger.info("RedisUtilsImpl.lock new Thread: {}", f);
if(f)
break;
}
}).start();
}
/**
* 看门狗
* 锁默认失效时间为30秒
*
* 1.先查看requestId是否被替换.
* 2.如果没有被替换,查看ttl,还是多少时间,20s <= time 临界值时,就再继续延迟30s.
* 3.如果已经被替换,表示逻辑已经执行完毕.
*/
new Thread(() -> {
while(true) {
String getRequestId = redisTemplate.boundValueOps(lock).get();
logger.info("RedisUtilsImpl.lock GetRequestId: {}", getRequestId);
if(StringUtils.isEmpty(getRequestId)) {
break;
} else if(getRequestId.equals(requestId)) {
Long expireTime = redisTemplate.getExpire(lock);
logger.info("RedisUtilsImpl.lock ExpireTime: {}", expireTime);
if(20 <= expireTime) {
redisTemplate.expire(lock, time, TimeUnit.MILLISECONDS);
}
} else {
break;
}
}
}).start();
} else {
Integer count = map.get(requestId);
map.put(requestId, count++);
threadLocal.set(map);
}
} catch (Exception e) {
logger.info("RedisUtilsImpl.lock Exception: {}", e);
redisTemplate.delete(lock);
}
return flag;
}
/**
* 业务代码执行完毕删除key,后续请求可以获取key
*
* @param lock
*/
@Override
public void unlock(String lock) {
try {
Map<String, Integer> map = threadLocal.get();
logger.info("RedisUtilsImpl.unlock ThreadLocal: {}", map);
if(!CollectionUtils.isEmpty(map)) {
String requestId = (String) map.keySet().toArray()[0];
Integer count = (Integer) map.values().toArray()[0];
String getRequestId = redisTemplate.boundValueOps(lock).get();
if(requestId.equals(getRequestId) && 1 < count) {
logger.info("RedisUtilsImpl.unlock Key: {}, Count(?): {}", lock, count);
map.put(requestId, count--);
threadLocal.set(map);
} else if (requestId.equals(getRequestId) && 1 == count) {
logger.info("RedisUtilsImpl.unlock Key: {}, Count(=1): {}", lock, count);
redisTemplate.delete(lock);
threadLocal.remove();
}
}
} catch (Exception e) {
logger.info("RedisUtilsImpl.unlock Exception: {}", e);
redisTemplate.delete(lock);
} finally {
redisTemplate.delete(lock);
}
}
}
三 git地址
已经上传到git,自测没问题
https://github.com/XUEZHIQIAN/springboot-redis.git
说明:
1.使用 redisTemplate
redisTemplate.boundValueOps(lock).setIfAbsent(requestId, time, TimeUnit.SECONDS)
2.全局key(分布式下使用同一个唯一的key)
3.唯一请求id(每个请求申请一个唯一的id,用于锁定key,并且实现可重入 count 数量 + 1)
4.过期时间和 set 使用同一个方法,保证原子性
5.非阻塞,当多个请求对同一个key进行加锁时,当其中一个用户已经获取到 key,那么其他请求就进行自旋 直到获取到 key
6.看门狗,当业务逻辑没有处理完,但过期时间快到,则自动续时间