[ Redis | 青训营笔记 ]

64 阅读3分钟

[ Redis | 青训营笔记 ]

这是我参与「第五届青训营」伴学笔记创作活动的第 15 天

零、前言:

记个笔记顺便(恰青豆还是有很多不完善也有可能对的地方还请大佬们指正,嗯这节课讲的挺好的!!

一、本堂课重点内容:

  • redis 是什么
  • redis 应用案例
  • redis 使用注意事项

二、详细知识点介绍:

15.1 redis 是什么

背景

mysql 把数据读到磁盘当遇到秒杀业务等压力大,用redis将数据存到内存里

mysql 与 redis 同步的写场景

服务端将数据存到mysql --> 监听 mysql 的binlog修改redis

redis 的基本原理

2023-02-15-10-23-37-image.png

15.2 redis 应用案例

具体代码在:redis_course: 青训营redis课程Demo (gitee.com)

  • 连续签到

    // 在现有基础上加一
    err := RedisClient.Incr(ctx, key).Err()
    
    // 过期
    err := RedisClient.ExpireAt(ctx, key, expAt).Err()
    

    数据结构:sds

    2023-02-15-10-38-28-image.png

    alloc = 5(数据的长度)

    len = 10 (分配的内存)

  • 消息通知

    // 推送
    RedisClient.BRPop(ctx, 0, ex04ListenList).Result()
    

    数据结构:redis 使用双向链表和listpack实现quicklist数据,同时redis一个节点存储了多个数据来提高效率

    2023-02-15-10-49-58-image.png

  • 计数

    一次设置多个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

    2023-02-15-11-07-45-image.png

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

    分为多个链表

    2023-02-15-11-23-40-image.png

    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)

    2023-02-15-11-42-20-image.png

    热 key:QPS超过 500

    危害:会集中访问一台机器导致不可用

    解决:设置Localcache,拆分

    2023-02-15-11-48-24-image.png 字节内使用redis访问代理结合“热key发现”和“locacache”

    2023-02-15-11-50-58-image.png

  • 慢查询场景

    • 批量操作(pipeline)单次不超过100个
    • zset大小小于5k
    • 避免大key
  • 缓存穿透,缓存雪崩

    穿透:热点数据查询绕过缓存,直接查询数据

    解决:缓存空值,布隆过滤器

    雪崩:大量缓存同时过期

    解决:缓存空值,使用集群