ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Redis基础教程:从数据类型到Spring Boot实战

Redis基础教程:从数据类型到Spring Boot实战 1. 为什么大家都在聊Redis它到底解决了什么问题我第一次接触Redis是好几年前做缓存的时候。当时项目里有一个高频查询接口每次请求都要去MySQL里捞数据数据库连接被打满慢查询堆积接口响应时间从几十毫秒一路飙到几秒。后来引入了Redis缓存热点数据接口响应掉到个位数毫秒数据库压力瞬间就降下来了。这是Redis最典型的应用场景但如果你以为Redis只是个缓存那就太小看它了。Redis的全称是Remote Dictionary Server也就是远程字典服务。它本质上是一个基于内存的键值型NoSQL数据库数据主要存储在内存里所以读写速度极快官方数据是读速度约11万次/秒写速度约8.1万次/秒。这个速度是传统关系型数据库完全比不上的。但Redis最厉害的地方不光是快而是它提供了丰富的数据结构——字符串、哈希、列表、集合、有序集合还有位图、HyperLogLog、地理坐标等高级结构。这就让它能做的事情远超缓存本身分布式锁、消息队列、排行榜、计数器、社交关系、自动补全、会话共享等等都能基于Redis来实现。这个教程适合谁我觉得有三类人一类是刚接触后端开发没多久听过Redis但一直没系统学过的准程序员一类是已经在项目里用过Redis主要是拿来做缓存但只停留在get/set层面的同学还有一类是正在准备面试需要把Redis基础概念梳理清楚的求职者。不管你属于哪一类这篇基础篇的目标只有一个让你对Redis形成一个完整、系统的认知框架而不是零散地记住几个命令。学完之后你能独立完成Redis的安装、配置、数据类型操作、可视化工具连接、Spring Boot集成并且能讲清楚缓存穿透、击穿、雪崩这些高频考点背后的原理。在开始之前我多说一句Redis的资料满天飞但很多教程要么太浅只列命令不解释设计思路要么太深上来就讲源码和集群把新手直接劝退。这篇基础篇我尽量用工程实践的视角把每个知识点、每个命令背后的为什么讲清楚。比如为什么Redis单线程还能这么快为什么list既能当栈又能当队列为什么生产环境一定要设置maxmemory。这些问题的答案才是你真正能在面试和工作中用上的东西。2. Redis的安装与基础配置Windows/Linux/Docker三种方式一次搞定2.1 Linux源码编译安装生产环境最推荐的方案生产环境几乎都是Linux最正规的安装方式是源码编译安装。Redis的官网是redis.io下载页面提供最新稳定版的源码包。这里插一句官网经常更新不同小版本之间的配置差异不大但大版本之间可能会有新特性比如Redis 6.0开始支持多线程IORedis 7.0引入了Function特性所以学习时尽量选当前较新的稳定版。源码编译的流程很简单wget https://download.redis.io/releases/redis-7.0.14.tar.gz tar -xzf redis-7.0.14.tar.gz cd redis-7.0.14 make make install编译完成后Redis的可执行文件会被安装到/usr/local/bin目录下。这里有个细节要说明make install只安装了服务端和客户端工具但不会自动创建配置目录和数据目录。我习惯手动建立一套清晰的目录结构mkdir -p /etc/redis mkdir -p /var/lib/redis mkdir -p /var/log/redis cp redis.conf /etc/redis/redis.conf然后再修改配置文件里的几个关键项。这里面dir参数是Redis持久化文件存放的位置默认是./也就是启动目录如果不改哪天你在某个临时目录下启动redis-serverRDB快照就会写到那个临时目录里非常容易丢数据。我踩过一次坑在这里必须提醒你dir参数一定要显式设置。daemonize yes bind 0.0.0.0 protected-mode yes port 6379 dir /var/lib/redis logfile /var/log/redis/redis-server.log maxmemory 256mb maxmemory-policy allkeys-lru这里逐项解释一下。daemonize yes是让Redis以后台守护进程方式运行这样关掉终端后服务不退出。bind 0.0.0.0表示监听所有网卡生产环境如果Redis只给内网用更安全的写法是bind 127.0.0.1加上内网IP。protected-mode yes很重要在没有设置密码且监听所有网卡的情况下Redis只允许本机访问这个机制在Redis 3.2之后默认开启能挡住很大一部分被入侵的风险。maxmemory是Redis能使用的最大内存应该设为机器物理内存的一部分不能贪多因为Redis备份时子进程需要拷贝内存页表留给系统一些余量更安全。maxmemory-policy是内存淘汰策略allkeys-lru意思是内存满了之后对所有key执行LRU算法淘汰最近最少使用的键。启动Redis并验证redis-server /etc/redis/redis.conf redis-cli ping如果返回PONG说明服务已经正常启动。这个时候再检查一下两个信息redis-cli info memory里的used_memory字段以及redis-cli info stats里的total_connections_received字段前者能观察到Redis实际占用的内存后者能看到累计接收的连接数这两个指标以后排查问题会经常用。2.2 Windows下的安装选择开发环境够用就行Redis官方并不支持Windows这是很多新手第一次接触Redis时困惑的地方。但其实微软曾维护过一个Windows移植版现在GitHub上也有一些社区维护的版本比如tporadowski/redis就是比较常用的Windows移植版本它基于Redis 5.0功能上足够日常开发学习使用。Windows下最简单的方式是直接下载zip压缩包解压后就能看到redis-server.exe和redis-cli.exe。启动方式是打开命令行cd到解压目录执行redis-server.exe服务就起来了。如果想自定义配置可以先把目录里的redis.windows.conf改好然后执行redis-server.exe redis.windows.conf。这里有一个非常重要的注意事项Windows版的Redis不能用于生产环境原因有两点。第一Windows移植版是基于较老的Redis版本缺少新版特性和修复第二Redis的持久化机制依赖fork系统调用Windows下的实现效率远低于Linux原生的fork尤其是数据量大了之后持久化时会发生明显的卡顿甚至假死。所以我的建议是Windows版仅用于本地开发测试跑通了业务逻辑即可生产环境一律用Linux。2.3 Docker方式快速部署适合本地开发和微服务环境Docker是现在最流行的Redis部署方式尤其在微服务和本地开发场景下一条命令就能拉起一个干净的Redis实例。如果你本机装了Docker执行docker run -d --name redis \ -p 6379:6379 \ -v /myredis/data:/data \ -v /myredis/conf/redis.conf:/etc/redis/redis.conf \ redis:7.0 redis-server /etc/redis/redis.conf这套参数解释一下-d是后台运行--name指定容器名称-p 6379:6379把宿主机的6379端口映射到容器的6379端口两个-v分别把宿主机上的数据目录和配置文件挂载进容器。最后面的redis-server /etc/redis/redis.conf是容器启动时执行的命令让Redis加载挂载进去的配置。Docker方式最大的好处是环境隔离和可重复同一个配置可以复制到任何机器上跑团队协作时特别方便。如果你想体验Redis主从复制也可以直接用Docker在一台机器上起三个容器分别映射到6379、6380、6381端口这种模式后面讲主从复制时会更清楚。2.4 可视化工具选型Another Redis Desktop Manager用起来最顺手Redis的命令行工具redis-cli很强大但日常查看数据、检查key分布、监控内存这些操作用可视化工具效率会高很多。市面上常用的工具有几款Redis官方出的RedisInsight功能全面支持数据浏览、Slow Log分析、内存分析等但界面相对重一点加载大数据集时偶尔会卡Redis Desktop Manager是老牌工具但新版变成了商业收费模式社区版停在老版本还有一款我觉得最顺手的是Another Redis Desktop Manager这是一个开源免费的跨平台工具GitHub上的Star数非常高。为什么推荐Another Redis Desktop Manager首先是免费功能上没有任何阉割其次它支持Redis Cluster能直接以集群模式连接多个节点这点很多同类工具做不到第三是它的树形结构展示key的方式非常直观支持按前缀分组我们在项目里通常会用业务前缀命名key比如user:info:1001、order:list:20240501这样在工具里一眼就能看出业务模块和数据分布。连接配置也很简单填入Redis所在的主机IP、端口6379、数据库编号如果设置了密码就在Auth里填。特别提醒一下如果你用Docker启动的Redis默认是没设置密码的开发环境无所谓但如果你的Redis暴露在公网上一定要设置requirepass否则会被人扫描到直接入侵。我就见过有人的Redis因为没设密码被挖矿程序写入了恶意定时任务处理起来相当麻烦。3. 五大核心数据类型从命令到场景一次讲透3.1 String字符串不仅仅是缓存值String是Redis里最基础的数据类型一个key对应一个valuevalue最大能存512MB。你可能会觉得String太简单了不就是set和get吗但String在Redis底层做了很多优化不同长度的字符串会使用不同的编码方式短的用embstr长的用raw这些细节非常影响内存效率。String的常用命令包括命令作用示例SET key value设置键值SET user:name zhangsanGET key获取值GET user:nameSETNX key value键不存在时设置分布式锁基础SETNX lock:order 1INCR key自增1INCR article:viewsDECR key自减1DECR stock:001INCRBY key num自增指定数值INCRBY article:views 10EXPIRE key seconds设置过期时间EXPIRE session:token 3600TTL key查看剩余过期时间TTL session:tokenSETEX key seconds value设置值并指定过期时间SETEX verify:code 300 123456这里我说一个面试里经常问的点为什么Redis的INCR能实现计数器而不是先GET再SET因为INCR是原子操作。原子在这里的意思是在并发情况下多个客户端同时执行INCR每个客户端都能拿到正确且不同的结果不会出现两个客户端同时读到100、都改成101的情况。Redis是单线程执行命令的所以这些基础命令天生就是原子的。实际场景里String用得最多的是这几类缓存热点数据比如把用户的个人信息序列化成JSON后缓存起来计数器和限流比如文章阅读量、接口调用次数分布式锁使用SETNX加上过期时间后面会详细讲共享Session把用户的登录凭证存到Redis多个服务实例共享。3.2 Hash哈希最适合存对象的类型Hash在Redis里表示的是一个key下面挂了多个field-value对它最适合存储对象。举个例子保存一个用户的信息如果用String存大概是这样user:1001的value是一串JSON序列化后的字符串。但这样做有个问题我只想改用户的手机号却必须先把整个JSON取出来反序列化改字段再序列化存回去非常浪费。用Hash就能解决。一个key是user:1001下面有username、email、phone这些field每个field各自独立取值和更新。HSET user:1001 username zhangsan HSET user:1001 email zsexample.com HSET user:1001 phone 13800000000 HGET user:1001 phone HGETALL user:1001 HINCRBY user:1001 login_count 1HINCRBY是我特别喜欢的一个命令它能对Hash里的某个字段做原子自增。比如说我要记录用户连续登录的天数或者累计登录次数用HINCRBY一次就能搞定不用取出整个对象再整体写回。Hash在底层有两种编码方式数据量小的时候用ziplist节省内存数据量大了会转为hashtable。这个自动转换的过程不用管但你要知道hash并不会无限制节省内存如果field特别多底层转换为hashtable后内存开销反而会上升。所以使用Hash的时候一个key下挂的字段一般建议控制在一个合理范围比如几百个以内。3.3 List列表能当队列也能当栈List在Redis里是一个双向链表头部和尾部都可以插入和删除。这意味着它能当队列用先进先出也能当栈用先进后出。常用的命令是LPUSH、RPUSH、LPOP、RPOP加上LRANGE查看指定范围的元素。LPUSH task:queue task_001 LPUSH task:queue task_002 RPOP task:queue这两条命令组合起来就实现了一个简单的队列生产者把任务LPUSH进列表头部消费者从列表尾部RPOP取出任务先进入的先被处理。BRPOP是阻塞版本它在列表为空时会一直等待直到有新元素进入。这个命令是实现简易消息队列的关键。用传统的轮询方式如果队列是空的消费者会立刻返回null程序就得写循环反复去拉既浪费资源又增加延迟。用BRPOP的话消费者会挂在那里一旦有新任务产生立刻就能拿到类似阻塞队列的效果。BRPOP task:queue 0最后一个参数0代表无限等待。实际项目中很多轻量级场景就是用List来实现任务队列的比如异步发送邮件、异步生成报表虽然没有Kafka那么强悍但胜在简单直接不需要额外引入消息中间件。3.4 Set集合去重和关系计算的好帮手Set是一个无序的字符串集合里面的元素不能重复。它天然适合做去重。常用命令有SADD、SMEMBERS、SISMEMBER、SREM还有SINTER、SUNION、SDIFF这几个做集合运算的命令。SADD user:1001:follows 2001 2002 2003 SADD user:1002:follows 2002 2004 SINTER user:1001:follows user:1002:follows这个例子是社交场景中的经典用法SINTER能算出两个用户共同关注了谁。SUNION能算出合集中有哪些人适合做粉丝数合并。SDIFF能算出差集比如我关注了但对方没有回关我的人。这三个命令的效率非常高集合运算在Redis服务端完成不需要把数据拉到客户端再处理。Set还有一个很值钱的特性SPOP命令能随机弹出集合中的元素。这个特性适合做抽奖场景或者抢票场景中随机分配资源。我不止一次在项目里看到用SQL实现随机抽奖的效率低不说并发一高还会有重复分配的问题换成Redis的Set加SPOP性能和准确度都提升一个量级。3.5 ZSet有序集合排行榜功能的最佳选型ZSet在Set的基础上给每个元素额外关联了一个score分数Redis根据score从小到大自动排序。这个特性让ZSet成了排行榜功能的最佳选型比分的话用String自增也能做但要排顺序、取Top N只有ZSet最合适。ZADD leaderboard 100 user_1001 ZADD leaderboard 200 user_1002 ZADD leaderboard 150 user_1003 ZINCRBY leaderboard 50 user_1003 ZREVRANGE leaderboard 0 9 WITHSCORES这组命令模拟了一个积分排行榜ZADD添加初始积分ZINCRBY给某个用户加分ZREVRANGE按分数从高到低取出前10名。ZSet底层用的是跳表加哈希表。哈希表负责快速定位元素跳表负责维护有序序列。这也是面试中比较高频的知识点面试官问ZSet底层是怎么实现的你如果能答出跳表加哈希的组合同时再解释一句为什么用跳表而不是平衡树会是很加分的表现。简单来说跳表的实现比平衡树简单区间查询的性能也能达到O(logN)对Redis这种追求性能又要控制代码复杂度的场景来说是非常好的平衡点。ZSet的场景远不止排行榜。延时队列也能用ZSet做把任务的执行时间戳作为score轮询的时候用ZRANGEBYSCORE取出当前时间之前到期的任务。还有热门商品列表用点击量做score定时刷新Top100性能极佳。3.6 其他常用结构位图、HyperLogLog和地理坐标基础篇里我简单提一下这三种容易被忽略但非常实用的结构。位图Bitmap本质上是String类型但它支持按位操作。它最典型的应用是用户签到功能。一年365天每天签到记为1没签到记为0只需要365个bit不到50个字节就能存储一个用户一整年的签到记录。命令是SETBIT key offset 1和GETBIT key offset。HyperLogLog是基数统计结构它能在极小的内存空间内估算唯一元素的数量。比如统计一个网站的UV如果精确到字节级别去记录每个IP几百万的IP量级内存根本扛不住。用HyperLogLog默认情况下只需要12KB左右的存储空间就能对几十亿级别的基数做统计标准误差约0.81%。这是典型的用精度换空间的思路在统计类场景中性价比极高。Geo是地理位置结构可以用来存储经纬度坐标并且支持计算两个位置之间的距离、查找某个坐标附近的元素。如果你做过基于LBS的附近的人功能Redis Geo能省掉很多空间计算的工作。4. 过期策略、持久化与内存淘汰Redis的底层机制要搞懂4.1 键过期机制Redis不是存进去就永远在Redis支持给key设置过期时间命令是EXPIRE key seconds或者SET时直接带EX参数比如SETEX。设置过期时间的场景太多了验证码5分钟有效、Session 30分钟有效、缓存数据10分钟有效。但你可能没想过一个问题Redis怎么判断key过期了又是怎么删除的这里涉及Redis的两种删除策略。第一种是惰性删除。当客户端访问一个key时Redis先检查它是否设置了过期时间如果过期了就直接删除并返回nil。这种策略的好处是省CPU缺点是如果key一直没被访问它会一直占着内存。第二种是定期删除。Redis每隔一定时间会随机抽取一部分设置了过期时间的key进行检测把过期的删掉。这里的重点是随机抽取不是遍历所有key因为Redis的key数量可能非常庞大不可能每次都用全量扫描消耗CPU。实际运行时是这两种策略配合使用的定期删除负责主动清扫惰性删除负责兜底。但这里就引出了一个经典问题如果大量key同时过期定期删除没扫干净又没被访问触发惰性删除这些key岂不是一直占着内存这就是内存淘汰策略要解决的问题。4.2 内存淘汰策略Redis内存满了该怎么办Redis可以设置maxmemory来限制最大内存。当内存达到这个上限时新写入命令会报错吗不会Redis会按照maxmemory-policy配置的策略来淘汰旧数据。之前的配置片段里我写的是allkeys-lru这是最常用的策略。常见的淘汰策略有这几种策略说明使用场景noeviction内存满后不再服务写请求直接报错数据绝不能丢的场景allkeys-lru对所有键执行LRU近似算法淘汰最久未使用的纯缓存场景最常用volatile-lru只对设置了过期时间的键执行LRU缓存加持久化混合使用allkeys-random随机淘汰任意key对淘汰谁不在乎只求腾出空间volatile-ttl淘汰剩余过期时间最短的key需要优先淘汰快过期的数据这里特别解释一下LRU近似算法。Redis并没有实现严格的LRU因为严格LRU需要维护每个key的访问时间内存开销太大。Redis的实现是每个key记录最近一次访问的时间戳在淘汰时随机抽取一部分样本在样本中挑出最久没被访问的key淘汰掉。默认取5个样本这就是近似LRU的由来。在大多数场景下近似LRU的效果已经足够好。这个知识点面试经常会考到你需要能说清楚Redis的LRU不是精确的是抽样的不同策略的适用场景不同volatile前缀的策略只作用于设置了过期时间的key。4.3 持久化机制RDB快照和AOF日志怎么选Redis是内存数据库如果不做持久化一旦进程退出或机器重启所有数据都会丢失。持久化是Redis非常重要的一块生产环境必须配置。RDB是一种快照持久化方式。它把某个时间点的完整数据集生成一个二进制文件默认叫dump.rdb。触发RDB快照的条件可以在配置文件里设置比如save 900 1 save 300 10 save 60 10000这几条的意思分别是900秒内至少有1个key变化就触发快照300秒内至少有10个key变化就触发60秒内至少有10000个key变化就触发。RDB的优点是恢复速度快适合做备份、迁移缺点是它是一次性的快照两次快照之间如果Redis崩溃中间的数据更新会丢失。AOF是追加日志方式。它会把你执行的每一条写命令都以Redis协议格式追加到日志文件中。Redis重启时把AOF里的命令重新执行一遍就能恢复数据。AOF的优点是数据安全性高可以配置everysec每秒刷盘一次极端情况下最多丢一秒的数据缺点是文件体积大恢复速度比RDB慢。现在的建议是混合使用。Redis 4.0之后支持开启aof-use-rdb-preambleAOF文件头部是RDB格式的完整快照后面的增量数据用AOF命令追加这样兼顾了恢复速度和数据安全。基础使用的话你至少要清楚RDB和AOF的区别生产环境两者都开用默认的配置基本问题不大。5. 事务、发布订阅与慢查询日志进阶基础必知必会5.1 Redis事务批量执行但不保证回滚Redis事务和MySQL事务有本质区别。Redis事务通过MULTI、EXEC、DISCARD三个命令来实现。MULTI开始事务后续命令进入一个队列EXEC一次性执行队列里的所有命令。MULTI SET user:name zhangsan INCR user:age EXEC事务里的命令是原子执行的注意这里的原子指的是执行期间不会被其他客户端的命令插队。这和前面说的INCR原子操作是一个意思基础命令在单线程环境下天然具有这种隔离性。但Redis事务不支持回滚。如果EXEC执行过程中某条命令出错了其他命令照样执行不会像MySQL那样全部撤销。这是设计上的取舍Redis作者认为回滚是数据库复杂性的来源而Redis追求的是简单高效。实际开发中我很少用MULTI/EXEC因为出错不回滚这个特性对业务来说不够安全。更多时候我会用Lua脚本来实现多条命令的原子性Spring Boot里的DefaultRedisScript就是做这个的。这里先提一下等你深入使用时自然会遇到。5.2 发布订阅模式一对多消息通知的轻量方案Redis提供了发布订阅机制。客户端可以订阅一个或多个频道其他客户端向频道发布消息后所有订阅者都能收到。常用命令是SUBSCRIBE、PUBLISH。PUBLISH channel:news Hello Redis SUBSCRIBE channel:news发布订阅适合简单的消息广播场景比如用户下单后通知积分服务、优惠券服务、短信服务各做各的事。但它有几个局限性需要注意消息不会持久化没有消费者在线时消息就丢了不支持消息堆积如果消费者处理不过来消息也不会暂存只能丢弃。所以它适合对消息可靠性要求不高的场景。如果你的系统需要保证每条消息都准确送达并处理建议用专业的消息队列比如RabbitMQ或Kafka。5.3 慢查询日志与Redis日志线上排查的基本功Redis会把执行时间超过指定阈值的命令记录到慢查询日志中。阈值由slowlog-log-slower-than配置单位是微秒默认10000微秒也就是10毫秒。slowlog-max-len控制慢查询日志最多保存多少条。排查操作的几个常用命令SLOWLOG GET SLOWLOG LEN SLOWLOG RESET如果一条命令执行时间很长十有八九是出现了大key或者用KEYS命令做了全量扫描。KEYS命令在key数量非常多时会阻塞Redis服务器生产环境绝对不能使用替代方案是SCAN命令它支持游标式增量遍历每次只返回一小部分数据不会卡住服务。Redis的日志文件配置在前面已经提到logfile参数指定日志路径。通过日志能观察到启动状态、持久化触发情况、错误信息等。排查问题时我习惯先看日志再看慢查询再有针对性地用INFO命令检查内存、连接数、命中率等指标。6. Redis在Spring Boot中的实战集成从依赖到缓存治理6.1 引入依赖和基础配置前面讲的都是Redis本身的操作实际项目中我们几乎总是通过代码访问Redis的。Java生态里最常用的是Spring Boot整合Spring Data Redis。第一步是引入依赖以Maven为例dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependency第二依赖commons-pool2是因为Spring Data Redis默认使用Lettuce连接池需要这个库来管理连接池。然后是application.yml配置spring: redis: host: 127.0.0.1 port: 6379 password: database: 0 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1ms这里database: 0指的是使用Redis默认的0号数据库。Redis默认有16个数据库编号从0到15。通过SELECT命令可以切换。但在微服务架构下我建议所有业务都使用0号库然后用key前缀来做业务隔离比如user:、order:、product:。原因在于Redis Cluster模式只支持0号库如果以后要扩展集群使用其他编号的库会直接迁移失败。这个坑网上踩的人非常多提前规划能省掉后面极大的麻烦。6.2 RedisTemplate与序列化问题Spring Data Redis提供了RedisTemplate和StringRedisTemplate两种操作模板。StringRedisTemplate只能操作字符串RedisTemplate更通用可以操作任意对象。在实际使用中最常遇到的问题就是序列化。Redis存储的是字节数据Java对象要存进去就必须序列化。Spring Boot默认使用JDK序列化会产生大量额外的类信息导致Redis里存储的数据可读性极差全是类似\xAC\xED\x00\x05t\x00的乱码。同时JDK序列化后的数据体积很大浪费内存。解决方法是自定义RedisTemplate的序列化器。我一般这么配置Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer stringRedisSerializer new StringRedisSerializer(); GenericJackson2JsonRedisSerializer jsonRedisSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setValueSerializer(jsonRedisSerializer); template.setHashValueSerializer(jsonRedisSerializer); template.afterPropertiesSet(); return template; } }这个配置的含义是key和hash的key都用String序列化保证Redis里key可读value和hash的value用JSON序列化存储的内容是清晰的JSON格式。但这里有一个需要特别注意的问题GenericJackson2JsonRedisSerializer序列化对象时会在JSON里带上class类型信息以便反序列化时还原成正确的类型。这在做缓存时很安静但对于一些简单的对象如果不想在缓存数据里暴露类路径可以考虑使用Jackson的ObjectMapper配合指定类型的方式进行序列化。这个话题在深水区文章里会展开基础篇先了解序列化器决定数据的可读性和内存大小这件事就足够了。6.3 缓存穿透、击穿、雪崩高频面试题的工程解释这三个概念是Redis面试几乎必问的而且很多人在描述时容易混淆。我结合工程场景一次讲清楚。缓存穿透指的是请求的数据在Redis里不存在在数据库里也不存在。比如一个恶意攻击者故意请求一个不存在的商品ID每次请求都会跳过缓存直接打到数据库数据库压力急剧增大。解决办法有几种一是把空值也缓存起来设置较短的过期时间这样后续同样的请求会命中空值缓存不会再打到数据库二是使用布隆过滤器在请求到达Redis前先通过布隆过滤器判断这个key是否可能存在不存在就直接拒绝节省中间所有环节的开销。缓存击穿指的是某一个热点key突然过期恰好这时有大量并发请求同时访问这个key所有请求都穿透到数据库导致数据库瞬间压力飙升。这和缓存穿透的区别在于击穿是缓存里有数据但过期了穿透是缓存和数据库里都没有。应对方案一是设置热点key永不过期通过后台任务定时更新二是使用互斥锁当发现缓存过期后只有第一个请求能拿锁去数据库查询并重建缓存其他请求等缓存重建完成后直接命中缓存。缓存雪崩指的是大量热点key在同一时间段集中过期或者Redis服务整体宕机导致大量请求直接打到数据库数据库被打垮。应对方案一是key的过期时间增加随机值比如5到10分钟之间随机避免集中过期二是做高可用部署比如主从加哨兵或者Redis Cluster三是做多级缓存比如本地缓存加Redis缓存即使Redis挂了本地缓存还能扛住一部分流量。这三个概念我建议你在面试时用缓存层失效后的并发冲击这个主线串起来说会显得思路清晰很多。实战中也要注意缓存设计的目标并不是永不失效而是让数据库不被打垮业务可以降级。7. 主从复制与分布式锁理解Redis的协作能力7.1 主从复制原理为什么要做主从Redis的单机部署有一个巨大的隐患如果这台机器坏了服务直接不可用而且如果没有持久化数据可能全丢。主从复制是解决可用性的第一步。主从复制的结构是一个主节点负责写入多个从节点负责同步主节点的数据并处理读请求。这样实现了读写分离主节点专注于写从节点分担读压力。更重要的是当主节点发生故障时从节点可以提升为新的主节点实现故障转移。配置主从复制的方式非常简单。在一台机器上模拟的话可以直接起两个Redis实例一个用6379端口另一个用6380端口然后在6380的配置中加入replicaof 127.0.0.1 6379或者不修改配置文件直接在启动后用命令redis-server --port 6380 redis-cli -p 6380 REPLICAOF 127.0.0.1 6379执行完REPLICAOF之后从节点会同步主节点的所有数据。你可以用INFO replication命令查看主从状态。主从复制的完整流程分为几个阶段从节点启动后向主节点发送同步请求主节点执行BGSAVE生成RDB快照并发送给从节点从节点加载RDB恢复数据后续主节点收到的所有写命令会实时发送给从节点执行。这个同步过程是全量同步加增量同步的组合。7.2 Redis分布式锁的入门实现分布式锁是面试中出现频率极高的词也是微服务架构下一定会遇到的需求。它的背景是多个服务实例同时操作同一个共享资源比如扣减库存如果不用锁两个请求同时读到库存为1同时更新为0结果最后库存变成1数据就错了。单机情况下可以用Java的synchronized或Lock但多个服务实例跨JVM本地锁没法互相感知必须用一个所有服务都能访问的中间件来实现互斥Redis就是最常用的选择。Redis分布式锁最简单的实现方式是基于SETNX命令SET lock:order:1001 1 NX EX 10NX参数表示只有key不存在时才能设置成功EX 10表示10秒后自动过期。如果返回OK说明抢锁成功如果返回nil说明锁已存在请求需要等待或失败。释放锁的时候要注意不能用简单的DEL命令因为你可能把别人持有的锁删掉。正确姿势是先用Lua脚本比较value是否属于自己的标识相同才删除。这个细节在入门阶段不一定马上用上但你要知道锁的释放必须校验持有者否则在高并发下会出大问题。需要注意的是单机Redis实现的分布式锁在主从切换场景下存在理论上的风险主节点写入锁但还没来得及同步到从节点主节点挂了从节点晋升为主节点后锁数据没了其他请求就能再次抢到锁导致锁失效。如果业务对一致性要求极高RedLock算法是另一种方案但RedLock本身也有争议。基础篇先掌握SETNX这个雏形后面我会专门写一篇文章深入讲解分布式锁的各种方案和坑。7.3 Redis序列化与缓存治理实践中的几个细节回到日常开发缓存的治理是一个长期话题。刚开始用Redis的时候最常见的做法是把所有查询结果都缓存起来结果发现缓存了大量永远用不到的数据内存吃紧。后来我总结了一套简单的缓存治理思路。第一缓存一定要有合理的过期时间不要设置永久有效。即使是热点数据也建议设置过期时间然后通过后台任务刷新这样能避免数据变更后缓存长期不更新。第二key命名要规范统一的格式是业务名实体名ID比如user:info:1001。规范的命名能让你在排查问题时快速定位数据属于哪个业务。第三要控制缓存数据的大小一个key存几百KB的大JSON是大忌你应该想想是不是数据粒度没拆分好比如只需要用户昵称的接口为什么要缓存整个用户实体。还有一个经常被忽略的点Redis的内存不是无限的用完就是瓶颈。所以在设计缓存方案时要清楚哪些数据值得缓存哪些不值得。判断标准就一个访问频率高、数据更新频率低、重建代价大满足这三点的数据最适合缓存。8. 高频Redis面试题整理基础篇的浓缩考点我在文章开头提到很多读者学Redis是为了应对面试。这里把基础篇范围内最常考的题目整理成一张表每个问题你都要确保能用自己的话说清楚。面试题核心要点Redis为什么快纯内存操作、单线程避免锁竞争和上下文切换、IO多路复用Redis单线程为什么还能这么快基于内存、非阻塞IO、避免多线程切换开销、命令执行简单Redis有哪些数据类型String、Hash、List、Set、ZSet以及Bitmap、HyperLogLog、GeoRedis持久化方式RDB快照、AOF日志、混合持久化RDB和AOF的区别RDB恢复快但可能丢数据AOF安全但文件大、恢复慢Redis内存满了怎么办maxmemory-policy的淘汰策略LRU近似算法数据库和缓存一致性问题先更新数据库再删除缓存配合延迟双删策略缓存穿透查不存在的key用空值缓存或布隆过滤器缓存击穿热点key过期瞬间的高并发冲击用互斥锁或逻辑过期缓存雪崩大量key集中过期用过期时间随机化、多级缓存Redis为什么不用多线程单线程能避免并发问题瓶颈在网络IO和内存不在CPURedis哨兵模式是什么监控主从节点自动故障转移客户端通过哨兵获取主节点地址Redis Cluster是什么数据分片到多个节点槽位16384每个节点负责一段槽Redis分布式锁怎么实现SETNX加过期时间释放时用Lua脚本校验持有者这张表基本上覆盖了基础篇的核心考点。你可以把每个问题当成一个话题先自己讲一遍讲不顺的地方再回头翻对应的章节。面试的本质是考察你是否真的理解而不是死记硬背所以每个问题都试着问自己一个为什么。Redis在7.0版本之后引入了一些新特性但这些属于进阶内容。基础篇先把核心概念和数据类型的家底打好后面学习高可用架构、集群、Lua脚本、Redisson框架等内容时会顺利很多。9. 学习Redis过程常见的坑与学习路径建议我见过太多初学者在学习Redis时走弯路这里分享几个常见的坑也是我自己的真实经历。第一个坑是Windows环境下的路径和空格问题。Windows移植版Redis对中文路径和带空格的目录兼容性不好如果你把解压目录放在C:\Program Files下面运行redis-server.exe时可能报错。解决办法是放到一个纯英文、无空格的目录比如D:\Redis。第二个坑是把生产环境的Redis暴露在公网上。Redis默认没有密码保护bind默认监听所有网卡时局域网甚至公网都能扫到。网上有很多针对Redis未授权访问的漏洞利用工具你在服务器上开着Redis却不设密码等于给攻击者大开方便之门。本地开发无所谓但只要是测试环境以上必须设置requirepass并限制bind地址。第三个坑是使用KEYS命令。很多人刚学会Redis就习惯在命令行输KEYS *看所有key我在前面的章节已经提过生产环境千万不能这么做。当Redis里有数百万个key时KEYS *会让Redis阻塞几秒甚至更久期间所有其他请求都会被卡住。正确做法是用SCAN命令或者直接在可视化工具里浏览。第四个坑是缓存和数据库的一致性没有考虑。如果业务是更新数据库后立刻更新缓存在高并发下会出现数据库和缓存短暂不一致的情况。更合理的做法通常是删除缓存而不是更新缓存让下一次查询时把最新数据加载进缓存这样能减少不必要的缓存更新次数。当然这里也有更复杂的延迟双删策略但基础篇先记住更新数据库后删缓存这个大方向。学习路径上我建议按这个顺序来先熟练使用redis-cli的命令行操作把五种基本数据结构的命令都敲一遍然后掌握持久化和内存淘汰机制再学会在Spring Boot里集成Redis写几个实际的缓存Demo接着理解主从、哨兵、集群的部署方式最后再攻分布式锁、Lua脚本、Redisson这些进阶内容。每一步都动手敲一敲光看不练效果很差。10. 结尾我的实际体会和给你的建议根据我自己的学习经历Redis这门技术最大的特点是没有深不见底的概念复杂度命令就那么多数据结构也好理解真正的难点在于你在实际项目中能不能针对场景选出合适的数据结构和方案。工具用一次记不住用十次就熟了我建议你把今天的部署环境搭好用一个实际的小项目把字符串缓存、Hash存储对象、ZSet排行榜都跑一遍比看十篇文章都管用。还有一点想强调Redis的学习一定要重视官方文档。很多网上教程写的命令参数不一定准确版本差异也会导致行为不一致。遇到不清楚的地方翻一下redis.io/documentation这个官方页面或者直接看本机Redis的版本号和配置文件注释反而比到处搜博客更高效。最后分享一个小技巧学Redis时给自己配一个可视化工具连上你本地的Redis实例随手写点数据观察不同类型的数据在工具中如何展示会让你对Redis的存储模型有非常直观的感受。多用、多看、多想Redis基础篇这一关就算真正过了。
返回列表