专栏名称: 鸭哥聊Java
回复关键字:666 ,领取免费简历模板,Java面试题,Java编程视频等。本号内容涵盖Java源码,JVM源码,Dubbo源码,Spring源码,Spring Cloud微服务架构,分布式高并发架构技术,MySQL性能调优等。
目录
相关文章推荐
南方人物周刊  ·  向上而生|粉笔的十年 ·  20 小时前  
高分子科技  ·  广西大学林宝凤教授团队 CEJ: ... ·  4 天前  
高分子科学前沿  ·  重磅发布:武书连2025中国大学排名! ·  2 天前  
人物  ·  不规划也没关系 ·  3 天前  
51好读  ›  专栏  ›  鸭哥聊Java

外包做的又慢又差,但又不好意思把他换掉,咋整?

鸭哥聊Java  · 公众号  ·  · 2025-02-18 11:25

正文

最近刷帖子,看到一个挺有意思的话题:外包做的又慢又差,但又不好意思把人家换掉,咋整?

有网友直接支招:“罚他转正,加工资,挂KPI就行了。”虽然是玩笑,但想让外包跟正编一样干活,却不愿给正编的待遇,这合理吗?

而且用外包,本质是图省钱,但预算和质量往往是一对天敌。既想节约成本,又想效率和质量顶尖。

如果需求真的紧急复杂,或许还是找正编更靠谱,毕竟预算决定了资源,而资源决定了结果。

我觉得吧,与其对外包抱太高期待,不如合理分工,挑那些流程化、风险低的活儿交给他们,重要任务还是要留给团队核心来做。毕竟资源有限时,优先级就是你的王牌 【备注:文末可领最新资料】

今日面试题


好了,我们回归正题, 今天咱们聊聊 Redis 的原子性问题,以及在没有 Lua 脚本的情况下,还有哪些办法可以保证 Redis 的原子性操作。

我觉得 Redis 的原子性是一个非常重要的话题,尤其是我们在设计高并发场景时,往往会依赖 Redis 来做分布式锁、库存扣减等操作。那么,除了 Lua 脚本,还有没有其他办法可以做到类似的效果呢?

Redis 的事务原子性

在 Redis 中,通过 MULTI EXEC 关键字可以实现事务操作。 MULTI 开启事务,后续的所有命令会被放入一个队列中,直到调用 EXEC 时,Redis 才会一次性执行所有的命令。这种机制可以保证命令的顺序执行,但不能保证全局的原子性。

比如,事务中的一个命令出错,Redis 并不会回滚事务,这就意味着事务中某些命令可能已经生效,而另一些命令因为错误未能执行。这是 Redis 事务设计中的一个限制,也是开发者需要注意的地方。

让我们用一个代码示例来说明这个问题:

// 假设我们在 Redis 中有以下键值
// a:stock 是一个 String 类型值
// b:stock 是一个 Integer 类型值

// Redis CLI 命令
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> LPOP a:stock       // 错误操作,a:stock 是 String 类型,LPOP 无法操作
QUEUED
127.0.0.1:6379> DECR b:stock       // 减少库存
QUEUED
127.0.0.1:6379> EXEC               // 执行事务
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 8

从上面的例子可以看出,当 LPOP 发生错误时, DECR 仍然被执行了。这说明 Redis 的事务并不支持自动回滚。

使用 Lua 脚本实现原子性

Lua 脚本是 Redis 中非常强大的工具,它的核心特点是能够保证脚本中的所有操作是原子性的。因为 Redis 在执行 Lua 脚本时是单线程执行的,所以整个脚本不会被其他命令打断,这就确保了原子性。

以下是一个使用 Lua 脚本扣减库存的例子:

String luaScript = "if (redis.call('GET', KEYS[1]) >= tonumber(ARGV[1])) then " +
                   "   return redis.call('DECRBY', KEYS[1], ARGV[1]) " +
                   "else " +
                   "   return -1 " +
                   "end";

Jedis jedis = new Jedis("localhost"6379);
Object result = jedis.eval(luaScript, Arrays.asList("item:stock"), Arrays.asList("1"));

if (Integer.parseInt(result.toString()) >= 0) {
    System.out.println("库存扣减成功,剩余库存:" + result);
else {
    System.out.println("库存不足,扣减失败");
}

Lua 脚本的执行保证了两个操作(判断库存是否足够和扣减库存)在同一事务中完成,确保了原子性。

除了 Lua 脚本,还有其他方法吗?

当然有!以下是几种常用的方式:

1、 使用 Redis 的原子命令
Redis 提供了一些原子性命令,比如 INCR , DECR , SETNX 等,这些命令本身在执行时是原子的。对于简单的场景,可以直接使用这些命令来避免使用事务或 Lua 脚本。

示例:

Jedis jedis = new Jedis("localhost"6379);
long stock = jedis.decrBy("item:stock"1);
if (stock >= 0) {
    System.out.println("库存扣减成功,剩余库存:" + stock);
else {
    System.out.println("库存不足,扣减失败");
}

注意,这种方式适用于逻辑比较简单的场景,比如单纯的自增、自减等。

2、 分布式锁

在复杂场景中,可以借助 Redis 的分布式锁来保证操作的原子性。通过获取锁来确保同一时刻只有一个线程能够操作某些关键资源。

示例:

String lockKey = "lock:item:stock";
String lockValue = UUID.randomUUID().toString();
try (Jedis jedis = new Jedis("localhost"6379)) {
    // 获取锁,过期时间为 10 秒
    if ("OK".equals(jedis.set(lockKey, lockValue, "NX""EX"10))) {
        // 执行操作
        int stock = Integer.parseInt(jedis.get("item:stock"));
        if (stock > 0) {
            jedis.decr("item:stock");
            System.out.println("库存扣减成功");
        } else {
            System.out.println("库存不足");
        }
    } else {
        System.out.println("获取锁失败,操作被其他线程占用");
    }
finally {
    // 释放锁
    if (lockValue.equals(jedis.get(lockKey))) {
        jedis.del(lockKey);
    }
}

分布式锁的核心是确保锁的获取和释放逻辑是可靠的,比如通过 SET NX EX 来保证锁的自动过期。

而最优解通常是 Lua 脚本 ,因为它既能保证复杂操作的原子性,又不需要引入额外的锁机制,是 Redis 官方推荐的最佳实践。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费: https://www.songshuhezi.com/deepseek


同时,也可以看我写的这篇文章《 DeepSeek满血复活,直接起飞! 》来进行本地搭建。







请到「今天看啥」查看全文