JAVA 秒杀10W/s高并发 热点商品 Redis 减库存方案

业务背景:抢茅台 ,一个商品id 可能 有 100W库存。这个id 根据redis hash后存储到一台机器上,一台redis 做递减减库存 ,能 抗住 10 W/s吗?

解决方案 :将库存同步到 多个Redis 分片库存算法。

在Lua中编写一个脚本来实现多个key的负载均衡获取,通常这样的场景是在Redis中使用Lua脚本来保证操作的原子性。以下是一个Lua脚本的示例,它实现了你所描述的功能:

  1. 尝试获取指定的key,并将其值减1。
  2. 如果该key不存在或库存为0,则查找所有key中库存最多的一个,并将其值减1。

假设我们的key前缀是stock:,并且我们已知要查询的key列表。请注意,这个示例可能需要根据你的实际使用场景进行调整。

 

lua复制代码

-- 已知的key列表
local keys = {'stock:item1', 'stock:item2', 'stock:item3'}
-- 要查询的key
local targetKey = KEYS[1]
-- 尝试获取并减少目标key的库存
local targetStock = redis.call('GET', targetKey)
if targetStock and tonumber(targetStock) > 0 then
redis.call('DECR', targetKey)
return targetKey
end
-- 如果目标key不存在或库存为0,查找库存最多的key
local maxStock = 0
local maxStockKey = nil
for _, key in ipairs(keys) do
local stock = redis.call('GET', key)
if stock and tonumber(stock) > maxStock then
maxStock = tonumber(stock)
maxStockKey = key
end
end
-- 减少找到的最大库存key的值
if maxStockKey then
redis.call('DECR', maxStockKey)
return maxStockKey
end
-- 如果没有找到任何key,返回nil
return nil

要使用这个脚本,你需要通过EVAL命令将其传递给Redis,并提供要查询的key作为参数。例如:

 

sh复制代码

redis-cli EVAL "$(cat script.lua)" 1 stock:item1

这里,script.lua是包含上述Lua脚本的文件,1是传递给脚本的key数量(在这个例子中,我们只传递了一个key),stock:item1是我们要查询的key。

请注意,这个脚本假设所有的key都存储了整数值作为库存。如果你的库存是以其他方式存储的,你可能需要修改脚本来适应你的数据结构。

此外,这个脚本在处理大量key时可能不是最高效的,因为它需要遍历整个key列表来查找库存最多的key。如果你的应用有大量的key,你可能需要考虑使用其他数据结构或方法来优化这个过程。

------------------------------------------------------------------------------------------------------------ 

要实现一个能够尽量将 Redis key 打散到不同槽的工具类,并自动生成后缀,我们可以考虑使用一个更复杂的策略,例如结合基础 key、当前时间戳、线程 ID 等信息来生成后缀,以确保每次生成的 key 尽可能不同。但请注意,即使这样,我们也不能保证生成的 key 一定会映射到不同的槽上,因为 CRC16 哈希的结果仍然依赖于 key 的具体字符序列。

以下是一个改进后的示例,它使用当前时间戳和随机字符来生成后缀:

 

java复制代码

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
public class RedisKeyShardingUtil {
// Redis 集群的槽数量
private static final int TOTAL_SLOTS = 16384;
private static final DateTimeFormatter TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
/**
* 使用自动生成的后缀生成新的 key,并尝试确保它们映射到不同的槽上。
* 注意:这只是一个示例,并不能保证生成的 key 一定映射到不同的槽上。
*
* @param baseKey 基础 key
* @param numKeys 要生成的 key 的数量
* @return 打散后的 key 列表
*/
public static List<String> shardKeysWithAutoSuffix(String baseKey, int numKeys) {
List<String> shardedKeys = new ArrayList<>();
for (int i = 0; i < numKeys; i++) {
// 使用当前时间戳和随机字符生成后缀
String suffix = generateAutoSuffix();
String shardedKey = baseKey + ":" + suffix;
shardedKeys.add(shardedKey);
}
// 这里没有实现检查生成的 key 是否映射到不同槽的逻辑,因为这需要实际的 Redis 环境来执行 CRC16 哈希。
// 在实际应用中,你可能需要连接到 Redis 集群并检查每个 key 的槽分配。
return shardedKeys;
}
/**
* 生成自动后缀,包含当前时间戳和随机字符。
*
* @return 自动生成的后缀字符串
*/
private static String generateAutoSuffix() {
// 使用当前时间戳作为后缀的一部分,增加 key 的多样性
String timestamp = TIMESTAMP_FORMATTER.format(Instant.now());
// 添加随机字符进一步增加 key 的多样性
String randomChars = generateRandomChars(5);
return timestamp + randomChars;
}
/**
* 生成指定长度的随机字符。
*
* @param length 随机字符的长度
* @return 随机字符字符串
*/
private static String generateRandomChars(int length) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append((char) ThreadLocalRandom.current().nextInt(97, 123)); // 生成小写字母
}
return sb.toString();
}
public static void main(String[] args) {
String baseKey = "skuid";
int numKeys = 3; // 要生成的 key 的数量
List<String> shardedKeys = shardKeysWithAutoSuffix(baseKey, numKeys);
shardedKeys.forEach(System.out::println);
}
}

在这个改进后的版本中,shardKeysWithAutoSuffix 方法接受一个基础 key 和要生成的 key 的数量作为参数,然后自动为每个 key 生成一个后缀。后缀由当前时间戳和随机字符组成,以增加 key 的多样性。这样,每次调用该方法时,由于时间戳的不同,生成的 key 也会有所不同。

然而,请再次注意,即使使用这种方法,我们也不能保证生成的 key 一定会映射到不同的槽上。在 Redis 集群中,key 到槽的映射是由 CRC16 哈希算法决定的,而且哈希碰撞是可能发生的。如果你需要确保 key 映射到不同的槽上,你可能需要实现更复杂的逻辑,例如手动计算槽的哈希值并进行比较,但这通常是不必要的,并且可能会降低性能。在大多数情况下,让 Redis 集群自己管理 key 的分布就足够了。

 

还有很多就不一一介绍了。下面是整理的一些面试题大纲,需要的来领取。

链接:https://pan.baidu.com/s/1Y_3W5AInjtV6DImHMC7wtw 
提取码:r44b

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从零开始学习人工智能

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值