//@desc:java 高并发下锁机制初探

//@desc:码字不宜,转载请注明出处

//@author:张慧源  <turing_zhy@163.com>

//@date:2021/12/28

1.探究背景

大家可以看到在高并发下导致积分变动错误,所以想了一些办法解决

2.使用乐观锁去解决

a.添加了version字段

b.做了自定义注解去在乐观锁失败的时候进行重试

/**
* 是否进行方法重试注解
*
* @author abner<huiyuan.zhang @ hex-tech.net>
* @date 2021-12-24 19:59:38
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface IsTryAgain { }
/**
* 重试切面
*
* @author abner<huiyuan.zhang @ hex-tech.net>
* @date 2021-12-24 20:40:40
*/
@Aspect
@Component
public class TryAgainAspect { /**
* 默认重试几次
*/
private static final int DEFAULT_MAX_RETRIES = 5; private int maxRetries = DEFAULT_MAX_RETRIES; public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
} @Pointcut("@annotation(cn.hexcloud.m82.points.service.annotation.IsTryAgain)")
public void retryOnOptFailure() {
// pointcut mark
} @Around("retryOnOptFailure()")
@Transactional(rollbackFor = Exception.class)
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
do {
numAttempts++;
try {
//再次执行业务代码
return pjp.proceed();
} catch (TryAgainException ex) {
if (numAttempts > maxRetries) {
//log failure information, and throw exception
// 如果大于 默认的重试机制 次数,我们这回就真正的抛出去了
throw new ApiException(ApiResultEnum.ERROR_TRY_AGAIN_FAILED.getName());
} else {
//如果 没达到最大的重试次数,将再次执行
System.out.println("=====正在重试=====" + numAttempts + "次");
}
}
} while (numAttempts <= this.maxRetries); return null;
}
}

c.到这里也没啥问题,但是我代码里面还有做幂等的校验

//检查幂等
UserPointsDetail idemInfo = iUserPointsDetailService.checkIdem(orderInAddScoreInDto.getOrderNo()); if (idemInfo != null) {
throw new BizException(new RespInfo("该笔积分已经添加过了-idemStr:" + orderInAddScoreInDto.getOrderNo()));
}

这个拦不住,所以我就想直接去加锁

2.1 小插曲,中间还想过用for update这种悲观锁去做解决自测锁表的情况很普遍

建议:如果不带主键,建议不要用for update悲观锁,有主键也慎用!

3.先做一个锁性能对代码并发性的影响探究

提前准备:安装abtest压测工具:https://www.jianshu.com/p/a7ee2ffb5c0f

a.不加锁下的并发

并发 195/s

b.synchronized 锁

并发28/s

c.lock 锁

并发 159/s

遗留问题:需要弄清Lock有哪些,每种的作用是什么

然后发现在集群环境下,这些锁也不能解决问题,需要用redis锁

4.Redis 分布式锁

依赖

<!--redis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
</dependency> <!--配置redis client-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>

助手函数

package cn.hexcloud.m82.points.service.utils.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; import javax.annotation.Resource; /**
* redis锁助手函数
* @author abner<huiyuan.zhang@hex-tech.net>
* @date 2021-12-29 11:49:34
*/
@Component
@Slf4j
public class RedisLockUtils { /**
* 设置超时时间10秒
*/
public static final int TIMEOUT = 10*1000; @Resource
private StringRedisTemplate stringRedisTemplate; /**
* 获取锁过期时间
* @author abner<huiyuan.zhang@hex-tech.net>
* @date 2021-12-29 12:02:05
* @return 锁过期时间
*/
public Long getLockOverdueTime(){
return System.currentTimeMillis() + TIMEOUT;
} /**
* 加锁
* @author abner<huiyuan.zhang@hex-tech.net>
* @date 2021-12-29 11:50:07
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value){
if(stringRedisTemplate.opsForValue().setIfAbsent(key, value)){
return true;
}
String currentValue = stringRedisTemplate.opsForValue().get(key);
//如果锁过期
if(!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()){
//获取上一个锁的时间
String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
if(!StringUtils.isEmpty(oldValue) && currentValue.equals(oldValue)){
return true;
}
}
return false;
} /**
* 解锁
* @author abner<huiyuan.zhang@hex-tech.net>
* @date 2021-12-29 11:51:04
* @param key
* @param value 当前时间+超时时间
* @return
*/
public void unlock(String key, String value){
try{
String currentValue = stringRedisTemplate.opsForValue().get(key);
if(!StringUtils.isEmpty(currentValue) && currentValue.equals(value)){
stringRedisTemplate.opsForValue().getOperations().delete(key);
}
}catch (Exception e){
log.error("【Redis分布式锁】 解锁异常 {}", e.getMessage());
}
}
}

在impl中使用

Long lockOverdueTime = redisLockUtils.getLockOverdueTime();
String lockKey = Thread.currentThread().getStackTrace()[1].getMethodName() + ":" + partnerId + ":" + userId; boolean isLock = redisLockUtils.lock(lockKey, String.valueOf(lockOverdueTime));
if (!isLock) {
throw new TryAgainException(ApiResultEnum.ERROR_TRY_AGAIN);
} try {
//业务代码 } catch (BizException e) {
throw e;
} finally {
//解锁
redisLockUtils.unlock(lockKey, String.valueOf(lockOverdueTime));
}

redis 锁可以实现很小粒度的锁(我的例子中是租户+用户维度的锁),而且可以解决多节点的并发,是我心头的好!

java 高并发下超购问题解决的更多相关文章

  1. java高并发下的数据安全

    高并发下的数据安全 我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的).如果是My ...

  2. Java高并发下多线程编程

    1.创建线程 Java中创建线程主要有三种方式: 继承Thread类创建线程类: 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此也把run方法称为 ...

  3. Java高并发下的 “单例模式”

    前言:单例模式大家应该很熟悉了,我在这里就自己总结一下自己这段时间学到的单例相关的知识. 单例模式的目的:保证一个类只有单一的实例,也就是说你无法通过new来创建这个类的一个新实例. 单例模式的意义: ...

  4. 高并发下的Java数据结构(List、Set、Map、Queue)

    由于并行程序与串行程序的不同特点,适用于串行程序的一些数据结构可能无法直接在并发环境下正常工作,这是因为这些数据结构不是线程安全的.本节将着重介绍一些可以用于多线程环境的数据结构,如并发List.并发 ...

  5. JAVA跨域、RestTemplate高并发下异常与配置、JSON数据Long转String

    ## 跨域支持 import org.springframework.context.annotation.Bean; import org.springframework.context.annot ...

  6. Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解

    Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解 说明:Java生鲜电商平台-生鲜电商高并发下的接口幂等性实现与代码讲解,实际系统中有很多操作,是不管做多少次,都应该产生一样的效果或返 ...

  7. PHP+Redis链表解决高并发下商品超卖问题

    目录 实现原理 实现步骤 上一篇文章聊了一下使用Redis事务来解决高并发商品超卖问题,今天我们来聊一下使用Redis链表来解决高并发商品超卖问题. 实现原理 使用redis链表来做,因为pop操作是 ...

  8. EF+MySQL乐观锁控制电商并发下单扣减库存,在高并发下的问题

    下订单减库存的方式 现在,连农村的大姐都会用手机上淘宝购物了,相信电商对大家已经非常熟悉了,如果熟悉电商开发的同学,就知道在买家下单购买商品的时候,是需要扣减库存的,当然有2种扣减库存的方式, 一种是 ...

  9. 【mysql】mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况 + 同一事务中使用多个乐观锁的情况处理

    mysql增加version字段实现乐观锁,实现高并发下的订单库存的并发控制,通过开启多线程同时处理模拟多个请求同时到达的情况 ==================================== ...

  10. 海量数据和高并发下的 Redis 业务优化实践

    本文内容是我在 6 月 23 日参加的深圳 GIAC 技术大会上演讲的文字稿. 观众朋友们,我是来自掌阅的工程师钱文品,掘金小册<Redis 深度历险>的作者.今天我带来的是分享主题是:R ...

随机推荐

  1. 香港Azure/.NET俱乐部第一次聚会纪实 - WPF在金融业的商业价值

    香港Azure/.NET俱乐部第一次聚会于2019年12月29日在香港上环地铁站星巴克举行. 香港Azure/.NET俱乐部的定位是:以商业价值为导向. 基于这个定位,可以推导出如下准则: 面向大型企 ...

  2. 容器镜像加速指南:探索 Kubernetes 缓存最佳实践

    介绍 将容器化应用程序部署到 Kubernetes 集群时,由于从 registry 中提取必要的容器镜像需要时间,因此可能会出现延迟.在应用程序需要横向扩展或处理高速实时数据的情况下,这种延迟尤其容 ...

  3. 如何使用LOTO示波器 绘制 频率响应特性曲线?

    在工作和项目中,经常会遇到一个功能电路模块对信号进行调理,或滤波,或放大,或衰减,或阻抗变换.这些功能电路模块可能是无源阻容的,也可能是有源的运放电路,也可能是更复杂的系统.但是它们对信号进行调理的最 ...

  4. 3D Object Detection Essay Reading 2024.04.01

    Swin Transformer paper: https://arxiv.org/abs/2103.14030 (ICCV 2021) code:https://github.com/microso ...

  5. 陈海波:OpenHarmony技术领先,产学研深度协同,生态蓬勃发展

      11月4日,以"技术筑生态,智联赢未来"为主题的第二届OpenHarmony技术大会在北京隆重举办.本次大会由OpenAtom OpenHarmony(简称"Open ...

  6. HR必备|可视化大屏助HR实现人才资源价值最大化

    人力资源管理质量的优劣关系到企业可持续发展目标的实现,在信息化时代背景下,应用信息技术加强人力资源管理过程的优化,利用技术提升人力资源管理质量和效率已是大势所趋. 利用信息技术构建信息化人力资源管理平 ...

  7. HarmonyOS自定义抽奖转盘开发(ArkTS)

      介绍 本篇Codelab是基于画布组件.显式动画,实现的一个自定义抽奖圆形转盘.包含如下功能: 1.  通过画布组件Canvas,画出抽奖圆形转盘. 2.  通过显式动画启动抽奖功能. 3.  通 ...

  8. Causal Inference理论学习篇-Tree Based-Causal Tree

    Tree-Based Algorithms Tree-based这类方法,和之前meta-learning 类的方法最明显的区别是: 这类方法把causal effect 的计算显示的加入了到了树模型 ...

  9. k8s 深入篇———— docker 是什么[一]

    前言 简单的整理一下一些基本概念. 正文 简单运行一个容器: 创建一个容器: docker run -it busybox /bin/bash 然后看下进程: ps -ef 做了一个障眼法,使用的是p ...

  10. mm系列权重文件瘦身

    瘦身脚本: (会在resnet50.pth文件的同级目录下生成一个resnet50_thin.pth) import os import torch root_dir = os.getcwd() de ...