2025/12/26 16:26:07
网站建设
项目流程
兰州网站建设哪家专业,利用影视网站做cpa,安卓app安装,网络域名解析错误总结#xff1a;
热点key拆分是什么#xff1f;
简单理解
key拆分 把1个key的数据#xff0c;拆分存储到多个key中类比#xff1a;
就像超市只有1个收银台#xff0c;100人排队#xff08;热点key#xff09;多副本的解决方式#xff1a;
→ 开10个收银台#xff0c;…总结热点key拆分是什么简单理解key拆分 把1个key的数据拆分存储到多个key中 类比 就像超市只有1个收银台100人排队热点key 多副本的解决方式 → 开10个收银台每个都能收款复制数据 key拆分的解决方式 → 把100人分成10组每组去不同收银台 每个收银台负责自己那组人的账单 数据本身就是分散的核心区别读多副本 vs key拆分区别读多副本是幂等的数据不会被改变但是key拆分适用于写场景最后合并统计。最终目的是一样的都是为了减少对一个key的访问压力。对比图示━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【多副本】- 数据完全相同随机读取 原始key stock:123 100 (1个key10万QPS访问) 拆分后 stock:123:copy0 100 ← 33%流量 stock:123:copy1 100 ← 33%流量 stock:123:copy2 100 ← 34%流量 特点数据相同只是复制了多份 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【key拆分】- 数据不同需要汇总 原始key counter:product_view 10000000 (1个key10万次INCR) 拆分后 counter:product_view:0 1000000 ← shard 0 counter:product_view:1 1050000 ← shard 1 counter:product_view:2 980000 ← shard 2 ... counter:product_view:9 1020000 ← shard 9 总计 sum(所有分片) 10000000 特点数据分散需要汇总典型场景计数器问题场景# 场景统计商品浏览量# 问题1个key被10万QPS的INCR操作打爆# 原始方案有瓶颈INCR product:123:view_count# 10万用户同时访问# 所有INCR操作都打到同1个key# 成为性能瓶颈解决方案拆分为10个分片统计总浏览量importrandomimporthashlibclassShardedCounter:分片计数器def__init__(self,redis_client,shard_count10):self.redisredis_client self.shard_countshard_count# 分片数量defincr(self,counter_name,user_idNone): 递增计数器 user_id: 用户ID用于确定分片 # 根据用户ID计算分片编号ifuser_id:# 方式1: 用户维度分片推荐# 同一用户总是访问同一分片shard_idself._get_shard_by_user(user_id)else:# 方式2: 随机分片shard_idrandom.randint(0,self.shard_count-1)# 构建分片keyshard_keyf{counter_name}:shard:{shard_id}# 只递增对应的分片countself.redis.incr(shard_key)print(f用户{user_id}访问 → 分片{shard_id}→{shard_key})returncountdefget_total(self,counter_name): 获取总计数汇总所有分片 total0# 读取所有分片并求和foriinrange(self.shard_count):shard_keyf{counter_name}:shard:{i}countself.redis.get(shard_key)ifcount:totalint(count)returntotaldef_get_shard_by_user(self,user_id):根据用户ID计算分片# 使用哈希确保同一用户总是映射到同一分片hash_valueint(hashlib.md5(str(user_id).encode()).hexdigest(),16)returnhash_value%self.shard_count# # 使用示例# counterShardedCounter(redis_client,shard_count10)# 模拟10万用户浏览商品print(模拟10万用户浏览商品...)foruser_idinrange(100000):counter.incr(product:123:view_count,user_id)# 输出示例# 用户0访问 → 分片6 → product:123:view_count:shard:6# 用户1访问 → 分片3 → product:123:view_count:shard:3# 用户2访问 → 分片8 → product:123:view_count:shard:8# ...# 查看各分片情况print(\n各分片计数)foriinrange(10):shard_keyfproduct:123:view_count:shard:{i}countredis_client.get(shard_key)print(f分片{i}:{count}次)# 输出# 分片0: 10023次# 分片1: 9987次# 分片2: 10105次# 分片3: 9876次# ...# 获取总浏览量total_viewscounter.get_total(product:123:view_count)print(f\n总浏览量:{total_views})# 输出总浏览量: 100000# # 效果分析# # 原方案1个key承受10万QPS# 新方案10个key每个承受1万QPS# 压力分散10倍可视化说明流程图【原始方案 - 单key热点】 10万用户 ↓ ↓ (所有INCR打到1个key) ↓ ┌─────────────────────┐ │ counter:total X │ ← 10万QPS性能瓶颈 └─────────────────────┘ 【拆分方案 - 分片】 10万用户 ↓ ├─→ 用户0 → hash → 分片6 ├─→ 用户1 → hash → 分片3 ├─→ 用户2 → hash → 分片8 ├─→ 用户3 → hash → 分片1 └─→ ... ┌──────────────────────────────────────┐ │ counter:shard:0 10023 (1万QPS) │ │ counter:shard:1 9987 (1万QPS) │ │ counter:shard:2 10105 (1万QPS) │ │ counter:shard:3 9876 (1万QPS) │ │ ... │ │ counter:shard:9 10002 (1万QPS) │ └──────────────────────────────────────┘ ↓ [需要时汇总] ↓ Total 100000实战案例秒杀库存场景库存扣减热点库存总量拆分为多个key持有均匀分布的库存量然后让用户的id和单个分片产生映射关系如果这个分片对应的key-value消耗完了就访问别的分片的value。真的是非常的聪明啊 但是仍然是采用空间换取时间性能减轻单个key的访问压力。classShardedStock:分片库存管理def__init__(self,redis_client,shard_count10):self.redisredis_client self.shard_countshard_countdefinit_stock(self,product_id,total_stock): 初始化库存均匀分配到各分片 stock_per_shardtotal_stock//self.shard_count remaindertotal_stock%self.shard_countforiinrange(self.shard_count):# 余数分配给前几个分片stockstock_per_shard(1ifiremainderelse0)keyfstock:{product_id}:shard:{i}self.redis.set(key,stock)print(f分片{i}初始化库存:{stock})defdeduct_stock(self,product_id,user_id): 扣减库存从用户对应的分片扣除 # 用户固定访问某个分片避免来回切换shard_idself._get_user_shard(user_id)stock_keyfstock:{product_id}:shard:{shard_id}# Lua脚本保证原子性lua_script local stock redis.call(GET, KEYS[1]) if tonumber(stock) 0 then redis.call(DECR, KEYS[1]) return 1 else return 0 end resultself.redis.eval(lua_script,1,stock_key)ifresult1:returnTrueelse:# 当前分片库存不足尝试其他分片returnself._try_other_shards(product_id,shard_id)def_try_other_shards(self,product_id,exclude_shard):尝试从其他分片扣减lua_script local stock redis.call(GET, KEYS[1]) if tonumber(stock) 0 then redis.call(DECR, KEYS[1]) return 1 else return 0 end # 遍历其他分片foriinrange(self.shard_count):ifiexclude_shard:continuestock_keyfstock:{product_id}:shard:{i}resultself.redis.eval(lua_script,1,stock_key)ifresult1:returnTruereturnFalse# 所有分片都没库存defget_total_stock(self,product_id):获取总库存total0foriinrange(self.shard_count):keyfstock:{product_id}:shard:{i}stockself.redis.get(key)ifstock:totalint(stock)returntotaldef_get_user_shard(self,user_id):用户ID映射到分片returnint(hashlib.md5(str(user_id).encode()).hexdigest(),16)%self.shard_count# # 使用示例秒杀100件商品# stock_mgrShardedStock(redis_client,shard_count10)# 初始化库存stock_mgr.init_stock(product_id999,total_stock100)# 输出# 分片0初始化库存: 10# 分片1初始化库存: 10# 分片2初始化库存: 10# ...# 分片9初始化库存: 10# 10万用户抢购success_count0foruser_idinrange(100000):ifstock_mgr.deduct_stock(999,user_id):success_count1print(f用户{user_id}抢购成功)ifsuccess_count100:breakprint(f\n总共{success_count}人抢购成功)print(f剩余库存:{stock_mgr.get_total_stock(999)})# 效果# - 原方案1个库存key被10万DECR打爆# - 新方案10个分片每个承受1万QPS# - 压力分散10倍分片策略对比策略1随机分片如果是固定的映射关系 就是一个用户有固定的分片去对应 那么系统本地是有缓存的 不需要重新计算分片的数字# 完全随机选择分片shard_idrandom.randint(0,shard_count-1)优点 ✅ 实现简单 ✅ 负载均衡好 缺点 ⚠️ 同一用户可能访问不同分片 ⚠️ 缓存亲和性差策略2用户哈希分片推荐意思是 万一 哈希倾斜了 有些分片对应的用户数量还是很多# 根据用户ID哈希shard_idhash(user_id)%shard_count 优点 ✅ 同一用户总是访问同一分片 ✅ 缓存亲和性好 ✅ 便于追踪 缺点 ⚠️ 如果用户分布不均可能负载不均策略3地域分片# 根据地域region_map{北京:0,上海:1,广州:2,深圳:3,# ...}shard_idregion_map.get(user_region,0)优点 ✅ 便于地域统计 ✅ 可以针对地域优化 缺点 ⚠️ 负载可能严重不均大城市访问量高适用场景对比读多副本 vs key拆分其实我感觉就是读多副本和写多副本的区别。。。。场景多副本key拆分计数器❌ 不适合多个副本值不同✅ 适合分片累加库存扣减❌ 不适合会超卖✅ 适合分片扣减商品详情✅ 适合内容相同❌ 不适合没必要拆分配置数据✅ 适合只读数据❌ 不适合整体数据点赞数❌ 不适合需要准确计数✅ 适合累加统计用户信息✅ 适合读多写少❌ 不适合单个对象完整对比总结━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【多副本】 原理同一份数据复制N份 读取随机选择一个副本 写入更新所有副本 示例 product:123:copy0 iPhone数据 product:123:copy1 iPhone数据 (内容相同) product:123:copy2 iPhone数据 适用只读或写少读多的完整数据 场景商品详情、文章内容、配置信息 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【key拆分】 原理逻辑上1个值物理上拆成N份 读取需要汇总所有分片 写入只写对应分片 示例 counter:view:shard:0 1000 counter:view:shard:1 1050 (内容不同) counter:view:shard:2 980 总计 1000 1050 980 3030 适用需要聚合计算的数据 场景计数器、库存、统计数据代码示例对比# # 场景1: 商品浏览 - 应该用多副本# classProductCache:商品缓存 - 使用多副本defget_product(self,product_id):# 随机选择副本内容相同copy_idrandom.randint(0,2)keyfproduct:{product_id}:copy{copy_id}returnredis.get(key)defupdate_product(self,product_id,data):# 更新所有副本foriinrange(3):keyfproduct:{product_id}:copy{i}redis.set(key,data)# # 场景2: 浏览计数 - 应该用key拆分# classViewCounter:浏览计数器 - 使用key拆分defincr_view(self,product_id,user_id):# 根据用户选择分片数据分散shard_idhash(user_id)%10keyfview:{product_id}:shard:{shard_id}returnredis.incr(key)defget_total_views(self,product_id):# 汇总所有分片total0foriinrange(10):keyfview:{product_id}:shard:{i}countredis.get(key)or0totalint(count)returntotal总结我个人认为多副本适合读 key拆分适合统计快速判断问题我的热点key应该用多副本还是拆分 判断流程 1. 这是一个完整的数据对象吗 如商品信息、文章内容、用户资料 ✅ 是 → 用多副本 ❌ 否 → 继续判断 2. 这是需要累加/聚合的数据吗 如计数器、统计值、库存 ✅ 是 → 用key拆分 ❌ 否 → 考虑其他方案 3. 主要操作是什么 读多写少 → 多副本 频繁递增/递减 → key拆分本质区别多副本Multi-Copy: ┌───────┐ │ 数据A │ → copy0 │ 数据A │ → copy1 (复制) │ 数据A │ → copy2 └───────┘ 特点内容相同 key拆分Sharding: ┌───────┐ │ 数据1 │ → shard0 │ 数据2 │ → shard1 (拆分) │ 数据3 │ → shard2 └───────┘ 特点内容不同需要汇总希望这个详细的解释让您理解了热点key拆分的概念简单说就是把一个逻辑上的值如总计数拆分成多个物理key存储用时再汇总。