当前位置:   article > 正文

SpringBoot集成Redisson

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,整理到这吧!

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

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号