基于Redis实现分布式锁、限流操作(基于SpringBoot)的实现

基于Redis实现分布式锁、限流操作——基于SpringBoot实现

  • 本文总结了一种利用Redis实现分布式锁、限流的较优雅的实现方式
  • 本文原理介绍较为通俗,希望能帮到有需要的人
  • 本文的demo地址:https://gitee.com/rederxu/lock_distributed.git

一、本文基本实现

  • 利用redis的key是否存在判断锁是否存在
  • 利用redis的increment/decrement方法进行计数,从而实现限流
  • 利用注解,对需要锁定/限流的方法进行配置(用起来超简单!!!)
  • 利用SpringBoot的拦截器,在访问前判断并加锁,访问完成后释放锁
  • 利用@ControllerAdvice捕获全局异常和终止访问

二、为什么选redis

  • 由于redis的原子性(即其操作属于最基本的操作,要么执行要么不执行,要么全部成功,要么全部不成功),因此,用来计数、记录值是否存在具有较高优势。
  • 此外,redis作为效率极高的程序外应用,能有效地独立保存数据,即使是多个服务,也能保持数据一致性(进而实现分布式控制)
  • 本文总结的分布式锁、限流操作都基于redis实现
  • 分布式锁:利用redis的key-value存储结构,判断key是否存在,key在即锁在
  • 限流操作:利用redis的原子性,通过increment/decrement方法进行计数,超出则抛出异常,终止访问。

三、Spring Boot的实现方式

  • 这里的Spring Boot可以理解为微服务中的一个子服务
  • 以下实现是编码过程,运行效果见第四部分,核心实现见本节第3、5部分

1. Reids配置和服务实现

1.1 redis配置
  • properties中的配置
server.port=8080
#redis
spring.redis.host=localhost
spring.redis.database=0
spring.redis.port=6379
  • Java代码配置:
package tech.xujian.lock.distributed.lockdistributed.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        // 1.创建一个redis模板对象
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);// 设置连接器

        // 2.配置redis模板的普通键值对的序列化策略
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));

        // 3.配置redis模板的Hash键值对的序列化策略
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());

        // 4.返回该redis模板对象,加入到spring容器中
        return redisTemplate;
    }
}

1.2 redis服务实现
  • 包含redis的常见基本操作
1.3 RedisService
package tech.xujian.lock.distributed.lockdistributed.service;

public interface RedisService {
    void set(String k,String value);
    void set(String k,String value,int expireMinute);
    String get(String k);

    long getLong(String k);

    long increment(String k);
    long decrement(String k);

    String getAndDelete(String k);
}

1.4 RedisServiceImpl
package tech.xujian.lock.distributed.lockdistributed.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import tech.xujian.lock.distributed.lockdistributed.service.RedisService;

import java.util.concurrent.TimeUnit;

@Service
public class RedisServiceImpl implements RedisService {

    @Autowired
    RedisTemplate<String,String> redisTemplate;

    @Override
    public void set(String k, String value) {
        redisTemplate.opsForValue().set(k,value);
    }

    @Override
    public void set(String k, String value, int expireMinute) {
        redisTemplate.opsForValue().set(k,value,expireMinute, TimeUnit.MINUTES);
    }

    @Override
    public String get(String k) {
        return redisTemplate.opsForValue().get(k);
    }

    @Override
    public long getLong(String k) {
        String str = get(k);
        return str == null ? 0 : Long.parseLong(str);
    }

    @Override
    public long increment(String k) {
        return redisTemplate.opsForValue().increment(k);
    }

    @Override
    public String getAndDelete(String k) {
        return redisTemplate.opsForValue().getAndDelete(k);
    }

    @Override
    public long decrement(String k) {
        return redisTemplate.opsForValue().decrement(k);
    }
}

2 注解实现

2.1 锁类型枚举
package tech.xujian.lock.distributed.lockdistributed.annotation;

public enum RedisLockType {
    IP(1),
    USERNAME(2),
    COUNT(3),
    ANY(4);

    private int value;

    RedisLockType(int value){
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}
2.2 注解声明
package tech.xujian.lock.distributed.lockdistributed.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    RedisLockType type() default RedisLockType.IP;
}

3 拦截去实现

3.1拦截器配置
package tech.xujian.lock.distributed.lockdistributed.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import tech.xujian.lock.distributed.lockdistributed.interceptor.RedisLockInterceptor;

@Component
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    RedisLockInterceptor redisLockInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(redisLockInterceptor).addPathPatterns("/**");
    }
}
3.2拦截器实现
  • 实现过程不赘述,直接看代码
package tech.xujian.lock.distributed.lockdistributed.interceptor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import tech.xujian.lock.distributed.lockdistributed.annotation.RedisLock;
import tech.xujian.lock.distributed.lockdistributed.annotation.RedisLockType;
import tech.xujian.lock.distributed.lockdistributed.service.RedisService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j
public class RedisLockInterceptor implements HandlerInterceptor {

    @Autowired
    RedisService redisService;

    private static final String CACHE_KEY_REDIS_LOCK = "system:distributed:lock:";

    //访问前拦截枷锁,锁定时抛出异常,由全局异常捕获
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            RedisLock redisLock = handlerMethod.getMethodAnnotation(RedisLock.class);
            if(redisLock == null){
                return HandlerInterceptor.super.preHandle(request, response, handler);
            }
            //需要进行锁判断
            switch (redisLock.type()){
                case IP:
                    doIpLock(request);
                    break;
                case USERNAME:
                    //TODO: 从request中通过header等读取到用户信息,然后参考doIpLock实现
                    //略
                    break;
                case COUNT:
                    //某个方法同时访问的人数限制的实现
                    doCountLock(handlerMethod);
                    break;
                case ANY:
                    //TODO: 该方法同时只允许一个人访问,实现方法与doCountLock一致
                    //略
                    break;
            }
        }
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    //访问结束后释放锁
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        if(handler instanceof HandlerMethod){
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            RedisLock redisLock = handlerMethod.getMethodAnnotation(RedisLock.class);
            if(redisLock == null){
                HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
                return;
            }
            //需要进行锁判断
            switch (redisLock.type()){
                case IP:
                    releaseIpLock(request);
                    break;
                case USERNAME:
                    //TODO: 略,释放锁
                    break;
                case COUNT:
                    //某个方法同时访问的人数限制的实现
                    releaseCountLock(handlerMethod);
                    break;
                case ANY:
                    //TODO: 略,释放锁
                    break;
            }
        }
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }

    //访问数量限制lock
    private void doCountLock(HandlerMethod handlerMethod){
        //根据方法名进行锁定
        String redisKey = CACHE_KEY_REDIS_LOCK + "count:" + handlerMethod.getMethod().getDeclaringClass() + ":" + handlerMethod.getMethod().toGenericString();
        redisKey = redisKey.replaceAll(" ","");
        log.info(redisKey);
        long countNow = redisService.getLong(redisKey);
        log.info("当前方法访问人数:" + countNow);
        //设限制100人,也可以考虑在注解中设置
        Assert.isTrue(countNow < 10,"系统拥堵,请稍后重试!当前访问人数:" + countNow);

        redisService.increment(redisKey);
    }

    private void releaseCountLock(HandlerMethod handlerMethod) {
        String redisKey = CACHE_KEY_REDIS_LOCK + "count:" + handlerMethod.getMethod().getDeclaringClass() + ":" + handlerMethod.getMethod().toGenericString();
        redisKey = redisKey.replaceAll(" ","");
        long countNow = redisService.decrement(redisKey);
        log.info("当前方法访问人数:" + countNow);
    }

    //ip判断和lock
    private void doIpLock(HttpServletRequest request){
        //如果是IP锁,则到redis中读取是否已经存在key
        String ip = ServletUtil.getClientIP(request);
        String redisKey = CACHE_KEY_REDIS_LOCK + "ip:" + ip;
        String value = redisService.get(redisKey);
        if(StrUtil.isEmpty(value)){
            //redis自然过期时间为1分钟,即为在一分钟内完成的请求会自动解锁,也可以考虑设置得更短
            redisService.set(redisKey,ip,1);
            return;
        }else{
            throw new RuntimeException("操作太快,请稍后重试");
        }
    }


    //释放锁
    private void releaseIpLock(HttpServletRequest request) {
        String ip = ServletUtil.getClientIP(request);
        String redisKey = CACHE_KEY_REDIS_LOCK + "ip:" + ip;
        redisService.getAndDelete(redisKey);
    }

}

4 全局异常拦截

  • 这里是一个简单的实现
package tech.xujian.lock.distributed.lockdistributed.exception;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class RedisLockExceptionHandler {
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public String exceptionHandler(Exception e){
        e.printStackTrace();
        return e.getMessage();
    }
}

5 Controller上进行注解

  • 示例如下,一看就懂
  • 需要加锁/限流的方法,只需要添加一个注解就像了
@RestController
@RequestMapping("/lock")
public class LockController {

    @Autowired
    RedisService redisService;

    @RedisLock(type = RedisLockType.IP)
    @GetMapping("/test/ip")
    public String lockTest() throws InterruptedException {
        Thread.sleep(20000);
        return "succeed.";
    }

    @RedisLock(type = RedisLockType.COUNT)
    @GetMapping("/test/count")
    public String testCount() throws InterruptedException {
        Thread.sleep(20000);
        return "succeed.";
    }
}

四、运行效果

  • 上文controller中使用sleep使接口卡顿20秒,用来示意接口访问需要时间

1. 正常访问

  • 访问中
    在这里插入图片描述
  • 访问结束后
    在这里插入图片描述

2. 锁

  • 如果在访问的时候再次发起访问,则提示错误(前者访问继续执行)
    在这里插入图片描述

3. 限流

  • 超出流量限制时:
    在这里插入图片描述
  • 本文的demo地址:https://gitee.com/rederxu/lock_distributed.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/452080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测试用例的设计(2)

目录 1.前言 2.正交排列(正交表) 2.1什么是正交表 2.2正交表的例子 2.3正交表的两个重要性质 3.如何构造一个正交表 3.1下载工具 3.1构造前提 4.场景设计法 5.错误猜测法 1.前言 我们在前面的文章里讲了测试用例的几种设计方法,分别是等价类发,把测试例子划分成不同的类…

第九节:揭开交互的秘密:如何制作原型图

交互设计与用户体验(上) 一、交互、原型的概念及关系 1、什么是交互? 交互(interaction)是指用户与产品之间的互动,即用户输入(input),产品对应给出反馈(Feedback)或输出(Output)的过程。简单可以理解为【交流和互动】。我们把任何两个系统之间的交互都可以看做【对…

P4513 小白逛公园 习题笔记(线段树维护区间最大连续子段和)

传送门https://www.luogu.com.cn/problem/P4513本文参考了董晓老师的博客 这道题着实想了很长时间&#xff08;新手&#xff09;&#xff0c;只能想到一个O&#xff08;mn&#xff09;的dp普通写法&#xff0c;那么遇上区间修改问题改怎么操作呢。答案很明显&#xff0c;线段树…

JVM垃圾收集算法

标记清除算法 1先把垃圾对象标记出来 2然后再进行挨个清除 缺点&#xff1a; 1.清除后的内存空间是不连续的碎片&#xff0c; 2.效率也不高&#xff08;相对于复制算法&#xff0c;复制算法是一次性清除&#xff0c;标记清除是挨个清除&#xff09; 复制算法&#xff08;适…

图论(蓝桥杯 C++ 题目 代码 注解)

目录 迪杰斯特拉模板&#xff08;用来求一个点出发到其它点的最短距离&#xff09;&#xff1a; 克鲁斯卡尔模板&#xff08;用来求最小生成树&#xff09;&#xff1a; 题目一&#xff08;蓝桥王国&#xff09;&#xff1a; 题目二&#xff08;随机数据下的最短路径&#…

基于EasyCVR视频技术的流媒体视频融合与汇聚管理系统建设方案

流媒体视频融合与汇聚管理系统可以实现对各类模块化服务进行统一管理和配置等操作&#xff0c;可实现对应用服务的整合、管理及共享&#xff0c;以标准接口的方式&#xff0c;业务平台及其他第三方业务平台可以方便地调用各类数据&#xff0c;具有开放性和可扩展性。在流媒体视…

前后端链条产生的跨域问题

环境&#xff1a; vitevue3 .net 6 vsstudio2022C# asp .net core webapi 看别的up说这个第一条报错是因为&#xff1a;后端没有允许跨域导致的 解决办法: 1.在后端添加允许跨域 Program.cs //添加跨域策略builder.Services.AddCors(options >{options.AddPolicy(…

生成式人工智能服务安全基本要求实务解析

本文尝试明晰《基本要求》的出台背景与实践定位&#xff0c;梳理《基本要求》所涉的各类安全要求&#xff0c;以便为相关企业遵循执行《基本要求》提供抓手。 引言 自2022年初以来&#xff0c;我国陆续发布算法推荐、深度合成与生成式人工智能服务相关的规范文件&#xff0c;…

ARM学习(25)链接装载高阶认识

ARM学习&#xff08;25&#xff09;链接装载高阶认识 1、例子引出 笔者先引入几个编译链接的例子来介绍一下&#xff1a; 声明无效&#xff1a;declared implicitly&#xff1f;&#xff0c;属于编译错误还是链接错误&#xff1f; 编译阶段的错误&#xff0c;属于编译错误&am…

【C++教程从0到1入门编程】第八篇:STL中string类的模拟实现

一、 string类的模拟实现 下面是一个列子 #include <iostream> namespace y {class string{public: //string() //无参构造函数// :_str(nullptr)//{}//string(char* str) //有参构造函数// :_str(str)//{}string():_str(new char[1]){_str[0] \0;}string(c…

【数据分享】2008-2022年全国范围逐年NO2栅格数据(免费获取)

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000-2022年全国范围逐年的PM2.5栅格数据、2013-2022年全国范围逐年SO2栅格数据、2013-2022年全国范围逐年CO栅格数据和2000-2022年全国范围逐年PM10栅格数据&#xff08;可查看之前的文章获悉详…

力扣--深度优先算法/回溯算法47.全排列 Ⅱ

思路分析&#xff1a; 使用DFS算法进行全排列&#xff0c;递归地尝试每个可能的排列方式。使用 path 向量保存当前正在生成的排列&#xff0c;当其大小达到输入数组的大小时&#xff0c;将其加入结果集。使用 numvisited 向量标记每个数字是否已经被访问过&#xff0c;以确保每…

科技助力床垫升级,康姿百德实体店品质有保障

在现代社会的快节奏生活中&#xff0c;高质量的睡眠已成为许多人追求的目标。睡眠质量不仅影响着我们的日常生活和工作效率&#xff0c;而且直接关系到身体健康。康姿百德床垫&#xff0c;作为市场上的优选产品&#xff0c;致力于为消费者提供舒适、健康的睡眠体验&#xff0c;…

ArcGIS学习(十七)基于GIS平台的水文分析

ArcGIS学习(十七)基于GIS平台的水文分析 本任务我们来学习”如何结合ArcGIS做水文分析?” 首先要说明的是,这个任务的水文分析是以ArcGIS工具视角来讲的。而水文分析也是“水文学”这个更大的概念下的一个分析方法。 水文学中研究最多的是水文循环,水文循环是一个物理过程…

Ansible介绍以及功能

ansible功能 批量执行远程命令,可以对远程的多台主机同时进行命令的执行 批量安装和配置软件服务&#xff0c;可以对远程的多台主机进行自动化的方式配置和管理各种服务 编排高级的企业级复杂的IT架构任务, Ansible的Playbook和role可以轻松实现大型的IT复杂架构 提供自动化…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Stepper)

步骤导航器组件&#xff0c;适用于引导用户按照步骤完成任务的导航场景。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 仅能包含子组件StepperItem。 接口 Stepper(value?: { index?…

基于ARMA-GARCH模型探究股价的日历效应和节假日效应【思路+代码】

目录 1. 模型定义1.1 ARMA-GARCH模型1.2 引入节假日效应的虚拟变量的新模型1.3 引入日历效应的虚拟变量的新模型 2. 实证部分2.1 准备工作2.2 引入节假日效应虚拟变量的模型建立和结果分析2.3 引入节假日效应和日历效应的虚拟变量的模型建立和结果分析 3. 结语 本文介绍了ARMA-…

5.Java并发编程—JUC线程池架构

JUC线程池架构 在Java开发中&#xff0c;线程的创建和销毁对系统性能有一定的开销&#xff0c;需要JVM和操作系统的配合完成大量的工作。 JVM对线程的创建和销毁&#xff1a; 线程的创建需要JVM分配内存、初始化线程栈和线程上下文等资源&#xff0c;这些操作会带来一定的时间和…

YOLOv9改进 添加新型卷积注意力框架SegNext_Attention

一、SegNext论文 论文地址:2209.08575.pdf (arxiv.org) 二、 SegNext_Attention注意力框架结构 在SegNext_Attention中,注意力机制被引入到编码器和解码器之间的连接中,帮助模型更好地利用全局上下文信息。具体而言,注意力机制通过学习像素级的注意力权重,使得模型可以对…

基于log4cpp封装日志类

一、log4cpp的使用 1. 下载log4cpp log4cpp官方下载地址 2. 安装log4cpp 第一步&#xff1a;解压 tar zxvf log4cpp-1.1.4.tar.gz 第二步&#xff1a;进入log4cpp文件夹并执行 ./configure tips&#xff1a;如果是ARM架构的CPU可能会失败&#xff0c;如下面这种情况&a…