16

Redis,就是这么朴实无华

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzA4MTc4NTUxNQ%3D%3D&%3Bmid=2650522787&%3Bidx=1&%3Bsn=7db357cd416a049b42d84ab215fdd8a4
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

e6JBVrU.gif!mobile

http://xjjdog.cn 对200+原创文章进行了细致的分类,阅读更流畅,欢迎收藏。

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。任何不保留此声明的转载都是抄袭。

Redis是2009年发布的,到今天已经超过10岁了。作为必备技能之一,关于它也有聊不完的话题。本文中的任何一个点,都可以展开,完成一篇中等规模的文章。

交流和面试时,你需要用最精准的语言进行描述,那么本文比较适合你。

YBBzEzV.png!mobile

redis能力:

  • 1 0W/s QPS (redis-benchmark)

  • 1w+ 长链接 (netstat / ss)

  • 最复杂的Zset 6kw数据 写入1k/s 读取5k/s 平均耗时5ms

  • 持久化 (rdb)

1. 基本概览

学习一门新语言,重要的是掌握它的基本数据结构,以及这些数据结构的API。redis的这些数据结构,就类似一门语言。

Redis数据结构

常用5种,一共10种。面试时一般回答5种即可,但其他5种是加分项。

  • String 字符串
  • Hash 字典
  • List 列表
  • Set 集合
  • ZSet 有序集合。性能参考: 《redis的zset有多牛?请把耳朵递过来》
  • Pubsub 发布订阅 (不推荐使用,坑很多)
  • Bitmap 位图
  • GEO 地理位置 (有限使用,附近的人)
  • Stream 流(5.0) (与Kafka非常像)
  • Hyperloglog 基数统计

Redis的协议

Redis是文本协议

  • RESP 以CRLF结尾(\r\n)
  • RESP3 (redis6启用,增加客户端缓存)

Redis底层数据结构

数据量较小和大数据量的时候,往往不同,关注大数据量的主要结构。

  • String -sds
  • Hash -(ziplist , dict)
  • Set -(intset,dict)
  • List -(ziplist,quicklist)
  • ZSet -(ziplist+skiptable 跳表)
  • Stream -(radix-tree 基数数)

跳表的关注度比较大,在Java中,可以参考类似 ConcurrentSkipListMap 实现。

另:Java中有序Set叫做 TreeSet ,但是用红黑树实现的,注意区别。

Redis持久化方式

生产环境,一般仅采用RDB模式。

  • RDB
  • AOF (类似Binglog row模式)
  • 混合模式:RDB+AOF

O(n)指令

  • keys *

  • hgetall

  • smembers

  • sunion

  • ...

建议在集合大小不确定的时候,使用 scan hscan sscan zscan 替代。另外,像 keys 这种危险命令,最好使用 RENAME 指令给屏蔽掉。

性能优化

  • unlink 删除key -> 异步避免阻塞
  • pipeline 批量传输,减少网络RTT ->减少频繁网络交互
  • 多值指令(mset,hmset)-> 减少频繁网络交互

  • 关掉 aof -> 避免io_wait

扩展方式

  • lua

  • redis-module

module模式知道的人比较少,属于比较底层的开发。

2. 问题排查

  • monitor指令 回显所有执行的指令。可以使用grep配合过滤
  • keyspace-events 订阅某些Key的事件。比如,删除某条数据的事件,底层实现基于pubsub
  • slow log 顾名思义,满查询,非常有用
  • --bigkeys 启动参数 Redis大Key健康检查。使用的是scan的方式执行, 不用担心阻塞
  • memory usage keymemory stats 指令
  • info
    instantaneous_ops_per_sec
    used_memory_human
    connected_clients
    
  • redis-rdb-tools rdb线下分析

3. 淘汰策略

如果你应聘的是redis dba,这道题答不出来,直接淘汰。

  1. 被动删除 (只有被get到的时候,删除并返回NIL 属于惰性删除)

  2. 主动删除 (100ms运行一次,随机删除持续25ms,类似Cron)

  3. ->内存使用超过maxmemory,触发主动清理策略

针对于第三种情况,有8种策略。注意,redis已经有LFU了。

  1. 默认 volatile-lru 从设置过期数据集里查找最近最少使用
  2. volatile-ttl 从设置过期的数据集里面优先删除剩余时间短的Key
  3. volatile-random 从设置过期的数据集里面任意选择数据淘汰
  4. volatile-lfu 从过期的数据集里删除 最近不常使用 的数据淘汰
  5. allkeys-lru
  6. allkeys-lfu
  7. allkeys-random 数据被使用频次最少的,优先被淘汰
  8. no-enviction

如果不设置 maxmemory ,Redis将一直使用内存,直到触发操作系统的OOM-KILLER。

4. 集群模式

  1. 单机

  2. 单机多实例

  3. 主从(1+n)

  4. 主从(1+n)& 哨兵(3或者基数个)

  5. Redis Cluster (推荐,但使用有限制)。参考: 《与亲生的Redis Cluster,来一次亲密接触》

互联网建议使用Redis Cluster,外包、项目随意。

具体搭建过程,请参考: 《好慌,Redis这么多集群方案,要用哪种?》

大规模

  • twemproxy

  • codis

  • 基于Netty Redis协议自研

  • 管理平台:CacheCloud

5. Redis常见问题

Redis使用场景

  • 缓存 (缓存一致性 缓存穿透 缓存击穿 缓存雪崩)

  • 分布式锁 (redlock)

  • 分布式限流

  • Session

API举例:

  • zset 排行榜,排序

  • bitmap 用户签到,在线状态

  • geo 地理位置,附近的人

  • stream 类似kafka的消息流

  • hyperloglog 每日访问ip数统计

缓存一致性

为什么有一致性问题?

  • 写入。缓存和数据库是两个不同的组件,只要涉及到双写,就存在只有一个写成功的可能性,造成数据不一致。

  • 更新。更新的情况类似,需要更新两个不同的组件。

  • 读取。读取要保证从缓存中读到的信息是最新的,是和数据库中的是一致的。

  • 删除。当删除数据库记录的时候,如何把缓存中的数据也删掉?

建议使用:Cache Aside Pattern

读请求:

  • 先读cache,再读db

变更操作:

  • 先操作数据库,再 淘汰 缓存

涉及到复杂的事务和回滚操作,可以把淘汰放在finally里。

问题:缓存淘汰失败!(概率很低 ,定时补偿)

缓存击穿

影响,轻微。

高流量下 大量请求读取一个失效的Key -> Redis Miss -> 穿透到DB

解决方式:采用分布式锁,只有拿到锁的第一个线程去请求数据库,然后插入缓存

缓存穿透

影响,一般。

访问一个不存在的Key(恶意攻击)-> Redis Miss -> 穿透到DB

解决方式:

  1. 给相应的Key设置一个Null值,放在缓存中

  2. BloomFilter预先判断

缓存雪崩

影响:严重。

大量Key同时失效 | 2.Redis当机 -> Redis Miss -> 压力打到DB

解决方式:

  1. 给失效时间加上相对的随机数

  2. 保证Redis的高可用

分布式锁

redis的分布式锁,并不是那么简单。建议使用redisson的redlock。最基础的指令是setnx。

setnx-> SET key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX] [GET]

分布式锁 关键点:

  • 原子性

  • 锁超时

  • 死锁

  • 读写锁

  • 故障转移

最简单的Redis分布式锁代码(不严谨)。

java端代码模拟lock和unlock。

public String lock(String key, int timeOutSecond) {
    for (; ; ) {
        String stamp = String.valueOf(System.nanoTime());
        boolean exist = redisTemplate.opsForValue().setIfAbsent(key, stamp, timeOutSecond, TimeUnit.SECONDS);
        if (exist) {
            return stamp;
        }
    }
}
public void unlock(String key, String stamp) {
    redisTemplate.execute(script, Arrays.asList(key), stamp);
}

lua脚本unlock。

local stamp = ARGV[1]
local key = KEYS[1]
local current = redis.call("GET",key)
if stamp == current then
    redis.call("DEL",key)
    return "OK"
end

6. Redis使用

常用Java客户端

  • lettuce SpringBoot默认,基于Netty的事件驱动模型

  • jedis   老牌的客户端,使用commons-pool来完成线程池开发

  • redisson 非常丰富的分布式数据结构,包括锁,分布式Map等。大量使用Lua脚本️

详细分析: Redis都要老了,你还在用什么古董客户端?

使用规范

根据公司情况自定义裁剪,没有万能的规范。更多:

这可能是最中肯的Redis规范了

  • 使用连接池,不要频繁创建关闭客户端连接

  • 消息大小限制 消息体在10kb以下,可以使用snappy、msgpack等压缩

  • 避免大key和hot key

  • 不使用O(n)指令

  • 不使用不带范围的Zrange指令

  • 不使用database(容易覆盖数据)

  • 不使用高级数据结构(使用基本的5种)

  • 不使用事务操作

  • 禁止长时间monitor

springboot cache redis

  • 使用时更要注意规范性

  • cache层抽象层次太高,如需要操作底层的数据结构,直接使用redisTemplate

Redis是多线程?

要看哪个阶段。数据操作阶段,一直是单线程的,哪怕是redis6。

这篇文章分析了这个过程: 和 杠精 聊Redis多线程 :(

End

祝好运!如有帮助,请不吝赐赞。

作者简介: 小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

推荐阅读:

一图解千愁,jvm内存从来没有这么简单过!

失联的架构师,只留下一段脚本

架构师写的BUG,非比寻常

nginx工程师,需要上承天命,下召九幽

实力解剖一枚挖矿脚本,风骚操作亮瞎双眼

又一P1故障,锅比脸圆

传统企业的人才们,先别忙着跳“互联网”!

面试官很牛,逼我尿遁

又一批长事务,P0故障谁来背锅?

一天有24个小时?别开玩笑了!

《程序人生》杀机!

可怕的“浏览器指纹”,让你在互联网上,无处可藏

2w字长文,让你瞬间拥有「调用链」开发经验

996的乐趣,你是无法想象的

作为高级Java,你应该了解的Linux知识(非广告)

必看!java后端,亮剑诛仙(最全知识点)

学完这100多技术,能当架构师么?(非广告)

Linux上,最常用的一批命令解析(10年精选)

数百篇「原创」文章,助你完成技术「体系化」

EnUneen.gif!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK