基于reidsson 的分布式锁
java
package com.me.redis_lock.controller;
import cn.hutool.core.collection.CollectionUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisClientConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.JedisUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@RestController
public class IndexController {
@Autowired
RedisTemplate redisTemplate;
private static final String GOODS="GOODS";
private static final String LOCK="LOCK";
@Autowired
private RedissonClient redissonClient;
@GetMapping("goods")
/**
* redissonClient 分布式锁
* 内置看门狗机制
* 实现 锁的续期
*/
public String buyGoods(){
RLock lock = redissonClient.getLock("LOCK");
try {
System.out.println("加锁");
lock.lock();
String string = (String) redisTemplate.opsForValue().get(GOODS);
// 库存
int goodCount =0;
if (!StringUtils.isEmpty(string)){
goodCount = Integer.parseInt(string);
}
if (goodCount>0){
// 购买
int realCount = goodCount -1;
redisTemplate.opsForValue().set(GOODS,realCount+"");
String stre =Thread.currentThread().getId()+"成功购买到商品,库存剩余"+realCount+"件";
System.out.println(stre);
return stre;
}else{
System.out.println("商品已售完");
return "商品已售完";
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (lock.isLocked()){
if(lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
return "";
}
基于redis + lua 脚本实现的
java
package com.me.redis_lock.controller;
import cn.hutool.core.collection.CollectionUtil;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.redisson.client.RedisClientConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.jedis.JedisUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@RestController
public class IndexController {
@Autowired
RedisTemplate redisTemplate;
private static final String GOODS="GOODS";
private static final String LOCK="LOCK";
/**
* redis 版本 分布式锁 setnx 不存在则设置成功 否则失败
*/
@GetMapping("goods2")
public String buyGoods2(){
String uuid =UUID.randomUUID().toString();
try {
// 分布式锁 实现 确保设置超时时间和 设置操作的原子性 时间需要根据业务实际设置
Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(LOCK, uuid, 1, TimeUnit.SECONDS);
if (!aBoolean){
System.out.println("未获得锁");
return "未获得锁";
}
// 获取库存
String string = (String) redisTemplate.opsForValue().get(GOODS);
// 库存 大于0
int goodCount =0;
if (!StringUtils.isEmpty(string)){
goodCount = Integer.parseInt(string);
}
if (goodCount>0){
// 购买 库存减1
int realCount = goodCount -1;
redisTemplate.opsForValue().set(GOODS,realCount+"");
String stre ="成功购买到商品,库存剩余"+realCount+"件";
System.out.println(stre);
return stre;
}else{
System.out.println("商品已售完");
return "商品已售完";
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 只能删除该线程申请的锁 避免误删 其他线程的 锁
// if (redisTemplate.opsForValue().get(LOCK).equals(uuid)){
// redisTemplate.delete(LOCK);
// }
// while (true){
// redisTemplate.watch(LOCK);
// if (uuid.equals(redisTemplate.opsForValue().get(LOCK))){
// redisTemplate.setEnableTransactionSupport(true);
// redisTemplate.multi();
// redisTemplate.delete(LOCK);
// List<Object> exec = redisTemplate.exec();
// if (exec ==null){
// continue;
// }
// }
// redisTemplate.unwatch();
// break;
// }
// lua 脚本 保证原子性
String sc="if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
"then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
DefaultRedisScript script = new DefaultRedisScript(sc,Long.class);
List<String > keys = CollectionUtil.newArrayList(LOCK);
List<String > args = CollectionUtil.newArrayList(uuid);
Object execute = redisTemplate.opsForValue().getOperations().execute(script, keys,uuid);
if (execute!=null ){
System.out.println("execute:"+execute);
}else{
System.out.println("解锁失败");
}
// lock.unlock();
}
return "";
}
}
lua
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
redis 限流 基于ip
java
/**
* 注解 方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLimit {
int seconds() default 10;
int maxCount() default 3;
}
java
/**
*
* 拦截器 基于 ip 限流
*/
package com.me.redis_lock.common.handler;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.me.redis_lock.common.lock.R;
import com.me.redis_lock.common.lock.RedisLimit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class LimitHandler implements HandlerInterceptor {
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
if (handler instanceof HandlerMethod){
HandlerMethod method = (HandlerMethod) handler;
RedisLimit methodAnnotation = method.getMethodAnnotation(RedisLimit.class);
if (methodAnnotation==null){
return true;
}
// 多少秒内只能访问 多少次 否则禁止访问
int seconds = methodAnnotation.seconds();
//
int maxCount = methodAnnotation.maxCount();
String ipAddress = getIpAddress(request);
// 基于IP 限流 和uri
String key = "limit:"+ipAddress+":"+request.getRequestURI();
Integer counter = (Integer) redisTemplate.opsForValue().get(key);
if (null !=counter){
log.info("(ip限流请求次数) ip:{} 接口名:{} 访问次数:{}", ipAddress, request.getRequestURI(), counter);
}
if (null==counter || (counter)<=0){
redisTemplate.opsForValue().set(key,1,seconds, TimeUnit.SECONDS);
return true;
}
if ((counter)<=maxCount){
redisTemplate.opsForValue().increment(key);
return true;
}
writeResponse(response);
return false;
}
}catch (Exception e){
e.printStackTrace();
}
return true;
}
/**
* 响应
*/
private void writeResponse(HttpServletResponse response) throws IOException {
response.setCharacterEncoding("UTF-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
// R.fail 是接口响应统一封装返回
R r = R.builder().result(false).msg("恶意攻击触发限流").build();
String string = JSON.toJSONString(r);
response.getWriter().println(string);
}
/**
* 获取真实ip
*/
public static String getIpAddress(HttpServletRequest request) {
String ip;
try {
ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
if (ip.equals("0:0:0:0:0:0:0:1")) ip="127.0.0.1";
}
} catch (Exception e) {
ip = "未知IP";
}
return ip;
}
}