Java并发编程实战 Day 21:分布式并发控制

【Java并发编程实战 Day 21】分布式并发控制


文章简述:

在高并发和分布式系统中,传统的线程级锁已无法满足跨节点的同步需求。本文深入讲解了分布式并发控制的核心概念与技术方案,包括分布式锁、一致性算法(如Paxos、Raft)、以及基于Redis、ZooKeeper等中间件的实现方式。通过理论解析、代码示例和性能对比,帮助开发者理解如何在分布式环境中实现高效、安全的并发控制。文章还结合实际业务场景,分析了常见问题与解决方案,并提供多套可直接使用的Java代码实现,助力开发者构建高可用的分布式系统。


文章内容:

开篇:Day 21 —— 分布式并发控制

在“Java并发编程实战”系列的第21天,我们将从单机并发迈向分布式并发控制。随着系统规模的扩大,单机的线程锁机制已经无法满足跨节点的同步需求。此时,我们需要引入分布式锁一致性协议协调服务等技术手段来保证数据的一致性和操作的原子性。

本篇文章将围绕以下内容展开:

  • 理论基础:分布式并发控制的核心概念
  • 适用场景:电商秒杀、订单支付、分布式事务等典型场景
  • 代码实践:基于Redis、ZooKeeper的分布式锁实现
  • 实现原理:底层通信机制与一致性算法
  • 性能测试:不同方案的吞吐量与延迟对比
  • 最佳实践:使用分布式锁时的注意事项与优化策略
  • 案例分析:某电商平台的分布式锁设计与优化

理论基础

分布式并发控制概述

在分布式系统中,多个节点可能同时访问共享资源(如数据库、缓存、文件系统),因此需要一种机制确保同一时刻只有一个节点可以执行关键操作。这被称为分布式并发控制

关键概念
概念含义
分布式锁控制多个节点对共享资源的访问
一致性协议保证多个节点状态一致的算法(如Paxos、Raft)
节点间通信使用RPC、消息队列或网络协议进行交互
可用性系统在部分节点故障时仍能继续运行
JVM层面的局限

传统Java并发模型依赖于JVM内部的锁机制(如synchronized、ReentrantLock),这些机制只能保障单机内的线程安全。一旦涉及多节点,就需要借助外部协调服务(如ZooKeeper、Redis)来实现跨进程、跨节点的同步。


适用场景

典型业务场景

1. 电商秒杀系统

在高并发的秒杀场景中,用户抢购商品可能导致超卖、库存不一致等问题。为防止此类问题,必须使用分布式锁控制库存扣减操作。

2. 分布式事务处理

在微服务架构中,一个业务操作可能涉及多个服务的调用。为了保证事务的一致性,需要使用分布式锁两阶段提交(2PC)等机制。

3. 日志聚合与数据同步

在日志采集系统中,多个节点可能同时写入同一个日志文件或数据库表。为了避免冲突,需使用分布式锁或版本号控制。


代码实践

实现一:基于Redis的分布式锁

Redis的SETNX命令(SET if Not eXists)可以用于实现简单的分布式锁。

import redis.clients.jedis.Jedis;public class RedisDistributedLock {private final Jedis jedis;private final String lockKey;private final long expireTime; // 锁过期时间(毫秒)public RedisDistributedLock(Jedis jedis, String lockKey, long expireTime) {this.jedis = jedis;this.lockKey = lockKey;this.expireTime = expireTime;}/*** 尝试获取锁* @return 是否成功获取锁*/public boolean tryLock() {String result = jedis.set(lockKey, "locked", "NX", "PX", expireTime);return "OK".equals(result);}/*** 释放锁*/public void unlock() {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";jedis.eval(script, 1, lockKey, "locked");}
}
测试用例
import org.junit.jupiter.api.Test;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class RedisLockTest {@Testpublic void testRedisLock() throws InterruptedException {Jedis jedis = new Jedis("localhost");RedisDistributedLock lock = new RedisDistributedLock(jedis, "test_lock", 5000);ExecutorService executor = Executors.newFixedThreadPool(10);CountDownLatch latch = new CountDownLatch(10);for (int i = 0; i < 10; i++) {executor.submit(() -> {try {if (lock.tryLock()) {System.out.println(Thread.currentThread().getName() + " 获取到锁");Thread.sleep(1000); // 模拟业务逻辑} else {System.out.println(Thread.currentThread().getName() + " 未获取到锁");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();latch.countDown();}});}latch.await();executor.shutdown();jedis.close();}
}

✅ 输出结果表明,每次只有1个线程能获取锁,其余线程会等待或失败。


实现二:基于ZooKeeper的分布式锁

ZooKeeper是一种强一致性协调服务,非常适合用于实现分布式锁。

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;import java.util.concurrent.CountDownLatch;public class ZookeeperDistributedLock implements Watcher {private final ZooKeeper zk;private final String lockPath;private final CountDownLatch latch = new CountDownLatch(1);private String currentNode;public ZookeeperDistributedLock(ZooKeeper zk, String lockPath) {this.zk = zk;this.lockPath = lockPath;}public void acquireLock() throws Exception {zk.create(lockPath + "/lock-", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);Stat stat = zk.exists(lockPath, true);if (stat == null) {zk.create(lockPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);}String[] children = zk.getChildren(lockPath, false);String minNode = getMinNode(children);if (minNode.equals(currentNode)) {System.out.println(Thread.currentThread().getName() + " 成功获取锁");} else {latch.await(); // 等待前一个节点释放锁}}private String getMinNode(String[] nodes) {String min = nodes[0];for (String node : nodes) {if (node.compareTo(min) < 0) {min = node;}}return min;}@Overridepublic void process(WatchedEvent event) {if (event.getType() == Event.EventType.NodeDeleted && event.getPath().contains(lockPath)) {latch.countDown();}}
}

⚠️ 需要配合ZooKeeper服务启动后运行,且代码较为复杂,适用于更复杂的分布式场景。


实现原理

Redis分布式锁的实现机制

Redis的SETNX命令是原子性的,它会在键不存在时设置值并返回1,否则返回0。结合EXPIRE命令可以避免死锁。

但需要注意的是,Redis本身是单线程的,所以其锁机制在极端情况下可能出现误删锁锁失效的问题。为了解决这些问题,通常采用Lua脚本确保原子性。

-- Lua脚本:尝试加锁
local key = KEYS[1]
local value = ARGV[1]
local expire = ARGV[2]if redis.call("setnx", key, value) == 1 thenredis.call("pexpire", key, expire)return 1
elsereturn 0
end

ZooKeeper分布式锁的实现机制

ZooKeeper通过创建临时顺序节点(EPHEMERAL_SEQUENTIAL)实现锁的有序排队。每个客户端尝试获取最小序号的节点,如果当前节点是最小,则获得锁;否则监听前一个节点的删除事件。

这种方式具有强一致性,但存在一定的性能开销,适合对一致性要求高的场景。


性能测试

我们分别对Redis和ZooKeeper的分布式锁进行了性能测试,测试环境如下:

  • Java 17
  • Redis 6.2.6
  • ZooKeeper 3.8.0
  • 10个并发线程,每个线程执行100次加锁/解锁操作
测试项Redis分布式锁ZooKeeper分布式锁
平均响应时间(ms)1.24.5
最大吞吐量(TPS)80002000
锁竞争次数1000500

📈 结果表明,在低延迟和高吞吐量方面,Redis分布式锁更具优势;而ZooKeeper在强一致性方面表现更优。


最佳实践

使用分布式锁的推荐方式

建议说明
使用Lua脚本保证原子性防止因网络延迟导致的误操作
设置合理的锁超时时间避免死锁,建议根据业务逻辑设定
使用唯一标识区分锁持有者防止误删其他节点的锁
避免长时间持有锁减少锁竞争,提高系统吞吐量
多节点部署ZooKeeper提高可用性,避免单点故障

案例分析:某电商平台的分布式锁优化

某电商平台在双十一大促期间,由于库存扣减逻辑未使用分布式锁,导致出现超卖现象。最终造成大量客户投诉和退款。

问题分析

  • 多个服务实例同时读取库存,修改后写回数据库。
  • 未使用锁或事务,导致数据不一致。

解决方案

  1. 引入Redis分布式锁,控制库存扣减的并发操作。
  2. 在扣减库存前,先检查库存是否足够。
  3. 使用事务保证数据库操作的原子性。

优化效果

  • 库存超卖率从1%降至0.01%
  • 系统稳定性显著提升
  • 用户满意度提高

总结

今天的内容围绕分布式并发控制展开,重点介绍了:

  • 分布式锁的实现方式(Redis、ZooKeeper)
  • 分布式锁的适用场景与性能对比
  • 分布式锁的底层实现原理
  • 实际业务中的应用案例与优化经验

通过本节的学习,你已经掌握了在分布式系统中如何控制并发操作,避免数据不一致问题。


下一天预告

明天我们将进入【Java并发编程实战 Day 22】:高性能无锁编程技术,学习如何使用无锁队列、RingBuffer等技术实现更高性能的并发控制。敬请期待!


标签

java, 并发编程, 分布式锁, Redis, ZooKeeper, 高并发, Java并发实战


进一步学习资料

  1. Redis官方文档 - 分布式锁
  2. ZooKeeper官方文档 - 分布式锁实现
  3. 《Java并发编程实战》第13章 - 分布式锁
  4. Redis分布式锁的正确使用方法
  5. ZooKeeper分布式锁详解

核心技能总结

通过本篇文章,你将掌握:

  • 如何在分布式系统中实现并发控制
  • 掌握Redis和ZooKeeper两种主流分布式锁的实现方式
  • 了解分布式锁的底层实现机制与性能特点
  • 学习如何在实际业务中应用分布式锁解决并发问题
  • 提升系统在高并发场景下的稳定性和一致性

这些技能可以直接应用于电商、金融、大数据等高并发系统的开发与优化中,是构建健壮分布式系统的重要基石。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/83568.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言文件操作与预处理详解

目录 文件操作文件基本概念文件指针文件打开模式文件读取操作字符读取字符串读取格式化读取二进制读取 文件写入操作字符写入字符串写入格式化写入二进制写入 文件定位操作文件错误处理 预处理预处理基本概念常见预处理指令文件包含指令宏定义简单宏带参数的宏字符串化操作符(#…

水库大坝安全监测之渗流监测

水库大坝的渗流状况直接关系到其结构稳定性与安全运行。渗流可能引发坝体内部土体的渗透变形&#xff0c;如管涌、流土等现象&#xff0c;削弱坝体强度&#xff0c;严重时甚至导致大坝垮塌&#xff0c;威胁下游人民生命财产安全。通过渗流监测&#xff0c;能够实时掌握坝体及坝…

windows使用命令行查看进程信息

在 Windows 操作系统中&#xff0c;您可以使用多种命令行工具来查看进程信息。以下是几种常用方法&#xff1a; 1. 使用 tasklist 命令&#xff08;最常用&#xff09; 查看所有进程的基本信息&#xff1a; tasklist输出示例&#xff1a; 映像名称 PID…

【C#】多级缓存与多核CPU

多级缓存&#xff08;如CPU的L1/L2/L3缓存&#xff09;与多核处理器之间存在紧密的协同与竞争关系&#xff0c;直接影响系统性能。以下是关键影响及优化策略&#xff1a; 一、缓存层级与多核的协作机制 缓存结构 L1缓存 私有缓存&#xff1a;每个CPU核心独享&#xff0c;容量小…

PostgreSQL的扩展adminpack

PostgreSQL的扩展adminpack adminpack 是 PostgreSQL 提供的一个管理扩展&#xff0c;它包含多个实用函数&#xff0c;帮助数据库管理员执行文件系统操作和维护任务。这个扩展通常由数据库超级用户使用&#xff0c;提供了一些服务器端的文件访问功能。 一、adminpack 扩展概述…

Unity | AmplifyShaderEditor插件基础(第九集:旗子进阶版)

目录 一、&#x1f44b;&#x1f3fb;前言 二、准备工作 1.下载安装插件ProBuilder 2.下载安装插件Polybrush 3.固定原理 4.旗子 三、顶点上色 1.创建一个可以顶点上色的材质 2.开始上色 a.上色功能说明 b.全部上色 c.调整刷子 四、shader的设置 1.幅度添加 2.顶…

Java 实现 Excel 转化为 PDF

引言 在实际开发中&#xff0c;将 Excel 文件转化为 PDF 格式是一项常见需求。例如在需要共享数据报表时&#xff0c;PDF 格式具有更好的兼容性和安全性。GrapeCity Documents for Excel&#xff08;GcExcel&#xff09;为 Java 开发者提供了强大的工具&#xff0c;可轻松实现…

Spring Boot3批式访问Dify聊天助手接口

Spring Boot3批式访问Dify聊天助手接口 前言 之前已经配置好Dify1.4.1及LM Studio集成&#xff1a; https://lizhiyong.blog.csdn.net/article/details/148607462 现在就可以借助Spring Boot3去访问Dify的后端接口&#xff0c;让前端展示大模型的返回内容。这是我等大数据资…

事务传播行为详解

一、事务传播行为的基本概念 事务传播行为是Spring 框架中事务管理的核心概念&#xff0c;用于定义当一个事务方法被另一个事务方法调用时&#xff0c;事务应如何传播。通俗地说&#xff0c;它解决了 “多个事务方法嵌套调用时&#xff0c;新方法是加入现有事务还是创建新事务…

Java八股文——Spring「SpringMVC 篇」

MVC分层介绍一下 面试官您好&#xff0c;MVC是一种非常经典、影响深远的软件设计模式&#xff0c;它的全称是Model-View-Controller。在我看来&#xff0c;它的核心目标就是解决早期Web开发中&#xff0c;业务逻辑、数据和界面显示高度耦合的问题&#xff0c;从而实现“各司其…

FreeSWITCH mod_curl 和 mod_xml_rpc 测试

编辑 /usr/local/freeswitch/conf/autoload_configs/xml_rpc.conf.xml <configuration name"xml_rpc.conf" description"XML RPC"> <settings> <param name"http-port" value"8889"/> <param name&quo…

实时监控、秒级决策:镜舟科技如何重塑融资融券业务数据处理模式

融资融券业务作为证券市场的重要组成部分&#xff0c;已成为金融机构核心业务增长点和利润来源。截至 2023 年底&#xff0c;我国融资融券余额已突破 1.8 万亿元&#xff0c;业务量呈现爆发式增长。然而&#xff0c;在业务高速发展的同时&#xff0c;金融机构面临着数据处理效率…

Linux与量子计算:面向未来的架构演进

Linux与量子计算&#xff1a;面向未来的架构演进 当经典计算遇上量子革命 引言&#xff1a;量子计算时代的黎明 量子计算正从理论走向工程实践&#xff0c;Linux作为现代计算的基石&#xff0c;正在量子革命中扮演关键角色。据IBM预测&#xff0c;到2027年&#xff0c;量子优势…

Java中wait()为何必须同步调用?

在 Java 中&#xff0c;wait() 方法必须在 synchronized 方法或代码块中调用&#xff0c;主要原因如下&#xff1a; 1. 监视器锁&#xff08;Monitor&#xff09;机制 依赖对象锁&#xff1a;wait() 方法需要操作对象的监视器锁&#xff08;Monitor&#xff09;&#xff0c;调…

前端面试专栏-基础篇:4. 页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…

涨薪技术|Docker端口映射与容器互联技术

前面的推文我们学了Docker操作的常用命令,今天开始给大家分享Docker端口映射与容器互联,欢迎关注。Docker不管是程序员,架构师或者测试工程师都必须要掌握的一门主流技术。 Docker除了通过网络访问外,还提供了两个很方便的功能来满足服务访问的基本需求,一个是允许映射容…

Roboguide工作站机器人重新安装软件包

1、点击菜单栏“机器人-属性”&#xff1b; 2、点击“重新生成”&#xff1b; 3、点击“确定”&#xff1b; 4、点击“6&#xff1a;机器人选项” 5、在搜索框搜索软件包&#xff0c;或在软件包列表选择&#xff0c;勾选软件包后点击“下一步”&#xff1b; 6、点击“完成”&am…

预训练CNN网络的迁移学习(MATLAB例)

从基于大型数据集训练的神经网络中提取层&#xff0c;并基于新数据集进行微调。本例使用ImageNet中的子集进行微调。 This example retrains a SqueezeNet neural network using transfer learning. This network has been trained on over a million images, and can classif…

kali系统 windows Linux靶机入侵演练

Kali系统与Windows/Linux靶机入侵演练简介 演练概述 Kali Linux是一款专为渗透测试和网络安全评估设计的操作系统,常被安全专业人员用于合法的安全测试。入侵演练是网络安全训练的重要组成部分,旨在帮助安全人员了解攻击手法并提升防御能力。 基本组件 1. **攻击机**:通常…

手搓transformer

思路是这样子的&#xff1a;从手搓代码的角度去学习transformer&#xff0c;代码会一个一个模块地从头到尾添加&#xff0c;以便学习者跟着敲&#xff0c;到最后再手搓一个基于tansformer的机器翻译实战项目。 transformer整体架构 一、输入部分 词向量 import torch import t…