[TOC]redis
redis 集群部署方式大部分采用类 Twemproxy 的方式进行部署。即经过 Twemproxy 对 redis key 进行分片计算,将 redis key 进行分片计算,分配到多个 redis 实例中的其中一个。tewmproxy 架构图以下:算法
因为 Twemproxy 背后的多个 redis 实例在内存配置和 cpu 配置上都是一致的,因此一旦出现访问量倾斜或者数据量倾斜,则可能会致使某个 redis 实例达到性能瓶颈,从而使整个集群达到性能瓶颈。json
Hot key,即热点 key,指的是在一段时间内,该 key 的访问量远远高于其余的 redis key, 致使大部分的访问流量在通过 proxy 分片以后,都集中访问到某一个 redis 实例上。hot key 一般在不一样业务中,存储着不一样的热点信息。好比缓存
……架构
在 client 端使用本地缓存,从而下降了redis集群对hot key的访问量,可是同时带来两个问题:一、若是对可能成为 hot key 的 key 都进行本地缓存,那么本地缓存是否会过大,从而影响应用程序自己所需的缓存开销。二、如何保证本地缓存和redis集群数据的有效期的一致性。
针对这两个问题,先不展开讲,先将第二个解决方案。并发
咱们知道 hot key 之因此是 hot key,是由于它只有一个key,落地到一个实例上。因此咱们能够给hot key加上前缀或者后缀,把一个hotkey 的数量变成 redis 实例个数N的倍数M,从而由访问一个 redis key 变成访问 N * M 个redis key。
N*M 个 redis key 通过分片分布到不一样的实例上,将访问量均摊到全部实例。运维
代码以下:tcp
//redis 实例数 const M = 16 //redis 实例数倍数(按需设计,2^n倍,n通常为1到4的整数) const N = 2 func main() { //获取 redis 实例 c, err := redis.Dial("tcp", "127.0.0.1:6379") if err != nil { fmt.Println("Connect to redis error", err) return } defer c.Close() hotKey := "hotKey:abc" //随机数 randNum := GenerateRangeNum(1, N*M) //获得对 hot key 进行打散的 key tmpHotKey := hotKey + "_" + strconv.Itoa(randNum) //hot key 过时时间 expireTime := 50 //过时时间平缓化的一个时间随机值 randExpireTime := GenerateRangeNum(0, 5) data, err := redis.String(c.Do("GET", tmpHotKey)) if err != nil { data, err = redis.String(c.Do("GET", hotKey)) if err != nil { data = GetDataFromDb() c.Do("SET", "hotKey", data, expireTime) c.Do("SET", tmpHotKey, data, expireTime + randExpireTime) } else { c.Do("SET", tmpHotKey, data, expireTime + randExpireTime) } } }
在这个代码中,经过一个大于等于 1 小于 M * N 的随机数,获得一个 tmp key,程序会优先访问tmp key,在得不到数据的状况下,再访问原来的 hot key,并将 hot key的内容写回 tmp key。值得注意的是,tmp key的过时时间是 hot key 的过时时间加上一个较小的随机正整数,保证在 hot key 过时时,全部 tmp key 不会同时过时而形成缓存雪崩。这是一种经过坡度过时的方式来避免雪崩的思路,同时也能够利用原子锁来写入数据就更加的完美,减少db的压力。高并发
另外还有一件事值得一提,默认状况下,咱们在生成 tmp key的时候,会把随机数做为 hot key 的后缀,这样符合redis的命名空间,方便 key 的收归和管理。可是存在一种极端的状况,就是hot key的长度很长,这个时候随机数不能做为后缀添加,缘由是 Twemproxy 的分片算法在计算过程当中,越靠前的字符权重越大,考后的字符权重则越小。也就是说对于key名,前面的字符差别越大,算出来的分片值差别也越大,更有可能分配到不一样的实例(具体算法这里不展开讲)。因此,对于很长 key 名的 hot key,要对随机数的放入作谨慎处理,好比放在在最后一个命令空间的最前面(eg:由原来的 space1:space2:space3_rand 改为 space1:space2:rand_space3)。工具
big key ,即数据量大的 key ,因为其数据大小远大于其余key,致使通过分片以后,某个具体存储这个 big key 的实例内存使用量远大于其余实例,形成,内存不足,拖累整个集群的使用。big key 在不一样业务上,一般体现为不一样的数据,好比:
……
对 big key 存储的数据 (big value)进行拆分,变成value1,value2… valueN,
//存 mset key1, vlaue1, key2, vlaue2 ... keyN, valueN //取 mget key1, key2 ... keyN
在开发过程当中,有些 key 不仅是访问量大,数据量也很大,这个时候就要考虑这个 key 使用的场景,存储在redis集群中是不是合理的,是否使用其余组件来存储更合适;若是坚持要用 redis 来存储,可能考虑迁移出集群,采用一主一备(或1主多备)的架构来存储。
在业务开发阶段,就要对可能变成 hot key ,big key 的数据进行判断,提早处理,这须要的是对产品业务的理解,对运营节奏的把握,对数据设计的经验。
经过监控以后,程序能够获取 big key 和 hot key,再报警的同时,程序对 big key 和 hot key 进行自动处理。或者通知程序猿利用必定的工具进行定制化处理(在程序中对特定的key 执行前面提到的解决方案)
尽可能仍是不要过后了吧,都是血和泪的教训,不展开讲。
谢谢阅读,欢迎交流。