分布式锁的 Java 实现与性能对比:从实战落地到选型指南(一) - 指南

news/2025/10/9 22:09:24/文章来源:https://www.cnblogs.com/slgkaifa/p/19132108

分布式锁的 Java 实现与性能对比:从实战落地到选型指南(一) - 指南

核心价值:覆盖电商秒杀、分布式任务调度、库存扣减 3 大高频场景,拆解 Redis(原生 / Redisson)、ZooKeeper、数据库 3 类 Java 实现方案,提供 “问题场景→原理图解→实战代码→故障案例→性能测试” 闭环,附 30 + 段可复用代码、6 张架构 / 时序图,帮你避开 90% 分布式锁落地坑。

一、为什么需要分布式锁?—— 从业务故障切入

在单机系统中,synchronizedReentrantLock可解决多线程资源竞争,但分布式系统(微服务集群)中,多节点竞争同一资源(如库存、任务)时,本地锁完全失效,直接引发业务故障:

1.1 典型故障案例:电商秒杀超卖

场景:某电商秒杀活动,商品库存 100 件,部署 3 个订单服务节点,用本地锁控制库存扣减。

问题:活动开始后,3 个节点同时读取库存 = 100,各自扣减后最终库存 =-20,出现超卖。

根因:本地锁仅能控制单节点内的线程竞争,无法跨节点同步资源状态(3 个节点的本地锁互不感知)。

1.2 分布式锁的 3 大核心需求

  1. 互斥性:同一时间仅允许 1 个节点的 1 个线程获取锁;

  2. 高可用:锁服务无单点故障(避免锁不可用导致业务中断);

  3. 安全性:锁需自动释放(避免死锁,如节点宕机后锁无法释放);

  4. 可选特性:重入性(同一线程可重复获取锁)、公平性(按请求顺序获取锁)、高性能(支持高并发)。

二、方案 1:基于 Redis 的分布式锁(高并发首选)

Redis 因高性能、部署简单,成为高并发场景(如秒杀)的分布式锁首选,常见实现分 “原生 Redis 命令” 和 “Redisson 封装” 两种。

2.1 问题场景:秒杀库存扣减(10 万 QPS)

需实现 “库存 100 件,多节点并发扣减,无超卖、无漏减”,要求锁响应时间 < 1ms,支持高并发。

2.2 实现 1:原生 Redis 命令(SET NX EX)

2.2.1 原理:利用 Redis 原子命令

Redis 的SET key value NX EX timeout命令是原子操作,满足锁的核心需求:

  • NX(Not Exist):仅当 key 不存在时才设置(保证互斥性);

  • EX(Expire):设置过期时间(避免死锁,保证安全性);

  • 锁 key:如lock:seckill:1001(1001 为商品 ID,区分不同资源);

  • 锁 value:随机 UUID(避免误释放其他线程的锁)。

2.2.2 实战代码(Java + Jedis)
import redis.clients.jedis.Jedis;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class RedisNativeLock
{
// Redis连接信息
private static final String REDIS\_HOST = "192.168.1.100";
private static final int REDIS\_PORT = 6379;
private static final String REDIS\_PASSWORD = "Redis@123456";
// 锁过期时间(5秒,避免死锁)
private static final int LOCK\_EXPIRE = 5;
// 重试间隔(100毫秒,避免频繁重试)
private static final int RETRY\_INTERVAL = 100;
// 生成随机UUID(避免误释放锁)
private String generateLockValue() {
return UUID.randomUUID().toString();
}
// 获取锁(带重试)
public String acquireLock(String lockKey, int maxRetryTimes) {
Jedis jedis = null;
try {
// 1. 建立Redis连接
jedis = new Jedis(REDIS\_HOST, REDIS\_PORT);
jedis.auth(REDIS\_PASSWORD);
// 2. 生成锁value
String lockValue = generateLockValue();
int retryCount = 0;
// 3. 循环重试获取锁
while (retryCount < maxRetryTimes) {
// 核心:执行SET NX EX命令(原子操作)
String result = jedis.set(lockKey, lockValue, "NX", "EX", LOCK\_EXPIRE);
if ("OK".equals(result)) {
System.out.println("获取锁成功,lockKey=" + lockKey + ", lockValue=" + lockValue);
return lockValue;
// 返回value,用于释放锁
}
// 重试间隔
TimeUnit.MILLISECONDS.sleep(RETRY\_INTERVAL);
retryCount++;
System.out.println("获取锁失败,重试次数=" + retryCount);
}
// 重试次数耗尽,获取锁失败
System.out.println("获取锁失败,已达最大重试次数=" + maxRetryTimes);
return null;
} catch (Exception e) {
System.err.println("获取锁异常:" + e.getMessage());
return null;
} finally {
// 关闭Redis连接
if (jedis != null) {
jedis.close();
}
}
}
// 释放锁(需验证value,避免误释放)
public boolean releaseLock(String lockKey, String lockValue) {
Jedis jedis = null;
try {
jedis = new Jedis(REDIS\_HOST, REDIS\_PORT);
jedis.auth(REDIS\_PASSWORD);
// 1. 先查询锁的value(确保是当前线程的锁)
String currentValue = jedis.get(lockKey);
if (lockValue.equals(currentValue)) {
// 2. 释放锁(DEL命令)
jedis.del(lockKey);
System.out.println("释放锁成功,lockKey=" + lockKey + ", lockValue=" + lockValue);
return true;
}
// 锁已被其他线程占用或已过期
System.out.println("释放锁失败:锁value不匹配或已过期");
return false;
} catch (Exception e) {
System.err.println("释放锁异常:" + e.getMessage());
return false;
} finally {
if (jedis != null) {
jedis.close();
}
}
}
// 实战:秒杀库存扣减(用Redis锁控制)
public void seckillStock(String lockKey, int productId) {
// 1. 获取锁(最多重试3次)
String lockValue = acquireLock(lockKey, 3);
if (lockValue == null) {
throw new RuntimeException("秒杀失败:获取锁超时");
}
// 2. 执行业务(扣减库存,此处用Redis模拟库存)
Jedis jedis = null;
try {
jedis = new Jedis(REDIS\_HOST, REDIS\_PORT);
jedis.auth(REDIS\_PASSWORD);
// 查询当前库存
String stockKey = "stock:product:" + productId;
int stock = Integer.parseInt(jedis.get(stockKey));
if (stock <= 0) {
throw new RuntimeException("秒杀失败:库存不足");
}
// 扣减库存(原子操作,避免超卖)
jedis.decr(stockKey);
System.out.println("秒杀成功,剩余库存=" + (stock - 1));
} catch (Exception e) {
throw new RuntimeException("秒杀业务异常:" + e.getMessage());
} finally {
// 3. 释放锁(必须在finally中,确保锁释放)
releaseLock(lockKey, lockValue);
}
}
// 测试:3个线程模拟3个服务节点秒杀
public static void main(String\[] args) {
RedisNativeLock lock = new RedisNativeLock();
String lockKey = "lock:seckill:1001";
// 商品1001的秒杀锁
int productId = 1001;
// 初始化库存(100件)
Jedis jedis = new Jedis(REDIS\_HOST, REDIS\_PORT);
jedis.auth(REDIS\_PASSWORD);
jedis.set("stock:product:" + productId, "100");
jedis.close();
// 启动3个线程(模拟3个服务节点)
for (int i = 0; i <
3; i++) {
new Thread(() ->
{
for (int j = 0; j <
40; j++) {
// 每个线程尝试秒杀40次
try {
lock.seckillStock(lockKey, productId);
} catch (Exception e) {
// 忽略失败(如库存不足、获取锁超时)
}
}
}).start();
}
}
}
2.2.3 原生实现的痛点与解决方案
痛点问题描述解决方案
锁过期导致业务未完成锁过期时间 5 秒,但业务执行需 10 秒,锁被释放后其他线程抢占实现 “看门狗” 机制(定时续期锁过期时间)
非重入性同一线程无法重复获取同一把锁(如递归调用)用 Redis Hash 存储 “线程标识 + 重入次数”
释放锁非原子操作查询 value 和 DEL 命令分两步,可能误释放其他线程的锁用 Lua 脚本实现原子释放(if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end

2.3 实现 2:Redisson 封装(企业级首选)

Redisson 是 Redis 官方推荐的 Java 客户端,已封装分布式锁的 “看门狗”“重入性”“原子释放” 等特性,无需手动实现。

2.3.1 核心特性
  • 重入锁:支持同一线程重复获取锁(RLock.lock());

  • 看门狗续期:默认 30 秒过期,业务未完成时自动续期(每 10 秒续期一次);

  • 公平锁:按请求顺序获取锁(Redisson.createFairLock());

  • 读写锁:支持并发读、独占写(RReadWriteLock),适合读多写少场景。

2.3.2 实战代码(Spring Boot + Redisson)
  1. 依赖配置(pom.xml)
<!-- Redisson依赖 --><dependency><groupId>org.redisson\</groupId><artifactId>redisson-spring-boot-starter\</artifactId><version>3.23.3\</version></dependency>
  1. Redisson 配置(application.yml)
spring:
redis:
host: 192.168.1.100
port: 6379
password: Redis@123456
timeout: 2000ms
redisson:
lock:
watch-dog-timeout: 30000 # 看门狗超时时间(30秒,默认)
threads: 4 # 业务线程池大小
netty-threads: 4 # Netty线程池大小
  1. Redisson 锁实现(秒杀业务)
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedissonSeckillService
{
@Autowired
private RedissonClient redissonClient;
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 秒杀库存扣减(Redisson锁实现)
public void seckill(String productId) {
// 1. 定义锁key(按商品ID区分)
String lockKey = "lock:seckill:" + productId;
// 2. 获取重入锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 3. 获取锁(最多等待3秒,锁自动过期30秒,看门狗自动续期)
boolean isLocked = lock.tryLock(3, 30, TimeUnit.SECONDS);
if (!isLocked) {
throw new RuntimeException("秒杀失败:获取锁超时");
}
// 4. 执行业务(扣减库存)
String stockKey = "stock:product:" + productId;
Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get(stockKey));
if (stock == null || stock <= 0) {
throw new RuntimeException("秒杀失败:库存不足");
}
// 原子扣减库存
stringRedisTemplate.opsForValue().decrement(stockKey);
System.out.println("秒杀成功,商品ID=" + productId + ",剩余库存=" + (stock - 1));
} catch (InterruptedException e) {
throw new RuntimeException("获取锁被中断:" + e.getMessage());
} finally {
// 5. 释放锁(仅当前线程持有锁时才释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("释放锁成功,lockKey=" + lockKey);
}
}
}
// 测试:高并发秒杀(100个线程)
public static void main(String\[] args) {
// 模拟Spring容器初始化(实际项目中由Spring管理)
RedissonSeckillService service = new RedissonSeckillService();
// 初始化库存(100件)
service.stringRedisTemplate.opsForValue().set("stock:product:1001", "100");
// 启动100个线程模拟高并发
for (int i = 0; i <
100; i++) {
new Thread(() ->
{
try {
service.seckill("1001");
} catch (Exception e) {
// 忽略失败
}
}).start();
}
}
}

2.4 故障案例:Redis 锁超时导致超卖

2.4.1 问题背景

某电商用原生 Redis 锁实现秒杀,锁过期时间设为 5 秒。大促期间,因 Redis 响应延迟,业务执行耗时增至 8 秒,锁自动过期后被其他线程抢占,导致同一库存被 2 个线程扣减,出现超卖。

2.4.2 根因分析
  1. 原生 Redis 锁未实现 “看门狗” 续期,锁过期时间固定;

  2. 业务执行时间受 Redis 性能影响,超过锁过期时间后,锁被释放;

  3. 未添加 “库存预扣减 + 最终校验” 机制,无法发现超卖。

2.4.3 解决方案
  1. 改用 Redisson 锁,依赖 “看门狗” 自动续期(业务未完成时,每 10 秒续期一次);

  2. 库存扣减前添加 “版本号校验”(用 Redis 的watch命令,避免并发修改);

  3. 最终库存落地数据库,定时校验 Redis 与数据库库存一致性,修复超卖数据。

2.5 Redis 锁避坑总结

适用场景:高并发(10 万 + QPS)、秒杀、库存扣减等性能优先场景;

不适用场景:对一致性要求极高(如金融支付)、Redis 集群不稳定场景;

⚠️ 必避坑点

  1. 锁 key 需按资源粒度设计(如lock:seckill:商品ID,避免锁粒度太大导致并发低);

  2. 原生实现必须用 Lua 脚本原子释放锁,避免误释放;

  3. 高并发场景优先用 Redisson,避免重复造轮子;

  4. Redis 集群需开启持久化(AOF+RDB),避免宕机后锁数据丢失。

三、方案 2:基于 ZooKeeper 的分布式锁(高可用首选)

ZooKeeper 因 “临时顺序节点” 特性,天然支持分布式锁的高可用与公平性,适合对一致性要求高的场景(如分布式任务调度)。

3.1 问题场景:分布式任务调度(避免重复执行)

某系统需定时执行 “订单对账” 任务,部署 3 个任务节点,要求同一时间仅 1 个节点执行任务,避免重复对账。

3.2 原理:临时顺序节点的 “Watcher” 机制

ZooKeeper 的分布式锁基于 “临时顺序节点 + Watcher 监听” 实现,核心逻辑:

  1. 所有节点在/lock/task路径下创建 “临时顺序节点”(如/lock/task/lock-0000000001);

  2. 每个节点判断自己创建的节点是否为 “最小序号节点”:

  1. 节点宕机后,临时节点自动删除,锁释放(保证安全性)。
3.2.1 架构图:ZooKeeper 分布式锁节点结构

在这里插入图片描述

3.3 实战代码(Java + Curator)

Curator 是 ZooKeeper 官方推荐的 Java 客户端,已封装分布式锁实现(InterProcessMutex),无需手动处理节点监听。

3.3.1 1. 依赖配置(pom.xml)
<!-- Curator依赖 --><dependency><groupId>org.apache.curator\</groupId><artifactId>curator-recipes\</artifactId><version>5.5.0\</version></dependency><dependency><groupId>org.apache.zookeeper\</groupId><artifactId>zookeeper\</artifactId><version>3.8.2\</version><!-- 排除冲突依赖 --><exclusions><exclusion><groupId>org.slf4j\</groupId><artifactId>slf4j-log4j12\</artifactId></exclusion></exclusions></dependency>
3.3.2 2. Curator 锁实现(分布式任务调度)
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class ZkLockTaskService
{
// ZooKeeper集群地址
private static final String ZK\_CONNECT\_STR = "192.168.1.100:2181,192.168.1.101:2181,192.168.1.102:2181";
// 锁节点路径(任务对账锁)
private static final String LOCK\_PATH = "/lock/task/reconciliation";
// 会话超时时间
private static final int SESSION\_TIMEOUT = 5000;
// 连接超时时间
private static final int CONNECTION\_TIMEOUT = 3000;
// 初始化Curator客户端
private CuratorFramework createCuratorClient() {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString(ZK\_CONNECT\_STR)
.sessionTimeoutMs(SESSION\_TIMEOUT)
.connectionTimeoutMs(CONNECTION\_TIMEOUT)
.retryPolicy(new ExponentialBackoffRetry(1000, 3)) // 重试策略:1秒重试,最多3次
.build();
client.start();
// 启动客户端
return client;
}
// 执行对账任务(ZooKeeper锁控制)
public void executeReconciliationTask() {
CuratorFramework client = null;
InterProcessMutex lock = null;
try {
// 1. 创建Curator客户端
client = createCuratorClient();
// 2. 创建分布式锁(重入锁)
lock = new InterProcessMutex(client, LOCK\_PATH);
// 3. 获取锁(最多等待5秒,获取成功后无过期时间,节点宕机自动释放)
boolean isLocked = lock.acquire(5, TimeUnit.SECONDS);
if (!isLocked) {
throw new RuntimeException("获取对账任务锁超时,放弃执行");
}
// 4. 执行业务(对账逻辑)
System.out.println("获取锁成功,开始执行对账任务...");
// 模拟对账耗时(3秒)
TimeUnit.SECONDS.sleep(3);
System.out.println("对账任务执行完成,释放锁");
} catch (Exception e) {
throw new RuntimeException("对账任务执行异常:" + e.getMessage());
} finally {
// 5. 释放锁(必须在finally中)
if (lock != null && lock.isAcquiredInThisProcess()) {
try {
lock.release();
System.out.println("锁释放成功");
} catch (Exception e) {
System.err.println("释放锁异常:" + e.getMessage());
}
}
// 6. 关闭Curator客户端
if (client != null) {
client.close();
}
}
}
// 测试:3个节点模拟分布式任务
public static void main(String\[] args) {
ZkLockTaskService service = new ZkLockTaskService();
// 启动3个线程(模拟3个任务节点)
for (int i = 0; i <
3; i++) {
int nodeId = i + 1;
new Thread(() ->
{
System.out.println("节点" + nodeId + "尝试执行对账任务");
try {
service.executeReconciliationTask();
} catch (Exception e) {
System.out.println("节点" + nodeId + "执行失败:" + e.getMessage());
}
}).start();
}
}
}

3.4 故障案例:ZooKeeper 羊群效应导致性能下降

3.4.1 问题背景

某系统用 ZooKeeper 锁实现分布式任务调度,部署 10 个任务节点。当持有锁的节点释放锁时,所有等待节点同时监听 “最小节点”,导致 ZooKeeper 服务器收到大量监听通知,响应延迟从 100ms 增至 500ms。

3.4.2 根因分析
  1. 错误实现:所有等待节点监听 “最小节点”,而非 “前一个节点”,导致 “羊群效应”(一个节点释放锁,所有节点被通知);

  2. ZooKeeper 的 Watcher 机制是 “一次性” 的,每次通知后需重新注册监听,增加服务器压力;

  3. 未限制等待节点数量,导致大量节点同时竞争锁。

3.4.3 解决方案
  1. 改用 Curator 的InterProcessMutex,其内部已实现 “监听前一个节点”,避免羊群效应;

  2. 任务节点添加 “执行间隔”(如同一任务仅允许 1 个节点执行,其他节点间隔 10 秒后重试);

  3. 优化 ZooKeeper 集群(增加服务器节点、调整会话超时时间),提升并发处理能力。

3.5 ZooKeeper 锁避坑总结

适用场景:高可用、公平性要求高、一致性优先的场景(分布式任务调度、配置同步);

不适用场景:超高并发(1 万 + QPS)、对性能敏感的场景;

⚠️ 必避坑点

  1. 必须用 Curator 客户端,避免手动实现监听导致羊群效应;

  2. 锁节点路径需唯一(如按任务类型区分),避免不同任务竞争同一把锁;

  3. ZooKeeper 集群需部署 3 个以上节点,确保高可用;

  4. 避免频繁创建 / 删除节点(ZooKeeper 性能瓶颈在节点操作)。

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

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

相关文章

Git克隆项目运行指南

运行别人代码一、通用前置步骤:克隆项目 所有项目第一步均为从 Git 拉取代码到本地,操作如下:打开命令行(终端 / CMD),执行克隆命令:git clone <项目仓库地址>(地址从 Git 仓库复制,如 GitHub/GitLab/G…

webpack library - 指南

webpack library - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", &qu…

2025.10.9 月考游寄 - Amy

摆烂没有好下场!忘记比赛有罚时了。摆烂过头加生理期导致完全小丑。 这次月考算是对自己的警示。还是有很多很多东西不会的,要抓紧学。 分解因数 从小到大筛需要的次数更少且用时更短。 罚时+1. from math import sq…

QGIS导出TIF栅格图层

QGIS版本: 3.40+导入栅格数据2.重投影图层注意事项: CRS选择 ESRI: 1021003.矢量栅格化,具体参数配置如图(update:分辨率越高越清晰,建议5000往上):

OpenCV——批量读取可视化图片 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

七层协议

在计算机网络领域,七层协议通常指的是开放式系统互联参考模型(OSI/RM,Open System Interconnection Reference Model),它是由国际标准化组织(ISO)提出的网络体系结构模型,将网络通信的功能划分为七个层次,从下…

20251009

[日期] [星期] [天气:阴雨] 早上硬生生从被窝里扯出来,窗外的天色还暗沉沉的,冷空气顺着窗缝往屋里钻,连续下了好几天雨。 简单洗漱后套上外套出门,雨丝细细密密地飘着,打在伞上。 课间收到通知,因为天气原因,…

各种B站客户端

本文依照适配平台数量为依据排列下列客户端,感谢所有软件作者的开发 由于部分软件作者要求不进行宣传,因此本文会缺少一些知名版本 为力求收集大部分版本,欢迎大家在评论区推荐本文未提及的版本 如果打不开GitHub链…

10.9正式恢复

我请了14天的病假。所以一些每人总结没有完成。 今天上了数据结构的课。学习了双向链表一些知识,然后构思了邮箱管理系统。

CSP-S模拟27

T1:喜剧的迷人之处在于 思路: 显然,因为\(ab\)是完全平方数,所以将\(a\)中的所有完全平方数约了以后的数就是\(b\)的最小值。但是题目还要求\(b\)属于区间\([l,r]\),所以我们可能需要给\(b\)乘上一个或多个完全平…

模型训练技巧 - -一叶知秋

模型训练技巧Model Bias(模型偏差) Bias(偏差) 是机器学习里衡量“模型预测与真实值平均偏离程度”的指标。 它反映模型对目标函数的逼近能力。 Optimization(优化)在一堆可能的方案中,找到“最好”的那个。在机…

20232324 2025-2026-1 《网络与系统攻防技术》实验一实验报告

20232324 2025-2026-1 《网络与系统攻防技术》实验一实验报告1.实验内容 1.1实验目标本次实践的对象是一个名为pwn1的linux可执行文件。 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串…

2025.10.8 训练记录

10.8 上午 早读爽睡 30min,闭眼到机房。 然后发现有人打开了我的浏览器打开了duel点击了加入比赛点击了准备。 就是这场。 嗯。最近大家打 duel 的热情好像很高。那我也打吧。 于是绷不住开始打。 C cf1849C 完美的降…

【触想智能】工业一体机在金融领域的应用优势和具体注意事项 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

汽车行业AI视觉检测方案(三):引领轮胎智检 - 实践

汽车行业AI视觉检测方案(三):引领轮胎智检 - 实践2025-10-09 21:45 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; dis…

WPF mvvm datagrid export as pdf via iTextSharp

Install-Package iTextSharp; public ICommand ExportAsPDFCommand { get; private set; }ExportAsPDFCommand = new DelCommand(async (obj) => await ExportAsPDFCommandExecuted(obj));private async Task Expor…

【每日一面】盒子模型

基础问答 问题:标准的 CSS 盒子模型是怎样的? 答案:标准盒子模型由内容区域(content)、内边距(padding)、边框(border)和外边距(margin)组成。在 content-box 模式下,width 和 height 属性仅指内容区域的宽…

日总结 9

配置环境变量本质是为操作系统和程序提供 “全局可读取的配置信息”,它能让终端无需输入完整路径即可直接调用软件(如java/python命令)、帮助软件定位依赖路径(如JAVA_HOME避免硬编码)、传递系统级参数(如临时文…

kettle插件-国产数据库瀚高插件,助力国产数据库腾飞

场景:国产数据库(瀚高,金仓,达梦,海量等)信创环境下最近发展的势头很猛,今天我们一起来学习下瀚高数据库,从瀚高数据库中读取数据以及使用瀚高数据库作为kettle的资源仓库,废话少数,开干。 1、使用docker安装…