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

分布式并发场景的核心问题与解决方案

文章目录

  • 分布式并发场景的核心问题与解决方案
    • 一、核心问题分析
      • 1. 分布式事务问题
      • 2. 数据一致性问题
      • 3. 并发控制问题
      • 4. 分布式锁失效问题
    • 二、解决方案
      • 1. 分布式事务解决方案
        • 1.1 可靠消息最终一致性方案
        • 1.2 TCC方案实现
      • 2. 缓存一致性解决方案
        • 2.1 延迟双删策略
        • 2.2 Canal方案
      • 3. 并发控制解决方案
        • 3.1 基于Redis的原子操作
        • 3.2 防重复提交
    • 三、系统监控与告警
      • 1. 分布式链路追踪
      • 2. 监控指标收集
    • 四、最佳实践建议
    • 五、注意事项

分布式并发场景的核心问题与解决方案

一、核心问题分析

1. 分布式事务问题

在分布式环境下,一个业务操作可能横跨多个服务,比如创建订单时涉及:

  • 订单服务:创建订单
  • 库存服务:扣减库存
  • 支付服务:冻结余额
  • 积分服务:赠送积分

可能出现的问题:

  • 部分服务成功,部分服务失败
  • 网络超时导致事务状态不确定
  • 服务宕机导致事务中断

2. 数据一致性问题

在分布式系统中,由于CAP理论的限制,我们通常需要在一致性和可用性之间做出选择。

典型场景:

  • 主从数据库的数据同步延迟
  • 分布式缓存的数据一致性
  • 跨服务的数据依赖

3. 并发控制问题

多个节点同时处理请求时的并发控制:

  • 超卖问题
  • 重复下单
  • 数据竞争

4. 分布式锁失效问题

  • Redis主从切换导致锁失效
  • 时钟不同步导致的锁判断错误
  • 网络分区导致的锁状态不一致

二、解决方案

1. 分布式事务解决方案

1.1 可靠消息最终一致性方案
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Autowiredprivate RocketMQTemplate rocketMQTemplate;@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate MessageMapper messageMapper;@Transactional(rollbackFor = Exception.class)public void createOrder(OrderDTO orderDTO) {// 1. 事务消息表记录TransactionMessage message = new TransactionMessage();message.setMessageId(UUID.randomUUID().toString());message.setMessage(JSON.toJSONString(orderDTO));message.setStatus(MessageStatus.PREPARING);messageMapper.insert(message);// 2. 创建订单Order order = convertToOrder(orderDTO);orderMapper.insert(order);// 3. 发送事务消息sendTransactionMessage(message);}private void sendTransactionMessage(TransactionMessage message) {Message msg = MessageBuilder.withPayload(message).setHeader("messageId", message.getMessageId()).build();TransactionSendResult result = rocketMQTemplate.sendMessageInTransaction("ORDER_TOPIC",msg,null);if (result.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE) {throw new BusinessException("发送事务消息失败");}}
}

消息消费者实现:

@Component
@RocketMQMessageListener(topic = "ORDER_TOPIC",consumerGroup = "order-consumer-group"
)
public class OrderMessageListener implements RocketMQListener<Message> {@Autowiredprivate StockService stockService;@Autowiredprivate MessageMapper messageMapper;@Overridepublic void onMessage(Message message) {String messageId = message.getHeaders().get("messageId", String.class);// 1. 检查消息是否已处理if (messageMapper.checkProcessed(messageId)) {return;}try {// 2. 处理业务逻辑TransactionMessage txMessage = JSON.parseObject(new String((byte[]) message.getPayload()),TransactionMessage.class);OrderDTO orderDTO = JSON.parseObject(txMessage.getMessage(),OrderDTO.class);// 3. 扣减库存stockService.decreaseStock(orderDTO.getProductId(), orderDTO.getQuantity());// 4. 更新消息状态messageMapper.markAsProcessed(messageId);} catch (Exception e) {// 5. 失败处理messageMapper.markAsFailed(messageId, e.getMessage());// 根据业务需求决定是否抛出异常重试throw e;}}
}
1.2 TCC方案实现
@Service
public class OrderTccServiceImpl implements OrderTccService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate StockTccService stockTccService;@Autowiredprivate PaymentTccService paymentTccService;@GlobalTransactionalpublic void createOrder(OrderDTO orderDTO) {// 1. Try阶段// 1.1 订单服务TryOrder order = prepareTryOrder(orderDTO);// 1.2 库存服务TrystockTccService.tryDecrease(orderDTO.getProductId(),orderDTO.getQuantity());// 1.3 支付服务TrypaymentTccService.tryFreeze(orderDTO.getUserId(),orderDTO.getAmount());}// Try阶段的订单处理private Order prepareTryOrder(OrderDTO orderDTO) {Order order = convertToOrder(orderDTO);order.setStatus(OrderStatus.TRY);orderMapper.insert(order);return order;}// Confirm阶段的订单处理public void confirmOrder(BusinessActionContext context) {String orderId = context.getActionContext("orderId").toString();Order order = orderMapper.selectById(orderId);order.setStatus(OrderStatus.CONFIRMED);orderMapper.updateById(order);}// Cancel阶段的订单处理public void cancelOrder(BusinessActionContext context) {String orderId = context.getActionContext("orderId").toString();Order order = orderMapper.selectById(orderId);order.setStatus(OrderStatus.CANCELED);orderMapper.updateById(order);}
}

2. 缓存一致性解决方案

2.1 延迟双删策略
@Service
public class ProductServiceImpl implements ProductService {@Autowiredprivate ProductMapper productMapper;@Autowiredprivate RedisTemplate<String, Product> redisTemplate;@Autowiredprivate ThreadPoolExecutor threadPoolExecutor;private static final String PRODUCT_CACHE_KEY = "product:";private static final long DELAY_DELETE_TIME = 1000; // 1秒@Transactional(rollbackFor = Exception.class)public void updateProduct(Product product) {// 1. 删除缓存String cacheKey = PRODUCT_CACHE_KEY + product.getId();redisTemplate.delete(cacheKey);// 2. 更新数据库productMapper.updateById(product);// 3. 延迟双删threadPoolExecutor.execute(() -> {try {Thread.sleep(DELAY_DELETE_TIME);redisTemplate.delete(cacheKey);} catch (InterruptedException e) {Thread.currentThread().interrupt();log.error("延迟双删失败", e);}});}
}
2.2 Canal方案
@Component
public class ProductCanalClient {@Autowiredprivate RedisTemplate<String, Product> redisTemplate;@Listen(table = "product")public void handleProductChange(CanalEntry.Entry entry) {if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {CanalEntry.RowChange rowChange = entry.getRowChange();for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {if (rowChange.getEventType() == CanalEntry.EventType.UPDATE) {// 处理更新事件handleProductUpdate(rowData);} else if (rowChange.getEventType() == CanalEntry.EventType.DELETE) {// 处理删除事件handleProductDelete(rowData);}}}}private void handleProductUpdate(CanalEntry.RowData rowData) {// 解析变更数据Map<String, String> data = parseRowData(rowData.getAfterColumnsList());String productId = data.get("id");// 更新缓存String cacheKey = "product:" + productId;Product product = convertToProduct(data);redisTemplate.opsForValue().set(cacheKey, product);}
}

3. 并发控制解决方案

3.1 基于Redis的原子操作
@Service
public class StockService {@Autowiredprivate StringRedisTemplate redisTemplate;private static final String STOCK_KEY = "product:stock:";public boolean decreaseStock(Long productId, Integer quantity) {String key = STOCK_KEY + productId;// Lua脚本保证原子性String script = "local stock = redis.call('get', KEYS[1]) " +"if stock and tonumber(stock) >= tonumber(ARGV[1]) then " +"    return redis.call('decrby', KEYS[1], ARGV[1]) " +"end " +"return -1";DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(script);redisScript.setResultType(Long.class);Long result = redisTemplate.execute(redisScript,Collections.singletonList(key),quantity.toString());return result != null && result >= 0;}
}
3.2 防重复提交
@Aspect
@Component
public class RepeatSubmitAspect {@Autowiredprivate StringRedisTemplate redisTemplate;@Around("@annotation(repeatSubmit)")public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader("token");String key = getRepeatSubmitKey(joinPoint, token);// 使用Redis的setIfAbsent实现防重boolean isNotRepeat = redisTemplate.opsForValue().setIfAbsent(key,"1",repeatSubmit.interval(),TimeUnit.MILLISECONDS);if (!isNotRepeat) {throw new BusinessException("请勿重复提交");}return joinPoint.proceed();}
}

三、系统监控与告警

1. 分布式链路追踪

@Configuration
public class SleuthConfig {@Beanpublic Sampler defaultSampler() {return Sampler.ALWAYS_SAMPLE;}
}

2. 监控指标收集

@Component
public class DistributedMetrics {@Autowiredprivate MeterRegistry registry;// 记录分布式锁获取情况private Counter lockCounter;private Timer lockTimer;@PostConstructpublic void init() {lockCounter = registry.counter("distributed.lock.acquire");lockTimer = registry.timer("distributed.lock.time");}public void recordLockAcquire(String lockKey, boolean success) {lockCounter.increment();Tags tags = Tags.of("lock_key", lockKey,"success", String.valueOf(success));registry.counter("distributed.lock.acquire", tags).increment();}public void recordLockTime(String lockKey, long timeMillis) {lockTimer.record(timeMillis, TimeUnit.MILLISECONDS);}
}

四、最佳实践建议

  1. 业务设计层面

    • 尽量避免复杂分布式事务
    • 考虑业务可补偿性
    • 合理设计重试机制
  2. 技术选型层面

    • 优先考虑消息队列解耦
    • 合理使用缓存
    • 选择合适的分布式事务方案
  3. 监控运维层面

    • 完善的监控系统
    • 合理的告警阈值
    • 灾难恢复预案
  4. 性能优化层面

    • 合理的数据分片策略
    • 避免长事务
    • 批量处理优化

五、注意事项

  1. 数据库层面

    • 避免大事务
    • 合理设计索引
    • 注意死锁问题
  2. 缓存层面

    • 防止缓存雪崩
    • 注意缓存穿透
    • 合理设置过期时间
  3. 消息队列层面

    • 保证消息可靠性
    • 处理重复消息
    • 注意消息顺序性
  4. 分布式锁层面

    • 防止锁失效
    • 避免死锁
    • 合理设置超时时间

相关文章:

分布式并发场景的核心问题与解决方案

文章目录 分布式并发场景的核心问题与解决方案一、核心问题分析1. 分布式事务问题2. 数据一致性问题3. 并发控制问题4. 分布式锁失效问题 二、解决方案1. 分布式事务解决方案1.1 可靠消息最终一致性方案1.2 TCC方案实现 2. 缓存一致性解决方案2.1 延迟双删策略2.2 Canal方案 3.…...

D - Many Segments 2(ABC377)

题意&#xff1a;给定n和m&#xff0c;给定n个区间li&#xff0c;ri&#xff0c;求出满足区间lr不完全包含区间liri的个数 分析&#xff1a;用优先队列对区间r进行排序&#xff0c;i表示左区间&#xff0c;每次找到右区间加入即可。 代码&#xff1a; #include<bits/stdc…...

数组指针和指针数组的区别

数组指针和指针数组的区别 根据我个人的理解如下&#xff1a; 数组指针&#xff1a;指向数组的指针。着重点在于最后的指针两个字。 指针数组&#xff1a; 所有元素都是指针的数组。着重点在于最后的数组两个字。 另外来看助手的回答: Kimi: 1. **数组指针&#xff08;Ar…...

【VUE点击父组件按钮,跳转到子组件】

要实现在Vue中&#xff0c;父组件通过点击按钮进入子组件的 <el-dialog> 弹窗&#xff0c;并在弹窗中嵌套 <el-table> 表格&#xff0c;可以按照以下步骤进行编写代码&#xff1a; 在父组件中&#xff0c;定义一个数据属性用于控制子组件弹窗的显示与隐藏。 data…...

Java列表排序:方法与实践

在Java编程中&#xff0c;列表排序是一个常见且重要的任务。本文将介绍Java中对列表进行排序的几种方法&#xff0c;包括使用Collections.sort()、List.sort()以及自定义排序规则。 1. 使用Collections.sort() Collections.sort()是Java提供的一个静态方法&#xff0c;用于对…...

哈希及其封装实现unordermap和set

哈希 直接定址法 哈希和之前的红黑树的区别就是&#xff0c;它是通过映射关系来找到目标的&#xff0c;可以把它想象成之前排序的计数排序&#xff0c;那其实就是哈希的一种方法&#xff0c;叫做直接定址法。 对于比较集中的数据&#xff0c;它只需要开一段区间&#xff0c;…...

在 AMD GPU 上构建解码器 Transformer 模型

Building a decoder transformer model on AMD GPU(s) — ROCm Blogs 2024年3月12日 作者 Phillip Dang. 在这篇博客中&#xff0c;我们展示了如何使用 PyTorch 2.0 和 ROCm 在单个节点上的单个和多个 AMD GPU 上运行Andrej Karpathy’s beautiful PyTorch re-implementation …...

Canvas简历编辑器-选中绘制与拖拽多选交互设计

Canvas简历编辑器-选中绘制与拖拽多选交互设计 在之前我们聊了聊如何基于Canvas与基本事件组合实现了轻量级DOM&#xff0c;并且在此基础上实现了如何进行管理事件以及多层级渲染的能力设计。那么此时我们就依然在轻量级DOM的基础上&#xff0c;关注于实现选中绘制与拖拽多选交…...

简单工厂(Simple Factory)

简单工厂&#xff08;Simple Factory&#xff09; 在创建一个对象时不向客户暴露内部细节&#xff0c;并提供一个创建对象的通用接口。 说明&#xff1a; 简单工厂把实例化的操作单独放到一个类中&#xff0c;这个类就成为简单工厂类&#xff0c;让简单工厂类来决定应该用哪…...

ffmpeg拉流分段存储到文件-笔记

通过ffmpeg可以从rtsp网络流拉取数据并存储到本地文件里&#xff0c;如下命令。做个笔记 ffmpeg -rtsp_transport tcp -i rtsp://192.168.1.168:6880/live -c copy -f segment -segment_time 60 stream_piece_%d.mp4这条 ffmpeg 命令的作用是从一个 RTSP 流中捕获视频&#xff…...

Java 实习工资大概是多少?——解读影响薪资的因素

文章目录 1. 城市因素&#xff1a;一线、二线的差距2. 公司类型&#xff1a;互联网公司、外企和传统企业的差别3. 个人能力&#xff1a;经验、技术栈的重要性4. 其他影响因素&#xff1a;学历和实习时间总结推荐阅读文章 Java 开发作为广泛应用的职业方向&#xff0c;实习工资的…...

【Linux】万字详解:Linux文件系统与软硬链接

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 &#x1f680; 前言 一&#xff1a; &#x1f525; 磁盘的物理结构二&#xff1a; &#x1f525; 磁盘的存储结构 三&#xff1a; &#x1f525; 磁盘的逻辑结构 四&#xff1a; &#…...

spacenavd

介绍spacenavd开源项目&#xff0c;主要是因为在斯坦福大学的UMI项目中使用了该项目。在斯坦福大学的 UMI&#xff08;Universal Manipulation Interface&#xff09;项目中&#xff0c;Spacenavd 主要用于处理 3D Space Mouse&#xff08;空间鼠标&#xff09;的输入&#xf…...

C#WPF的XAML的语法详谈和特性

WPF的XAML&#xff08;eXtensible Application Markup Language&#xff09;是一种基于XML的标记语言&#xff0c;用于在.NET框架中定义和描述用户界面。XAML提供了一种声明性的方式来构建应用程序的UI元素&#xff0c;包括窗口、控件、布局、样式、动画和数据绑定等。 XAML的…...

一篇文章讲透数据结构之二叉搜索树

前言 在前面的学习过程中&#xff0c;我们已经学习了二叉树的相关知识。在这里我们再使用C来实现一些比较难的数据结构。 这篇文章用来实现二叉搜索树。 一.二叉搜索树 1.1二叉搜索树的定义 二叉搜索树&#xff08;Binary Search Tree&#xff09;是基于二叉树的一种升级版…...

新手入门c++(8)

到时候了&#xff0c;是时候给你们讲一下其他的定义形式与格式化输入输出了。 1.长整型变量 长整型变量分为两种&#xff1a; ①long类型 在计算机编程中&#xff0c;long 类型是一个整型数据类型&#xff0c;用于存储较大的整数。它的大小和范围取决于操作系统和编译器的实…...

新手铲屎官提问,有哪几款噪音低的宠物空气净化器推荐

相信很多铲屎官都明白的的痛就是猫咪掉毛太严重&#xff0c;所以每次看到满天飞的浮毛时只想赶紧逃离&#xff0c;一点都不想清理。但是家是自己的&#xff0c;猫是自己的&#xff0c;健康也是自己的&#xff0c;不清理也得清理。 为了更有效的清理浮毛&#xff0c;我朋友特意…...

解决RabbitMQ脑裂问题

文章目录 前言一、现象二、解决办法 前言 RabbitMQ脑裂 一、现象 RabbitMQ镜像群出现脑裂现象&#xff0c;各个节点的MQ实例都“各自为政”&#xff0c;数据并不同步。 二、解决办法 # 停止mq sh rabbitmq-server stop_app # 查看mq进程是否存在 ps -ef | grep rabbitmq # …...

经纬恒润AUTOSAR成功适配芯钛科技Alioth TTA8车规级芯片

在汽车电子领域&#xff0c;功能安全扮演着守护者的角色&#xff0c;它确保了车辆在复杂多变的情况下保持稳定可靠的运行。随着汽车电子的复杂性增加&#xff0c;市场对产品功能安全的要求也日益提高。基于此背景&#xff0c;经纬恒润AUTOSAR基础软件产品INTEWORK-EAS-CP成功适…...

4、java random随机数、一维数组、二维数组

目录 Random类与随机数生成数组的概述与使用数组的内存分配与访问数组的常见问题与解决方案一维数组的遍历与操作二维数组的概述与遍历1. Random类与随机数生成 引言 在编程中,我们经常需要生成随机数,比如在游戏、模拟实验或者数据处理中。Java提供了一个非常方便的类Rand…...

C++ 魔法三钥:解锁高效编程的封装、继承与多态

快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 目录 &#x1f4af;前言 &#x1f4af;封装 1.封装概念 2.封装格式 3.封装的原理 4.封装的作用 &#x1f4af;继承 1.继承的概念 2.继承格式 3.继承的…...

姿态传感器(学习笔记上)

上节我们学的是温湿传感器&#xff0c;这节我们学的是姿态传感器&#xff0c;虽然都是传感器&#xff0c;但是它们还是有很大的区别的&#xff0c;这节的传感器我们通过学习可知&#xff0c;开发板上的姿态传感器型号是QMI8658C&#xff0c;内部集成3轴加速度传感器和3轴陀螺仪…...

labelimg使用教程

快捷键 W&#xff1a;调出标注的十字架&#xff0c;开始标注 A&#xff1a;切换到上一张图片 D&#xff1a;切换到下一张图片 del&#xff1a;删除标注的矩形框 CtrlS&#xff1a;保存标注好的标签 Ctrl鼠标滚轮&#xff1a;按住Ctrl&#xff0c;然后滚动鼠标滚轮&#xff0c;…...

力扣21 : 合并两个有序链表

链表style 描述&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例&#xff1a; 节点大小相同时&#xff0c;l1的节点在前 何解&#xff1f; 1&#xff0c;遍历两个链表&#xff0c;挨个比较节点大小 同时遍…...

【Spring】Spring Boot 配置文件(7)

本系列共涉及4个框架&#xff1a;Sping,SpringBoot,Spring MVC,Mybatis。 博客涉及框架的重要知识点&#xff0c;根据序号学习即可。 有什么不懂的都可以问我&#xff0c;看到消息会回复的&#xff0c;可能会不及时&#xff0c;请见谅&#xff01;&#xff01; 1、配置文件作…...

《向量数据库指南》——解锁Wikipedia文章向量的跨语言搜索秘籍

嘿,各位向量数据库和AI应用的小伙伴们,我是你们的老朋友王帅旭,大禹智库的向量数据库高级研究员,也是《向量数据库指南》的作者。今天,咱们来聊聊一个超棒的数据集——百万条 Wikipedia 文章向量,这可是我在研究过程中发现的一个宝藏啊! 首先,咱们得说说这个数据集的来…...

【力扣 + 牛客 | SQL题 | 每日5题】牛客SQL热题204,201,215

1. 力扣1126&#xff1a;查询活跃业务 1.1 题目&#xff1a; 事件表&#xff1a;Events ------------------------ | Column Name | Type | ------------------------ | business_id | int | | event_type | varchar | | occurrences | int | --------…...

下载数据集用于图像分类并自动分为训练集和测试集方法

一、背景 最近需要用Vision Transformer&#xff08;ViT&#xff09;完成图像分类任务&#xff0c;因此查到了WZMIAOMIAO的GitHub&#xff0c;里面有各种图像处理的方法。而图像处理的前期工作就是获取大量的数据集&#xff0c;用于训练模型参数&#xff0c;以准确识别或分类我…...

Python xlrd库介绍

一、简介 xlrd是一个用于读取Excel文件(.xls和.xlsx格式)的Python库。它提供了一系列函数来访问Excel文件中的数据&#xff0c;如读取工作表、单元格的值等。 二、安装 可以使用以下命令安装xlrd库&#xff1a; pip install xlrd 三、使用方法 1. 导入库&#xff1a; 示例…...

Javascript立即执行函数

//立即执行函数 把函数的声明看作一个整体声明结束就立即调用 // (function(){console.log(hello) // })(); console.log((function (){ return 0; })()); // let afunction(){ console.log(hello) }; console.log(typeof a);//function,数组&#xff1a;objeck...