Java 篇-项目实战-天机学堂(从0到1)-day11

java 篇: 1.基础地基 2.设计原理 3.项目实战


分布式锁集群下的琐失效问题:

开启多个实例,模拟集群环境

记得在虚拟机选项当中把端口改了,把 local 去掉

然后三个服务启动

全部启动后,就注册到 Nacos 当中了,负载均衡,每刷新一次,一个服务后台就有日志了,轮着来

虚拟机地址,端口号是网关,最后记得加上服务的前缀

网关接收的是一个 token,不再是用户 id 了

复制粘贴一下这个 TOKEN

理论上是这样的结果

但是我 jmter 测试通过,但是数据库当中没有数据,不知道为何,先过。

分布式锁简单分布式锁原理:

如果加锁后,服务宕机了,那其他线程也拿不到锁了,所以需要加过期时间,并且加锁和过期时间得是原子性的。

setnx 不能设置过期时间,所以使用 set lock t1 nx ex 20

代码实现:

先建个类

实现获取锁和释放锁两个功能

好了,测试一下,这里我就不测了。

分布式锁的问题及 Redisson 简介:

不需要给 redis 和 Redisson 各配一套地址,只需要配置 Redis 就行了,Redisson 会按照 Redis 的方式读取,并完成自己的配置。

@ConditionalOnClass({RedissonClient.class, Redisson.class})

只有当 RedissonClient 和 Redisson 两个类同时存在于类路径(classpath)中时,被标注的配置类或 Bean 才会生效。

waitTime 重试时间(在这时间内不停尝试获取锁),leaseTime 锁释放时间

在 common 包的 pom.xml 下,已经有 redisson 的依赖

但是加了一个 scope,里面是 provided,意思是这个依赖,将来在打包的时候不会打包进去,将来是需要调用方提供的,谁引用了 common,谁需要提供。那这里引入,是因为让 RedissonConfig 这个自动装配的类不报错,它需要 redisson 相关东西,这样代码编译可以通过。

但因为加了 scope,打包的时候没有,那下面这句的条件就是不成立的,所以需要手动引入 Redisson 依赖,才能触发这个条件。

所以我们需要在调用它的 promotion 模块的 pom.xml 文件种手动引入依赖

改造代码:

参数支持无参、两参、三参,无参 l1 默认为 30s

这里采用无参

这里还有可以改进的地方,你看它无论有木有获取锁,最终都会释放锁。

现在好了

后面可以进行测试了

Redisson 的 lock.unlock(),内部已经用 LUA 脚本做了很多复杂的判断。并且自带看门狗机制,测试一下

用 sleep 模拟业务超时。开一个实例,用一个线程测就行了,

这里就显示阻塞卡住了

每过 10s(30/3)重置有效期为 30s

还支持可重入,存储结构为哈希,key(UUID+ 线程 id)就是线程标识,value 重入的次数,将来同一线程多次来获取,就 +1,释放锁就-1,到 0 就删除了

分布式锁基于自定义注解改造分布式锁:

① 自定义注解:

重试时间设置为 1s,过期设置为-1 默认,用看门狗。

@Retention()是用来标记注解的作用时间的,是运行期间呢,还是源码期间呢,还是字节码期间,这里是运行期间,@Target()标记作用目标,这里标记某个方法

②:定义环绕增强通知:

传入参数,一个切入点,一个切入点标记

别忘了加上

③:配置生效

当然也可以直接在 myLockAspect 类当中加 @Component 注解

改下业务代码:

这样其实还不能解决问题,因为事务注解先执行,然后锁再执行,那锁先释放,然后事务再提交,就又出现之前的问题了。所以得让锁注解先执行。

默认最大。值越小,越先执行。这里设置为 0,就行

分布式锁简单工厂模式改造分布式锁:

这里就固定了 reetrantLock 锁,但还有其他锁,为了通用性,需要进行改造。

先定义一个枚举类

reentrantLock、fairLock(公平锁)、readLock(读锁)、writeLock(写锁)

注解当中加入,之后对切面代码改造一下,

这里可以用工厂模式

目前就是把刚才的代码,换了个地方写

一个类型,对应一个行为,即对应一个操作,即对应一个方法,即对应一个函数。这个函数入参是字符串,返回值是 RLock

对切面代码进行改造:

把 config 里面注释掉,不再用 RedssonClient 了,用 MyLockFactory 了,上面已经加了 Component 了

分布式锁策略模式改造分布式锁:

用 if-else 或者 swith 来判断,从执行上来讲是个比较简单的逻辑,那现在呢,是基于 map 去 get 一个具体的 key,需要对 key 进行哈希运算,然后对数组取模,找到数组脚标。这样执行的流程就变复杂,性能就下降了

这里换成 EnumMap,不需要传入大小,只需要传入 MyLockType.class,它会根据传入的枚举类当中的枚举项确定大小的,也不用动态扩容了。而且枚举项是固定,那是不是可以对其进行编号,拿编号找对应的脚标就行

工厂改造完成后完整代码:

由于场景不同,处理逻辑也不同,有些是没获取到锁,就直接 return 了,有些是不停尝试,直到成功。

获取锁有多种方式,失败处理也有多种方式。组合起来就是获取锁的处理策略。

传统策略模式写法:

先去定义一个接口,里面声明获取锁的方法,然后根据五个策略,写五个实现类,每个实现类按照刚才分析的思路编写对应的逻辑,那还需要五个对象提前创建出来,跟锁工厂一样,整到一个 Map 当中。接着定义一个策略对应的枚举,封装到自定义注解当中,由用户选择具体哪个枚举,也就是对应的策略。然后再从工厂里取出对应的策略的实现类调用里面的方法。实现复杂

代码实现,在枚举类当中,实现相应的业务:

抽象接口,waitTime、leaseTime 从注解当中拿,prop 为 properity 简写

类似于匿名内部类

完整代码:

复制代码
package com.tianji.promotion.utils;

import com.tianji.common.exceptions.BizIllegalException;
import org.redisson.api.RLock;

public enum MyLockStrategy {
    _SKIP_FAST_(){
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            return lock.tryLock(0, prop.leaseTime(), prop.unit());
        }
    },
    _FAIL_FAST_(){
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            boolean isLock = lock.tryLock(0, prop.leaseTime(), prop.unit());
            if (!isLock){
                throw new BizIllegalException("请求太频繁");
            }
            return true;
        }
    },
    _KEEP_TRYING_(){
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            lock.lock(prop.leaseTime(), prop.unit());
            return true;
        }
    },
    _SKIP_AFTER_RETRY_TIMEOUT_(){
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            return lock.tryLock(prop.leaseTime(), prop.unit());
        }
    },
    _FAIL_AFTER_RETRY_TIMEOUT_(){
        @Override
        public boolean tryLock(RLock lock, MyLock prop) throws InterruptedException {
            boolean isLock = lock.tryLock(prop.waitTime(), prop.leaseTime(), prop.unit());
            if (!isLock){
                throw new BizIllegalException("请求太频繁");
            }
            return true;
        }
    },
    ;

    public abstract boolean tryLock(RLock lock, MyLock prop) throws InterruptedException;
}

改为 public ,方便调用

注解当中添加,默认是抛异常

那这样用户就可以自己选策略,当然不选,就用默认的

切面改造完成:

本来切面就是把锁逻辑和业务代码分开,然后锁类型又用工厂模式,又和切面类分开,现在锁的重试逻辑又分开了,每个类只干自己的事情,里面不能存在其他无关逻辑

分布式锁 SPEL 表达式动态锁名称:

用 SPEL 表达式实现动态锁名称:

得有个办法动态解析表达式,并且从参数列表当中寻找,替换

这个在 common 包当中已经写好了

这里换成 common 包当中的 Lock 就行了,开始测试

异步领券优化思路:

那返回什么消息给用户呢,所以我们必须把领券资格的判断放在发消息之前

那放到前面,执行一大串,又得考虑性能问题,所以得用 Redis 缓存

下面进行相关代码改造:

对于组织数据,有两种方案,第一种直接用 BeanUtil 将 coupon 转 map,因为 coupon 数据很多,但是我们这只要四个就够了,所以这里采用手动比较合适

这里不能直接 toString,转过去的时间结构可能不是我们想要的,这里采用把它转成毫秒值,就是从 1970 年开始的那个毫秒值,有两个好处,精度更准确,占用空间小。用到 DateUtils

最后别忘了加上事务

进行测试:

缓存没了

异步领券基于 Redis 的领券和消息发送:

导入 dto 类:

mq 常量

添加交换机:

添加路由键:

接下来进行改造代码:

.increment 增加以及返回增加后的结果,原子性操作

下面在 Promotion 模块下搞监听器

导入依赖

下面就可以进行测试了,这里就不测了。

练习 1 兑换码异步兑换的思路分析:

这里其实不需要保存所有兑换码 id,因为自增长是连续的,只需要记录每一个优惠券的起始范围

在这过程中多次向 Redis 发送请求,多次网络交互,性能会受到网络的影响,所以可以用 LUA 脚本。

代码改造:


如果对你有帮助的话,请点赞,关注,收藏。热爱可抵一切!👍 ❤️ 🔥

相关推荐
格林威1 小时前
线阵工业相机:线阵图像出现“波浪纹”,是机械振动还是编码器问题?
开发语言·人工智能·数码相机·计算机视觉·视觉检测·工业相机·线阵相机
light blue bird1 小时前
MES/ERP 工序 BOM 协同多节点工站组件
java·jvm·oracle
Pkmer2 小时前
古法编程: 适配器模式
java·设计模式
liliangcsdn2 小时前
LLM如何辅助RAG从大量文档中筛选目标文档
开发语言·人工智能
无忧.芙桃2 小时前
C++IO库的超详细讲解
开发语言·c++
longxibo2 小时前
【Flowable 7.2 源码深度解析与实战】
java·后端·流程图
norq juox2 小时前
Spring 中集成Hibernate
java·spring·hibernate
朗迹 - 张伟2 小时前
用AI开发QT——Qt与Trae开发环境搭建
开发语言·qt·策略模式