Skip to content

基于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;
    }
}