乐趣区

关于redis:Redis核心技术3638

36 秒杀

秒杀场景能够分成秒杀前、秒杀中和秒杀后三个阶段。

次要特色:

  1. 刹时并发高(数据库千级并发,Redis 万级并发)
  2. 读多写少,读数据比较简单

秒杀过程:
1. 秒杀前:
尽量把商品详情页的页面元素动态化,而后应用 CDN 或是浏览器把这些动态化的元素缓存起来。
无需应用 Redis。

2. 秒杀中:
这个阶段的操作就是三个:库存查验、库存扣减和订单解决,并发压力在库存查验上。

  • 应用 Redis 保留库存量,这样一来,申请能够间接从 Redis 中读取库存并进行查验。
  • 应用 Redis 进行库存扣减
  • 应用数据库进行订单解决(保障事务)

注:为了防止申请查问到旧的库存值,库存查验和库存扣减这两个操作须要保障原子性(
lua 脚本)。

3. 秒杀完结后:
失败用户刷新商品详情,胜利用户刷新订单详情。
无需应用 Redis。

原子操作

在秒杀场景中,一个商品的库存对应了两个信息,别离是总库存量和已秒杀量。
其中,itemID 是商品的编号,total 是总库存量,ordered 是已秒杀量。

key: itemID
value: {total: N, ordered: M}

办法 1:lua 脚本

# 获取商品库存信息            
local counts = redis.call("HMGET", KEYS[1], "total", "ordered");
#将总库存转换为数值
local total = tonumber(counts[1])
#将已被秒杀的库存转换为数值
local ordered = tonumber(counts[2])  
#如果以后申请的库存量加上已被秒杀的库存量依然小于总库存量,就能够更新库存         
if ordered + k <= total then
    #更新已秒杀的库存量
    redis.call("HINCRBY",KEYS[1],"ordered",k)                              return k;  
end               
return 0

办法 2:分布式锁
先让客户端向 Redis 申请分布式锁,只有拿到锁的客户端能力执行库存查验和库存扣减。

// 应用商品 ID 作为 key
key = itemID
// 应用客户端惟一标识作为 value
val = clientUniqueID
// 申请分布式锁,Timeout 是超时工夫
lock =acquireLock(key, val, Timeout)
// 当拿到锁后,能力进行库存查验和扣减
if(lock == True) {
   // 库存查验和扣减
   availStock = DECR(key, k)
   // 库存曾经扣减完了,开释锁,返回秒杀失败
   if (availStock < 0) {releaseLock(key, val)
      return error
   }
   // 库存扣减胜利,开释锁
   else{releaseLock(key, val)
     // 订单解决
   }
}
// 没有拿到锁,间接返回
else
   return

留神:应用分布式锁时,客户端须要先向 Redis 申请锁,只有申请到了锁,能力进行库存查验等操作,这样一来,客户端在争抢分布式锁时,大部分秒杀申请自身就会因为抢不到锁而被拦挡。

倡议:分布式锁和业务数据放在集群不同实例上,能够加重业务数据实例压力。

小结

秒杀优化:

  1. 前端动态化:利用 CDN 和浏览器缓存。
  2. 申请拦挡和控流:拦挡歹意申请(黑名单),限度申请数量。
  3. 库存过期工夫解决:不要设置过期工夫,防止缓存击穿。
  4. 数据库订单异样解决:减少订单重试性能,保障订单胜利解决。

倡议:解决秒杀的业务数据用独自的实例保留,不要和日常业务放在一起。

问题:应用多个实例的切片集群来分担秒杀申请,是否是一个好办法?
长处:集群分担申请,能够升高单实例压力;
毛病:

  1. 申请不均匀时会数据歪斜,增大单个实例压力,导致没有全副卖出;
  2. 获取库存时须要查问多个切片,这种状况倡议不展现库存。

37 数据歪斜

数据歪斜有两类:

  • 数据量歪斜:在某些状况下,实例上的数据分布不平衡,某个实例上的数据特地多。
  • 数据拜访歪斜:每个实例的数据量相差不大,然而某个实例上的数据是热点数据,被拜访得十分频繁。

产生起因

数据歪斜的起因别离是某个实例上保留了 bigkey、Slot 调配不平衡以及 Hash Tag。

bigkey

  1. bigkey 的 value 值很大(String 类型),或者是 bigkey 保留了大量汇合元素(汇合类型),会导致这个实例的数据量减少,内存资源耗费也相应减少。
  2. bigkey 的操作个别都会造成实例 IO 线程阻塞,影响访问速度。

解决办法:

  1. 防止把过多数据放在一个 key 中
  2. 把汇合类型的 bigkey 拆分成很多小汇合,保留在不同实例上。

Slot 调配不平衡

大量的数据被调配到同一个 Slot 中,而同一个 Slot 只会在一个实例上散布,这就会导致,大量数据被集中到一个实例上,造成数据歪斜。

查看 Slot 分配情况:

CLUSTER SLOTS

Slot 迁徙:

  1. CLUSTER SETSLOT:应用不同的选项进行三种设置,别离是设置 Slot 要迁入的指标实例,Slot 要迁出的源实例,以及 Slot 所属的实例。
  2. CLUSTER GETKEYSINSLOT:获取某个 Slot 中肯定数量的 key。
  3. MIGRATE:把一个 key 从源实例理论迁徙到指标实例。

    #1. 从实例 3 上迁入 Slot 300
    CLUSTER SETSLOT 300 IMPORTING 3
    
    # 2. 迁到实例 5
    CLUSTER SETSLOT 300 MIGRATING 5
    
    # 3. 分批次迁徙,一次 100 个 key
    CLUSTER GETKEYSINSLOT 300 100
    
    # 4. 执行迁徙,设置数据库编号(0)和迁徙超时工夫
    MIGRATE 192.168.10.5 6379 key1 0 timeout
    
    # 反复 3,4 步直到所有 key 迁徙实现

Hash Tag

Hash Tag 是指加在键值对 key 中的一对花括号 {}。
这对括号会把 key 的一部分括起来,客户端在计算 key 的 CRC16 值时,只对 Hash Tag 花括号中的 key 内容进行计算。
如果没用 Hash Tag 的话,客户端计算整个 key 的 CRC16 的值。
比方 key 为 user:profile:{3231}时,只计算 3231 的 CRC16 值。

益处:HashTag 雷同时,数据会映射到同一个实例上。
利用场景:
次要是用在 Redis Cluster 和 Codis 中,反对事务操作和范畴查问。因为 Redis Cluster 和 Codis 自身并不反对跨实例的事务操作和范畴查问,当业务利用有这些需要时,就只能先把这些数据读取到业务层进行事务处理,或者是一一查问每个实例,失去范畴查问的后果。所以应用 Hash Tag 把要执行事务操作或是范畴查问的数据映射到同一个实例上,这样就能很轻松地实现事务或范畴查问了。

应答办法

  1. 如果热点数据以服务读操作为主,在这种状况下,咱们能够采纳热点数据多正本的办法来应答。
    具体做法是,把热点数据复制多份,在每一个数据正本的 key 中减少一个随机前缀,让它和其它正本数据不会被映射到同一个 Slot 中。
  2. 对于有读有写的热点数据,咱们就要给实例自身减少资源了,例如应用配置更高的机器,来应答大量的拜访压力。

小结

倡议:在构建切片集群时,尽量应用大小配置雷同的实例(例如实例内存配置放弃雷同),这样能够防止因实例资源不平衡而在不同实例上调配不同数量的 Slot。

38 通信开销

Redis Cluster 的规模下限,是一个集群运行 1000 个实例。
实例间的通信开销会随着实例规模减少而增大,在集群超过肯定规模时(比方 800 节点),集群吞吐量反而会降落。

Gossip 协定

Redis Cluster 在运行时,每个实例上都会保留 Slot 和实例的对应关系(也就是 Slot 映射表),以及本身的状态信息。

为了让集群中的每个实例都晓得其它所有实例的状态信息,实例之间会依照肯定的规定进行通信。这个规定就是 Gossip 协定。

  1. 每个实例之间会依照肯定的频率,从集群中随机筛选一些实例,把 PING 音讯发送给筛选进去的实例,用来检测这些实例是否在线,并替换彼此的状态信息。PING 音讯中封装了发送音讯的实例本身的状态信息、局部其它实例的状态信息,以及 Slot 映射表。
  2. 实例在接管到 PING 音讯后,会给发送 PING 音讯的实例,发送一个 PONG 音讯。PONG 音讯蕴含的内容和 PING 音讯一样。

Gossip 协定能够保障在一段时间后,集群中的每一个实例都能取得其它所有实例的状态信息。

应用 Gossip 协定进行通信时,通信开销受到通信音讯大小和通信频率这两方面的影响,音讯越大、频率越高,相应的通信开销也就越大。

音讯大小

1. 对于一个蕴含了 1000 个实例的集群来说,每个实例发送一个 PING 音讯时,会蕴含 100 个实例的状态信息,总的数据量是 10400 字节,再加上发送实例本身的信息,一个 Gossip 音讯大概是 10KB。
2. 为了让 Slot 映射表可能在不同实例间流传,PING 音讯中还带有一个长度为 16,384 bit 的 Bitmap,这个 Bitmap 的每一位对应了一个 Slot,如果某一位为 1,就示意这个 Slot 属于以后实例。这个 Bitmap 大小换算成字节后,是 2KB。

把实例状态信息和 Slot 调配信息相加,就能够失去一个 PING 音讯的大小了,大概是 12KB。每个实例发送了 PING 音讯后,还会收到返回的 PONG 音讯,两个音讯加起来有 24KB。

通信频率

  1. Redis Cluster 的实例启动后,默认会每秒从本地的实例列表中随机选出 5 个实例,再从这 5 个实例中找出一个最久没有通信的实例,把 PING 音讯发送给该实例。
  2. Redis Cluster 的实例会依照每 100ms 一次的频率,扫描本地的实例列表,如果发现有实例最近一次接管 PONG 音讯的工夫,曾经大于配置项 cluster-node-timeout 的一半了(cluster-node-timeout/2),就会立即给该实例发送 PING 音讯,更新这个实例上的集群状态信息。

优化:
配置项 cluster-node-timeout 定义了集群实例被判断为故障的心跳超时工夫,默认是 15 秒,能够调大到 20~25 秒。

能够在调整 cluster-node-timeout 值的前后,应用 tcpdump 命令抓取实例发送心跳信息网络包的状况。

tcpdump host 192.168.10.3 port 16379 -i 网卡名 -w /tmp/r1.cap
退出移动版