[ Redis | 青训营笔记 ]
这是我参与「第五届青训营」伴学笔记创作活动的第 15 天
零、前言:
记个笔记顺便(恰青豆还是有很多不完善也有可能对的地方还请大佬们指正,嗯这节课讲的挺好的!!
一、本堂课重点内容:
- redis 是什么
- redis 应用案例
- redis 使用注意事项
二、详细知识点介绍:
15.1 redis 是什么
背景
mysql 把数据读到磁盘当遇到秒杀业务等压力大,用redis将数据存到内存里
mysql 与 redis 同步的写场景
服务端将数据存到mysql --> 监听 mysql 的binlog修改redis
redis 的基本原理
15.2 redis 应用案例
具体代码在:redis_course: 青训营redis课程Demo (gitee.com)
-
连续签到
// 在现有基础上加一 err := RedisClient.Incr(ctx, key).Err() // 过期 err := RedisClient.ExpireAt(ctx, key, expAt).Err()数据结构:sds
alloc = 5(数据的长度)
len = 10 (分配的内存)
-
消息通知
// 推送 RedisClient.BRPop(ctx, 0, ex04ListenList).Result()数据结构:redis 使用双向链表和listpack实现quicklist数据,同时redis一个节点存储了多个数据来提高效率
-
计数
一次设置多个key可以使用Pipeline来减少网络传输
pipe := RedisClient.Pipeline() userCounters := []map[string]interface{}{ {"user_id": "1556564194374926", "got_digg_count": 10693, "got_view_count": 2238438, "followee_count": 176, "follower_count": 9895, "follow_collect_set_count": 0, "subscribe_tag_count": 95}, {"user_id": "1111", "got_digg_count": 19, "got_view_count": 4}, {"user_id": "2222", "got_digg_count": 1238, "follower_count": 379}, } for _, counter := range userCounters { uid, err := strconv.ParseInt(counter["user_id"].(string), 10, 64) key := GetUserCounterKey(uid) rw, err := pipe.Del(ctx, key).Result() if err != nil { fmt.Printf("del %s, rw=%d\n", key, rw) } // 将三条命令存放到pipe里面统一执行 _, err = pipe.HMSet(ctx, key, counter).Result() if err != nil { panic(err) } } // 批量执行上面for循环设置好的hmset命令 _, err := pipe.Exec(ctx)批量读取信息
pipe.HGetAll(ctx, GetUserCounterKey(userID))对单独数据项变更
_, err = RedisClient.HIncrBy(ctx, redisKey, field, incr).Result()数据结构:hash
rehash重点,用户无感的扩容
-
排行榜
// 初始化 RedisClient.ZAdd(ctx, Ex06RankKey, initList...).Result() // 倒排序 RedisClient.ZRevRangeWithScores(ctx, Ex06RankKey, 0, -1).Result() // 获取用户分值 RedisClient.ZScore(ctx, Ex06RankKey, name).Result() // 增加积分 RedisClient.ZIncrBy(ctx, Ex06RankKey, score, name).Result() // 获取用户排名 RedisClient.ZRevRank(ctx, Ex06RankKey, name).Result()数据结构:跳跃表 zskiplist
分为多个链表
redis 中使用跳跃表+hash
-
限流
用redis里的key来计数,一个访问加一
currentQPS, err := RedisClient.Incr(ctx, key).Result() if currentQPS > ex03MaxQPS { // 超过流量限制,请求被限制 eventLogger.Append(common.EventLog{ EventTime: time.Now(), Log: common.LogFormat(routine, "被限流[%d]", currentQPS), }) // sleep 模拟业务逻辑耗时 time.Sleep(50 * time.Millisecond) err = RedisClient.Decr(ctx, key).Err() if err != nil { panic(err) } }数据结构:也是sds
-
分布式锁
redis 是单线程来实现的 set 操作
// 尝试获取锁 RedisClient.SetNX(ctx, resourceKey, routine, exp).Result()该实现存在的问题:
- 业务超时解锁,导致并发问题。业务执行时间超过锁超时时间
- redis主备切换临界点问题。主备切换后,A持有的锁还未同步到新的主节点时,B可在新主节点获取锁,导致并发问题。
- redis集群脑裂,导致出现多个主节点
15.3 redis 使用注意事项
-
大 Key,热 Key
大 key:字节数大于10 kb 或者复杂元素结构个数大于5000
危害:读取成本高,查询慢,主从复制异常
解决:拆分,压缩,冷热分离(只存前几页数据后续走db)
热 key:QPS超过 500
危害:会集中访问一台机器导致不可用
解决:设置Localcache,拆分
字节内使用redis访问代理结合“热key发现”和“locacache”
-
慢查询场景
- 批量操作(pipeline)单次不超过100个
- zset大小小于5k
- 避免大key
-
缓存穿透,缓存雪崩
穿透:热点数据查询绕过缓存,直接查询数据
解决:缓存空值,布隆过滤器
雪崩:大量缓存同时过期
解决:缓存空值,使用集群