SpringBoot集成Redisson

一 : 什么是Redisson?

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

下面开整!!!

二 : 引入pom文件

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.14.0</version>
</dependency>

正常是这样直接引入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>

三 : 增加配置文件

在使用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;
    }
}

四 : 工具类

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

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();
    }

}

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

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

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

五 : 如何应用

1、手动加锁

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

public class TestRedisson {

	@Resource
    private RedissonUtil redissonUtil;

    public void testRedisson(){
        //定义Key
        String myKey = ")!@#$%^&*(";
        redissonUtil.lock(myKey);
        {
            //执行代码块
        }
        redissonUtil.unLock(myKey);
    }

}

如果不想获取两次客户端

public class TestRedisson {

	@Resource
    private RedissonUtil redissonUtil;

    public void testRedisson(){
        //定义Key
        String myKey = ")!@#$%^&*(";
        RLock clint = redissonUtil.getClint(myKey);
        clint.lock();
        {
            //执行代码块
        }
        clint.unlock();
    }

}

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 "";
    
}

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

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());
    }

}

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 {};
}

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());
    }

}

嗯,这样似乎就好一点了,不过还有另一种方式,调用那个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");
    }
}
public interface ITestRedisson extends BaseService {
   
}
public class TestRedisson implements ITestRedisson {

	@BusinessLock("testRedisson")
    @BusinessMethod("testRedisson")
    public void testRedisson(){
        {
            //执行代码块
        }
    }
    
}

嗯,应该可以了,不过还可以优化优化,将这个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);
    }

}

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

七 : 总结

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

OK,整理到这吧!

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

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
概要介绍: 本课程主要是介绍并实战一款java中间件~redisson,介绍redisson相关的核心技术栈及其典型的应用场景,其中的应用场景就包括布隆过滤器、限流器、短信发送、实时/定时邮件发送、数据字典、分布式服务调度等等,在业界号称是在java项目里正确使用redis的姿势。本课程的目标就在于带领各位小伙伴一起学习、攻克redisson,更好地巩固自己的核心竞争力,而至于跳槽涨薪,自然不在话下!  课程内容: 说起redisson,可能大伙儿不是很熟悉,但如果说起redis,想必肯定很多人都晓得。没错,这家伙字如其名,它就是架设在redis基础上的一款综合性的、新型的中间件,号称是java企业级应用开发中正确使用redis的姿势/客户端实例。 它是架设在redis基础之上,但拥有的功能却远远多于原生Redis 所提供的,比如分布式对象、分布式集合体系、分布式锁以及分布式服务调度等一系列具有分布式特性的对象实例… 而这些东西debug将在本门课程进行淋漓尽致的介绍并实战,除此之外,我们将基于spring boot2.0搭建的多模块项目实战典型的应用场景:对象存储、数据字典、短信发送、实时/定时邮件发送、布隆过滤器、限流组件、分布式服务调度....课程大纲如下所示: 下面罗列一下比较典型的核心技术栈及其实际业务场景的实战,如下图所示为redisson基于订阅-发布模式的核心技术~主题Topic的实际业务场景,即实时发送邮件: 而下图则是基于“多值映射MultiMap”数据结构实战实现的关于“数据字典”的缓存管理: 除此之外,我们还讲解了可以与分布式服务调度中间件dubbo相媲美的功能:分布式远程服务调度,在课程中我们动手搭建了两个项目,用于分别充当“生产者”与“消费者”角色,最终通过redisson的“服务调度组件”实现服务与服务之间、接口与接口之间的调用!  课程收益: (1)认识并掌握redisson为何物、常见的几种典型数据结构-分布式对象、集合、服务的应用及其典型应用场景的实战; (2)掌握如何基于spring boot2.0整合redisson搭建企业级多模块项目,并以此为奠基,实战企业级应用系统中常见的业务场景,巩固相应的技术栈! (3)站在项目管理与技术精进的角度,掌握对于给定的功能模块进行业务流程图的绘制、分析、模块划分、代码实战与性能测试和改进,提高编码能力与其他软实力; (4)对于Java微服务、分布式、springboot精进者而言,学完本课程,不仅可以巩固提高中间件的实战能力,其典型的应用场景更有助于面试、助力相关知识点的扫盲! 如下图所示: 关键字:Spring BootRedis,缓存穿透,缓存击穿,缓存雪崩,红包系统,Mybatis,高并发,多线程并发编程,发送邮件,列表List,集合Set,排行榜,有序集合SortedSet,哈希Hash ,进阶实战,面试,微服务、分布式 适用人群:redisson学习者,分布式中间件实战者,微服务学习者,java学习者,spring boot进阶实战者,redis进阶实战者

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

message丶小和尚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值