• SpringBoot集成Redisson


    一 : 什么是Redisson?

    网上有太多的解释了,基于项目使用简单说一下自己的理解。
    背景:项目采用集群部署,需要解决的问题,防止不同服务器调用相同的接口,导致数据重复更新,导致数据问题。
    使用Redisson控制相同接口,处理并发操作,来解决上述问题。

    下面开整!!!

    二 : 引入pom文件

    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.14.0</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    正常是这样直接引入pom文件即可,但是在后续使用的时候会报maven冲突
    所以还是按照下面的方式引入,剔除23采用21。

    <!-- 集成redisson -->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.14.0</version>
        <exclusions>
            <exclusion>
                <groupId>org.redisson</groupId>
                <artifactId>redisson-spring-data-23</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-data-21</artifactId>
        <version>3.14.0</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    三 : 增加配置文件

    在使用Redisson之前,很多项目都是已经配置了redis,如果已经配置了redis,那配置文件就不需要改动,便可以直接使用。
    如果没有redis的配置文件,则可引入。
    此处从安全角度出发,建议redis设置密码。
    在这里插入图片描述
    如果项目没有redis,SpringBoot项目还需要加一个config配置类。

    import org.springframework.beans.factory.annotation.Autowired;
    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.StringRedisSerializer;
    
    @Configuration
    public class RedisConfig {
    
        @Autowired
        private RedisConnectionFactory factory;
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate() {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new StringRedisSerializer());
            redisTemplate.setConnectionFactory(factory);
            return redisTemplate;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    四 : 工具类

    下面搞一个工具类,方便使用。

    import org.redisson.api.RLock;
    import org.redisson.api.RReadWriteLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.stereotype.Component;
    import javax.annotation.Resource;
    import java.util.UUID;
    
    /**
     * Redisson 加锁
     */
    @Component
    public class RedissonUtil {
    
        @Resource
        private RedissonClient redissonClient;
    
        public String getKey(){
            return UUID.randomUUID().toString();
        }
    
    	public String getKey(Class<?> tClass, Thread thread){
        	return tClass.toString() + "_" + thread.getStackTrace()[2].getMethodName();
    	}
     
        public RLock getClint(String key){
            RReadWriteLock lock = redissonClient.getReadWriteLock(key);
            return lock.writeLock();
        }
    
        public void lock(String key) {
            this.getClint(key).lock();
        }
    
        public void unLock(String key) {
            this.getClint(key).unlock();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    工具类中的getKey(),这个是一个随机的,方便测试使用。
    getKey另一个getKey()生成的Key为调用的方法对应的类名拼接方法名
    具体使用如下

    获取Key
    String lockKey = redissonUtil.getKey(this.getClass(), Thread.currentThread());
    
    • 1
    • 2

    那么问题来了,怎么使用呢?

    五 : 如何应用

    1、手动加锁

    直接使用工具类,对于特殊的代码块,手动加锁。如下:

    public class TestRedisson {
    
    	@Resource
        private RedissonUtil redissonUtil;
    
        public void testRedisson(){
            //定义Key
            String myKey = ")!@#$%^&*(";
            redissonUtil.lock(myKey);
            {
                //执行代码块
            }
            redissonUtil.unLock(myKey);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果不想获取两次客户端

    public class TestRedisson {
    
    	@Resource
        private RedissonUtil redissonUtil;
    
        public void testRedisson(){
            //定义Key
            String myKey = ")!@#$%^&*(";
            RLock clint = redissonUtil.getClint(myKey);
            clint.lock();
            {
                //执行代码块
            }
            clint.unlock();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2、自动加锁

    这样的话,是可以实现,只不过如果我很多代码都需要的话,是不是有点麻烦了呢?
    那么可以换一种方式来处理。

    2.1、用拦截器?

    锁的机制是有头有尾,而拦截器只是有头,对尾不做处理。
    似乎好像不太可行。

    2.2、用过滤器?

    作用似乎同上,好像也不太行。

    2.3、用监听?

    听上去好像可行。
    用一个注解,然后对项目进程开启监听,只要通过调用注解的方法,就加锁。
    但问题似乎还是同上,无法做到收尾。

    2.4、用接口,配合注解来实现?

    那么我们可以用接口,配合注解来实现包含头包含尾。
    开整!!!
    先搞一个方法级注解
    注:注解使用时,值填的是固定值

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 业务锁
     */
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface BusinessLock {
    
        String value() default "";
        
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    再搞一个接口
    如果只写一个接口,里面定义一个方法,那么实现我的接口,就需要实现我的这个固定的方法,然后搞一下加锁。
    如此一来,是不是我一个类,如果有两个方法都需要加锁的话,就没办法实现了呢?
    改动一下接口

    import com.BusinessLock;
    import com.RedissonUtil;
    import com.SpringContextUtils;
    import org.redisson.api.RLock;
    import org.springframework.core.annotation.AnnotationUtils;
    import java.lang.reflect.Method;
    
    public interface BaseService {
    
        public default Result doBusiness(String json) {
            Object o = SpringContextUtils.getBean(this.getClass());
            Method[] methods = o.getClass().getMethods();
            for (Method m : methods) {
                BusinessLock businessLock = AnnotationUtils.findAnnotation(m, BusinessLock.class);
                boolean isLock = false;
                RLock rLock = null;
                if(null != businessLock){
                    isLock = true;
                    RedissonUtil redissonUtil = SpringContextUtils.getBean(RedissonUtil.class);
                    rLock = redissonUtil.getClint(o.getClass().getName() + "_" + m.getName() + "_" + businessLock.value());
                }
                try {
                    if(isLock) rLock.lock();
                    return (Result) m.invoke(o, json);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    if(isLock) rLock.unlock();
                }
            }
            throw new IllegalArgumentException("方法不存在" + o.getClass().getName());
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    2.5、进一步优化

    上述方法,似乎已经实现了功能,通过反射调用,在前后进行加解锁。
    好像还不能使用,新的问题又来了,我怎么知道反射调用哪个方法呢?
    也就是说接口进来,怎么知道调用实现类的哪个方法呢?

    OK,再搞一个注解吧

    import org.springframework.stereotype.Component;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Component
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface BusinessMethod {
        String value() default "";
        String[] params() default {};
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    BaseService 就得改造一下了

    import com.BusinessLock;
    import com.HandleMethod;
    import com.RedissonUtil;
    import com.SpringContextUtils;
    import org.redisson.api.RLock;
    import org.springframework.core.annotation.AnnotationUtils;
    import java.lang.reflect.Method;
    
    public interface BaseService {
    
        public default Result doBusiness(String json, String method) {
            Object o = SpringContextUtils.getBean(this.getClass());
            Method[] methods = o.getClass().getMethods();
            for (Method m : methods) {
                BusinessMethod businessMethod = AnnotationUtils.findAnnotation(m, BusinessMethod.class);
                if(null != businessMethod && businessMethod.value().equals(method)){
                    BusinessLock businessLock = AnnotationUtils.findAnnotation(m, BusinessLock.class);
                    boolean isLock = false;
                    RLock rLock = null;
                    if(null != businessLock){
                        isLock = true;
                        RedissonUtil redissonUtil = SpringContextUtils.getBean(RedissonUtil.class);
                        rLock = redissonUtil.getClint(o.getClass().getName() + "_" + m.getName() + "_" + businessLock.value());
                    }
                    try {
                        if(isLock) rLock.lock();
                        return (Result) m.invoke(o, json);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    } finally {
                        if(isLock) rLock.unlock();
                    }
                }
            }
            throw new IllegalArgumentException("方法不存在" + o.getClass().getName());
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    嗯,这样似乎就好一点了,不过还有另一种方式,调用那个method,是接口传的,本身接口拿到的就是报文,是不是可以在json中,加一个默认的参数,就叫interfaceMethod?
    这样,是不是就可以直接从json中获取方法,与注解中的值匹配了?

    2.6、使用一下看看

    注解,接口都好了,那下面就是使用了

    @Controller
    public class TestRedisson Controller {
    
        @Resource
        private ITestRedisson testRedisson;
        
        @RequestMapping("/testRedisson")
        @ResponseBody
        public Result doBusiness(@RequestBody String strJson) throws Exception {
            return testRedisson.doBusiness(strJson,"testRedisson");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    public interface ITestRedisson extends BaseService {
       
    }
    
    • 1
    • 2
    • 3
    public class TestRedisson implements ITestRedisson {
    
    	@BusinessLock("testRedisson")
        @BusinessMethod("testRedisson")
        public void testRedisson(){
            {
                //执行代码块
            }
        }
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    嗯,应该可以了,不过还可以优化优化,将这个method放进json中。

    六 : 作用域

    锁的作用域仅仅作用在某个类的某个方法上

    package com;
    
    import com.Result;
    import com.RedissonUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.redisson.api.RLock;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    import javax.annotation.Resource;
    
    @RestController
    @RequestMapping("/test")
    @Slf4j
    public class B {
    
        @Resource
        private RedissonUtil redissonUtil;
    
        @RequestMapping(value = "/api")
        @ResponseBody
        public Result methodOne(@RequestBody String strJson) throws Exception {
            RLock clint = redissonUtil.getClint(redissonUtil.getKey(this.getClass(), Thread.currentThread()));
            clint.lock();
            Thread.sleep(Integer.valueOf(strJson));
            this.methodTwo(strJson);
            clint.unlock();
            return Result.success(strJson);
        }
    
        @RequestMapping(value = "/api1")
        @ResponseBody
        public Result methodTwo(@RequestBody String strJson) throws Exception {
            Thread.sleep(Integer.valueOf(strJson));
            return Result.success(strJson);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    如上方法
    调用接口 /test/api 时,传参10000,这时方法 methodOne 加锁,方法内等待10秒。
    在等待的过程中调用接口 /test/api1 ,传参1,这时接口即可响应,并不受 methodOne 加锁的限制。
    由此得出,锁内调用的方法,不受加锁的影响,仍可供其他线程调用。

    七 : 总结

    对于金额等敏感数据的操作,一定需要注意是否会重复叠加。
    对表的更新,可以直接更新某个字段为某个值,也可以换种方式,更新某个字段,为字段本身加某个值。
    例如:update test set amount = amount + 100 where business_no = ‘test’;

    OK,整理到这吧!

    如有不正确之处,还望指正!书写不易,觉得有帮助就点个赞吧!☺☺☺

  • 相关阅读:
    spring 中的路径匹配
    springboot集成swagger3+解决页面无法访问问题
    如何配置node.js环境
    上帝视角看Vue源码整体架构+相关源码问答
    编译安装Linux内核
    【数据库系统概论】关系数据库中的关系完整性
    Linux常用命令——bmodinfo命令
    猿创征文| NoSQL数据库简介
    Linux 8:线程
    AWVS漏洞扫描使用基础与介绍
  • 原文地址:https://blog.csdn.net/qq_38254635/article/details/126398716