当前位置: 首页 > wzjs >正文

婚纱网站建设 最开始俄罗斯跨境电商平台ozon

婚纱网站建设 最开始,俄罗斯跨境电商平台ozon,重装的系统没有wordpress,网站建站侵权怎么办文章目录一、前言二、项目背景三、解决方案四、思路延展1. Lock4j 分布式锁1.1 基础使用1.1.1 基础示例1.1.2 自定义异常策略1.4 源码简析2. AOP 切面的执行顺序五、参考内容一、前言 本系列用来记录一些在实际项目中的小东西,并记录在过程中想到一些小东西&#x…

文章目录

  • 一、前言
  • 二、项目背景
  • 三、解决方案
  • 四、思路延展
    • 1. Lock4j 分布式锁
      • 1.1 基础使用
        • 1.1.1 基础示例
        • 1.1.2 自定义异常策略
      • 1.4 源码简析
    • 2. AOP 切面的执行顺序
  • 五、参考内容


一、前言

本系列用来记录一些在实际项目中的小东西,并记录在过程中想到一些小东西,因为是随笔记录,所以内容不会过于详细。

二、项目背景

在 A 项目中,存在多个聊天角色,每个角色对应都对应一个会话,具体的创建逻辑是当用户点击到会话页面时会调用一个 createConversation 接口,这个接口会去查找后台所有没有创建过会话的角色,并为其创建会话。

基于以上 :

  • 由于这个接口会创建多个会话,所以需要加上事务。
  • 为了避免并发调用该接口时会话的重复创建,需要在接口中加上分布式锁。

因此该需求的大致逻辑如下:

  @SneakyThrows@Transactional(rollbackFor = Exception.class)@Overridepublic int createConversation() {RLock lock = redisson.getLock("lock");try {// 尝试获取锁,等待10秒,自动释放时间为10秒if (lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS)) {// 查询未创建会话的角色List<Role> roles = findNeedCreateConversationRole();// 为其创建会话createConversationForRoles(roles);} else {log.warn("分布式锁获取失败");}} finally {lock.unlock();}return 0;}

这里存在两个问题:

  1. 分布式锁的锁时长只有 10s,10s 后如果接口逻辑未执行完则锁会释放:这里可以直接使用 Redission 的看门狗机制来自动续期,非本篇重点,不再赘述。

    看门狗的工作原理

    • 默认续期时间:30 秒(可通过 Config.lockWatchdogTimeout 配置)
    • 自动续期逻辑:
      1. 线程获取锁成功后,Redisson 会启动一个后台定时任务
      2. 每过 lockWatchdogTimeout / 3 秒(默认 10 秒)检查锁是否还被持有
      3. 如果锁仍被持有,则自动将锁的过期时间延长至 lockWatchdogTimeout(默认 30 秒)
      4. 直到线程释放锁或发生异常
  2. 分布式锁的释放先于事务,则可能存在锁已经释放但事务未提交时,新的请求进来导致会话重复插入。而事实上我们确实出现了这个问题。

三、解决方案

我们以下面 Demo为例 :

    @Transactional(rollbackFor = Exception.class)@Overridepublic int insertWithLock(String name) {RLock lock = redisson.getLock("lock");try {// 尝试获取锁,等待10秒,自动释放时间为10秒if (lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS)) {DataDemo dataDemo = new DataDemo();dataDemo.setId(IdUtil.getSnowflake().nextId());dataDemo.setName(name);dataDemoMapper.insert(dataDemo);} else {log.warn("分布式锁获取失败");}} finally {lock.unlock();}return 0;}

按照我们一开始的设想,即使并发请求,也只有一个请求会获取到分布式锁,从而保证了请求的幂等性,但实际上,当我们实际尝试就会发现上面的写法并不能保证幂等性,关键在于 事务的提交和锁的释放的先后顺序关系。

即当两个请求 A,B 并发请求时,其执行顺序可能如下:

序号请求A请求 B说明
1事务 A 开启
2事务 B 开启
3获取分布式锁
4插入数据
5释放分布式锁此时分布式锁已经释放,但事务 A 尚未提交
6获取分布式锁由于事务 A 已经将分布式锁释放,所以这里事务 B 可以获取到分布式锁
7插入数据
8释放分布式锁
9事务A提交
10事务B提交

基于上述的执行顺序,则可能造成分布式锁的 “失效” 问题,解决方案也很简单,因为锁先于事务释放,所以可以先加锁,再开启事务即可,如下(使用声明式事务保证事务的开启和提交都是在锁的作用范围内):

//    @Transactional(rollbackFor = Exception.class)@Overridepublic int insertWithLock(String name) {RLock lock = redisson.getLock("lock");try {// 尝试获取锁,等待10秒,自动释放时间为10秒if (lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS)) {transactionTemplate.executeWithoutResult(transactionStatus -> {DataDemo dataDemo = new DataDemo();dataDemo.setId(IdUtil.getSnowflake().nextId());dataDemo.setName(name);dataDemoMapper.insert(dataDemo);});} else {log.warn("分布式锁获取失败");}} finally {lock.unlock();}return 0;}

四、思路延展

1. Lock4j 分布式锁

除了上面给出例子中自己实现分布式锁外,还可以直接引入 Lock4J 来简化分布式锁的实现。

Lock4J 并没有 Redission 的看门狗机制,这也就意味着如果业务执行时间超过锁的过期时间,锁会自动释放,可能导致多个线程同时访问临界区。

Lock4J 是一个基于 Spring Boot 的分布式锁框架,旨在简化分布式系统中的锁管理,支持 Redis、ZooKeeper 等多种分布式锁实现,而在 Spring 中 Lock4J 为这些不同的实现提供的不同的依赖,可以在项目中引入如下依赖来使用功能:

<!-- 基于 Redisson 实现的分布式锁 -->
<dependency><groupId>com.baomidou</groupId><artifactId>lock4j-redisson-spring-boot-starter</artifactId><version>2.2.7</version>
</dependency><!-- 基于 ZooKeeper 实现的分布式锁 -->
<dependency><groupId>com.baomidou</groupId><artifactId>lock4j-zookeeper-spring-boot-starter</artifactId><version>2.2.7</version>
</dependency>

1.1 基础使用

1.1.1 基础示例

我们可以通过 @Lock4j 注解来实现一个分布式锁,如下:

    @Override@SneakyThrows@Lock4j(keys = {"#lockKey"}, expire = 30000, acquireTimeout = 1000, autoRelease = false)public void lock2Do(String lockKey, String lockName) {Thread.sleep(1000);log.info("[lock2Do][{}-{} 获取到分布式锁]", lockKey, lockName);}

@Lock4j 注解存在几个参数,如下:

  • name :用于多个方法锁同一把锁 可以理解为锁资源名称 为空则会使用 包名+类名+方法名
  • executor :指定锁执行,如 Redis、ZooKeeper。(如果容器中有多个Redis 或 Zk 实例,也可以在不同的方法指定不同的实例)
  • keys :锁键表达式,支持 SpEL(如 #lockKey 表示方法参数 lockKey)。
  • expire :锁的过期时间(毫秒),过期时间一定是要长于业务的执行时间。未设置则为默认时间30秒
  • acquireTimeout :获取锁的等待时间(毫秒),结合业务,建议该时间不宜设置过长,特别在并发高的情况下。未设置则为默认时间3秒。
  • autoRelease :业务方法执行完后(方法内抛异常也算执行完)自动释放锁,如果为false,锁将不会自动释放直至到达过期时间才释放
  • failStrategy :锁获取失败后的处理策略,我们可以自定义失败处理策略
  • keyBuilderStrategy :key 的生成策略

Lock4j 的默认配置都是从 com.baomidou.lock.spring.boot.autoconfigure.Lock4jProperties 中获取,我们可以在 yaml 中通过配置修改默认值。

lock4j:acquire-timeout: 3000expire: 30000retry-interval: 100

1.1.2 自定义异常策略

Lock4J 提供了锁获取失败的默认处理(抛出 LockException),也支持自定义异常处理器,我们只需要在自定义一个实现 LockFailureStrategy 接口的类注册到容器中即可启用,如下:

@Component
@Slf4j
public class LogLockFailureStrategy implements LockFailureStrategy {@Overridepublic void onLockFailure(String key, Method method, Object[] arguments) {log.info("[onLockFailure][{}-{} 获取分布式锁失败]", key, method.getName());}
}

使用方式则是直接在 Lock4j 注解的 failStrategy 属性指定即可,如下:

    @SneakyThrows@Lock4j(keys = {"#lockKey"}, failStrategy = LogLockFailureStrategy.class)public void lock2Do(String lockKey, String lockName) {Thread.sleep(5000);log.info("[lock2Do][{}-{} 获取到分布式锁]", lockKey, lockName);}

LockInterceptor 在注入到Spring 容器中会将容器中的 LockFailureStrategy 实例注入到其中,在获取分布式锁失败时,会根据指定的失败策略类型获取到对应的策略执行。

1.4 源码简析

Lock4J 功能的实现基于 @Lock4J 注解,在 Spring 中这种注解实现显然是通过 AOP 实现的,而 Lock4J 的增强实现则是 LockAnnotationAdvisor 。

Spring AOP 的具体实现在 Spring源码分析:全集整理 的 AOP 部分有过分析,这里不再赘述。


LockAnnotationAdvisor 的部分关键代码如下:

public class LockAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {private final Advice advice;private final Pointcut pointcut = new AnnotationMethodPoint(Lock4j.class);public LockAnnotationAdvisor(@NonNull LockInterceptor lockInterceptor, int order) {this.advice = lockInterceptor;// 用于 Spring AOP 排序setOrder(order);}// 切面类的实现:判断逻辑就是判断当前方法是否被 @Lock4j 注解修饰。@Overridepublic Pointcut getPointcut() {return this.pointcut;}// 切面的增强,其实现由构造函数传入@Overridepublic Advice getAdvice() {return this.advice;}...
}

上面可以知道,AOP 增强的逻辑在 LockInterceptor 中。下面我们来看LockInterceptor#invoke 的实现,如下:

AnnotationMethodPoint 的逻辑就是判断方法是否被 @Lock4j 注解修饰,如果修饰则满足条件。

@Override
public Object invoke(MethodInvocation invocation) throws Throwable {// 修复使用其他AOP组件时方法被多次代理的问题// 通过AopProxyUtils获取目标对象的最终类Class<?> cls = AopProxyUtils.ultimateTargetClass(invocation.getThis());// 如果最终类与当前代理对象的类不相同,直接放行调用if (!cls.equals(invocation.getThis().getClass())) {return invocation.proceed();}// 从方法上解析Lock4j注解信息Lock4j lock4j = AnnotatedElementUtils.findMergedAnnotation(invocation.getMethod(), Lock4j.class);LockInfo lockInfo = null;try {// 构建锁操作对象,包含锁键生成器和失败策略LockOperation lockOperation = buildLockOperation(lock4j);// 构建锁键前缀,格式为:配置前缀:注解名称(或类名+方法名)String prefix = lock4jProperties.getLockKeyPrefix() + ":";prefix += StringUtils.hasText(lock4j.name()) ? lock4j.name() :invocation.getMethod().getDeclaringClass().getName() + invocation.getMethod().getName();// 构建完整锁键:前缀#SpEL表达式解析后的键String key = prefix + "#" + lockOperation.lockKeyBuilder.buildKey(invocation, lock4j.keys());// 尝试获取锁,参数:锁键、过期时间、获取超时时间、锁执行器lockInfo = lockTemplate.lock(key, lock4j.expire(), lock4j.acquireTimeout(), lock4j.executor());// 成功获取锁则继续执行目标方法if (null != lockInfo) {return invocation.proceed();}// 锁获取失败处理lockOperation.lockFailureStrategy.onLockFailure(key, invocation.getMethod(), invocation.getArguments());return null;} finally {// 自动释放锁配置检查if (null != lockInfo && lock4j.autoRelease()) {// 释放锁并记录失败日志final boolean releaseLock = lockTemplate.releaseLock(lockInfo);if (!releaseLock) {log.error("releaseLock fail,lockKey={},lockValue={}", lockInfo.getLockKey(),lockInfo.getLockValue());}}}
}

该方法的整个时间还是比较清楚的,具体逻辑已经添加注释,这里不再赘述。

当我们需要一些更复杂的自定义分布式锁的逻辑,可以直接使用 lockTemplate 来完成自定义的分布所锁的逻辑。

2. AOP 切面的执行顺序

在使用 Lock4j 时会额外引申出一个问题:在一个方法中同时添加 @Transactional@Lock4j 注解时,他们增强的顺序是如何确定的?如果 @Transactional 的增强在 @Lock4j 外层,则不是又出现了 “先开启事务再加锁” 的情况了吗?

先说结论:在 Spring AOP 中存在多个切面对同一个方法增强时,会对各个切面进行排序,多个切面可以通过实现 Ordered 接口 或者切面类上添加 @Order 注解,指定优先级,数字越小,越先执行。

Spring AOP 会向容器中注册一个自动代理创建器,在Bean 创建时会 AbstractAdvisorAutoProxyCreator#findEligibleAdvisors 方法来查找容器中所有的 Advisor,如下:

	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {List<Advisor> candidateAdvisors = findCandidateAdvisors();List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 对 所有的 Advisor 进行排序,按照 Ordered 接口或 @Order 注解指定的优先级进行排序,数字越小优先级越高。eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;}

而 LockAnnotationAdvisor 默认的优先级是 Ordered.HIGHEST_PRECEDENCE (Integer.MIN_VALUE),所以其优先级最高,会优先于 @Transactional 注解执行,从而保证 “先加锁后开启事务” 的情况。


五、参考内容

  1. 豆包

文章转载自:

http://nYPc3aTB.qcjqd.cn
http://MyuMF7LN.qcjqd.cn
http://SxyUfns6.qcjqd.cn
http://JmFOaceq.qcjqd.cn
http://LFgI38Li.qcjqd.cn
http://3It8NPD0.qcjqd.cn
http://iptTjAkK.qcjqd.cn
http://NH6pscA6.qcjqd.cn
http://pHjWhfD2.qcjqd.cn
http://dMOv5cFF.qcjqd.cn
http://146ZyRXs.qcjqd.cn
http://W2Sod3SN.qcjqd.cn
http://Py0Lf4IM.qcjqd.cn
http://8qhUfoO8.qcjqd.cn
http://XW4HdnEb.qcjqd.cn
http://TWwhmWOl.qcjqd.cn
http://Cyd7BC1U.qcjqd.cn
http://zSBM16QB.qcjqd.cn
http://co4X74DQ.qcjqd.cn
http://FX7OwaTG.qcjqd.cn
http://Mn7JXZO4.qcjqd.cn
http://fQgrUpKC.qcjqd.cn
http://MlUI1n4p.qcjqd.cn
http://OfNNti9g.qcjqd.cn
http://8dNZZGc1.qcjqd.cn
http://tCQjGieo.qcjqd.cn
http://MECPrpXH.qcjqd.cn
http://lYzgdQxa.qcjqd.cn
http://uJFlGEtD.qcjqd.cn
http://8TVagj56.qcjqd.cn
http://www.dtcms.com/wzjs/661084.html

相关文章:

  • 佛山建设公司网站网站布局设计分析特点
  • 设计网站界面工程项目信息网
  • 怎么样查看网站开发语言端点seo博客
  • 北京国贸网站建设公司动画制作软件an
  • 已有域名怎么做网站洛阳网络公司排名
  • 网站推广的途径和要点个人网站制作与设计论文
  • 移动网站技术建设工程公司组织架构图
  • 自助网站开发海外营销公司
  • 网站前台怎么做凡客官网旗舰店
  • 个人博客网站制作流程文件夹里内容做网站的分类
  • 驻马店北京网站建设wordpress 无广告视频
  • 怎样在建设部网站上查公司信息佛山网站建设哪个
  • 网站建设编码公司网站图片传不上去
  • 北京海淀工商局网站阳江人才招聘网官网
  • 在凡科做网站本地网站做不大
  • 自己可以做百度网站吗佛山推广系统
  • 网站做百度推广需要什么材料小网站托管费用
  • 滁州网站建设工作室网站编辑的工作内容
  • 公司网站建设任务书做矿业的郑州公司网站
  • 全国做网站公司前十名有了域名空间怎么做网站
  • 云闪付当前页面设计隐私长春网络推广长春seo公司
  • 做网站一般用什么系统凡客v 网上商城
  • nft制作网站花瓣网设计网站
  • 素材匹配网站青岛 机械 中企动力提供网站建设
  • 网站开发总监招聘企业网站快速备案服务
  • 百度站长平台论坛北京企业网站怎么建设
  • 中国建设银行陕西分行网站设计师在线接单
  • 北京通州住房和城乡建设部网站阿里巴巴logo图片
  • 大连开发区网站开发公司电话wordpress文章链接带问号
  • 智慧旅游网站开发与设计与实现备案网站域名查询