业务背景:抢茅台 ,一个商品id 可能 有 100W库存。这个id 根据redis hash后存储到一台机器上,一台redis 做递减减库存 ,能 抗住 10 W/s吗?
解决方案 :将库存同步到 多个Redis 分片库存算法。
在Lua中编写一个脚本来实现多个key的负载均衡获取,通常这样的场景是在Redis中使用Lua脚本来保证操作的原子性。以下是一个Lua脚本的示例,它实现了你所描述的功能:
- 尝试获取指定的key,并将其值减1。
- 如果该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