不知道你有没有过这种经历手头的接口请求越来越慢数据库的CPU忽高忽低日志里时不时冒出一句“连接超时”几台机器都在排队等同一批数据。身边的老同事不慌不忙地敲了几行命令加了几个参数系统就像被按了加速键。你问他们做了什么十有八九答案是同一个词Redis。Redis这个项目我已经断断续续用了快十年。从最开始只在缓存层见到的陌生人到后来面试时绕不开的必考题再到现在日常架构里几乎默认存在的组件它的地位确实上升得很快。如果你刚接触它会觉得它不过是一个“键值对存储”听起来没什么了不起。但真把它用对了它几乎能在每一个“数据访问太慢”的场景里救场。这篇文章就是系列的第一篇目标很单纯不带任何基础像认识新同事一样搞清楚Redis是谁、解决什么问题、怎么把它跑起来、它的核心技能是什么以及我们为什么需要它。这篇适合完全没有接触过Redis的读者也适合那些已经装过Redis、但一直停留在set/get阶段的朋友。看完之后你至少能做到在自己的机器上把Redis跑起来理解它到底擅长什么并且能给同事讲清楚“为什么大家都说Redis快”。1. 它不是普通的“缓存数据库”而是一个数据结构服务器1.1 先搞清楚Redis给自己下的定义刚开始接触Redis的时候我犯过一个典型的错误把它当成MySQL的替代品。不少人一听“内存数据库”第一反应就是“把数据放到内存里读写就快了”。这句话不能算错但它掩盖了真正重要的设计理念。Redis官方文档里一直用一种很执拗的方式称呼自己它不是“database”也不是“cache”而是“data structure server”数据结构服务器。它的核心价值不在于“内存快”而在于它把一组常用数据结构字符串、哈希、列表、集合、有序集合做成了可以被网络访问的服务。你在写代码时用的String、Map、List、Set在Redis里都有对应的原生方案并且支持原子操作。这一点和MySQL完全不同。MySQL是关系模型建表、写SQL、做关联查询数据是“被动的”需要你自己设计查询方式。Redis里每个数据结构的增删改查都是自带的比如LPUSH、SADD、ZRANGEBYSCORE你不需要先设计表不需要写一条复杂的SQL直接向它的“容器”发命令就行。所以如果你问“Redis到底是什么”我的回答是一个通过网络访问、基于内存运行、自带数据结构封装的远程操作服务。你可以把它当作一个更聪明、更快的SharedMemory也可以把它当作一个能跨进程访问的高级集合类库。1.2 为什么硬盘型数据库会让系统变慢想理解Redis存在的价值必须从“慢”说起。MySQL这类数据库的数据通常放在磁盘里或者大部分在磁盘里。磁盘的顺序读可能还行但随机小数据量的访问一次需要经历的步骤非常多操作系统寻址、磁盘旋转/寻道、控制器响应、页缓存命中与否。即便加了索引访问一个索引节点也要走几次磁盘I/O。内存则完全是另一个量级。内存的随机访问耗时通常在几十纳秒到上百纳秒大约是机械硬盘的十万分之一哪怕和固态硬盘比也有几个数量级的优势。一个问题性能瓶颈一旦到了数据库层面最常见的解药就是“把数据搬到更近、更快的地方”。Redis就是那个“更快的地方”。它把热数据直接放在内存里再通过高效的网络模型对外提供服务。你在应用里调用一次Redis可能往返只需要不到一毫秒同样一次请求落到MySQL少说也要几毫秒如果涉及多表联查或者大量扫描上百毫秒都很正常。1.3 一个数据请求在Redis里经历了什么用一句话描述Redis的工作流程客户端把命令比如GET user:1001发到6379端口Redis服务端从事件循环里读取请求解析成命令对象操作内部的数据结构再把结果写回网络连接。这个过程里Redis有几个设计上的“性格”所有命令是串行执行的同一时刻只有一条命令在真正操作数据。这让它天然没有并发写冲突的烦恼也不需要加锁。数据都存在内存中不需要像数据库那样维护复杂的磁盘索引结构命令的“路径”非常短。网络部分使用IO多路复用单个线程就能管理成千上万个客户端连接。你可以把Redis理解成一个非常专注的人只有一个大脑一次只想一件事但它反应足够快处理完一件马上处理下一件所以整体的吞吐量依然惊人。这种“单线程但快”的特点我后面专门讲。2. 十分钟把Redis跑起来开发机上的最快上手路线2.1 为什么先跑起来比先啃文档重要Redis入门最容易卡住的不是命令而是“我怎么把它弄到机器上”。很多教程一上来就写一大堆编译参数、配置项结果读者连服务都没启动过。我的建议是先别管那些高级配置用最快的方式把服务启动把ping/pong打通亲眼看到数据放进去再读出来你自然就有继续往下学的感觉了。时间有限的情况下我只推荐三种启动方式Docker、二进制解压、包管理器直接安装。2.2 方式一Docker最推荐几秒钟起服务如果你机器上已经有Docker这是最干净的方式。执行docker run -d --name redis-demo -p 6379:6379 redis:7-alpine解释一下这条命令做了什么docker run -d后台运行容器。--name redis-demo给容器起个名字方便后续操作。-p 6379:6379宿主机的6379端口映射到容器的6379端口。Redis默认监听6379应用代码可以通过本机6379访问。redis:7-alpine基于轻量Alpine系统的Redis 7镜像体积小启动快。启动完成后验证docker ps看到redis-demo容器在运行后再打开一个终端用redis-cli连接docker exec -it redis-demo redis-cli ping如果返回PONG恭喜你Redis已经活了。把ping改成redis-cli set hello world redis-cli get hello你会看到set返回OKget返回world。这个过程中的每一步都是可以直观感受到的比看十页文档都管用。2.3 方式二直接用系统包管理器不方便用Docker的时候Linux发行版基本都能直接安装。在Debian/Ubuntu上sudo apt update sudo apt install redis-server sudo systemctl start redis-server sudo systemctl enable redis-server在CentOS/RHEL上sudo yum install redis sudo systemctl start redismacOS用户如果装了Homebrewbrew install redis redis-server这样Redis会在前台启动日志直接刷在当前终端。这种方式的优点是省事缺点是有些发行版的Redis版本可能偏旧。比如某些系统的Redis还停留在6.2而官方现在已经在推7.x。版本差距在入门阶段几乎感受不到但如果你想体验最新的功能建议还是用Docker镜像或源码编译。2.4 方式三源码编译稍微抵抗一下源码编译是排查问题的基础功但入门阶段不是必须的。如果你确实想试试在Linux上需要先装gcc和make然后wget http://download.redis.io/releases/redis-7.0.11.tar.gz tar xzf redis-7.0.11.tar.gz cd redis-7.0.11 make make test make installmake install会把redis-server和redis-cli装到/usr/local/bin。源码编译最有价值的地方在于你能看到Redis整个目录结构src里是C源码redis.conf里是全部配置项。真到排错的时候你会感谢自己当初没有只靠Docker。2.5 跑起来之后重点看三个指标服务起来不等于万事大吉。我每次在新环境里启动Redis固定会执行三条命令redis-cli info memory redis-cli info stats redis-cli dbsizeinfo是Redis自带的“体检报告”。info memory里我会关注used_memory、maxmemory、mem_fragmentation_ratioinfo stats里我会关注total_connections_received、commands_processeddbsize则告诉我当前库里有多少个键。这些数字在入门阶段看懂含义即可不用记住单位used_memory当前用了多少内存。maxmemory允许使用的最大内存默认是0表示不限制生产环境必须手动设。mem_fragmentation_ratio内存碎片率太大说明内存碎片严重崩溃重启前建议调整内存分配器。commands_processed累计处理了多少命令可以用于估算吞吐量。我见过不少人启动完Redis就急着写业务代码从不看这些基础指标结果线上内存被打满OOM之后才回头查。越早养成“启动后先体检”的习惯后面越省心。3. Redis的五种基础数据结构每一个都对应一类实际问题3.1 String不只是字符串还是计数器、对象状态和素材缓存String是Redis里最基础的类型但它被严重低估。很多人只拿它存一段文本其实它能做的事情很多SET login_count 100 INCR login_count # 返回101 DECR login_count # 返回100 INCRBY login_count 50 # 返回150INCR这类原子自增命令是我用得最密集的。无论是统计用户访问次数、生成订单号、记录点赞数还是做简单的限流窗口INCR配合EXPIRE都能在一条命令里完成“计数过期”两个动作。如果用数据库做同样的事往往需要写一个悲观锁或乐观锁接口延迟和开发成本都会明显变高。String还可以直接存JSON序列化后的对象。比如用户信息SET user:1001 {name:A同学,level:5} EX 3600EX 3600的意思是3600秒后自动过期。这个特性做缓存特别顺手数据不必一直占内存。3.2 Hash初学者的第一反应是Map但它更适合存对象Hash类型在Redis里是键下面再挂字段和值。它天然适合模拟一个对象。HSET user:1001 name A同学 level 5 score 88 HGET user:1001 name HGETALL user:1001HSET允许一次设置多个字段HGET只取某一个字段HGETALL取全部。如果你把用户对象直接序列化成JSON塞进String每次更新level字段都要读出整个JSON、反序列化、改字段、再序列化、写回。而用Hash只更新一个字段的命令就是一条HSET。Hash特别适合“热点对象频繁改其中一个属性”的场景比如游戏里玩家的血量、等级、位置。每次只用一行命令修改一个字段比整体覆盖JSON高效得多。3.3 List自带时间轴的队列容器List是一个有序的字符串列表。它最常用的两个入口方向从左边推、从右边推对应着不同的业务模型。LPUSH task_queue job1 RPUSH task_queue job2 LRANGE task_queue 0 -1 LPOP task_queue简单说LPUSH加在头部RPUSH加在尾部LPOP从头部取RPOP从尾部取。当生产者用LPUSH写、消费者用RPOP等就是一个最简版本的队列模型。注意它用起来很像数据结构里的双端队列但生产环境中不建议直接用List做需要可靠消息投递的任务队列因为LPOP取走某个元素之后如果消费者崩了消息就丢了。要在Redis上做更可靠的队列后面可以了解Stream类型那是专门为此设计的。3.4 Set没有重复、没有顺序却解决了一堆判断问题Set是无序去重的字符串集合。对Set最常见的操作是求交集、并集、差集。SADD online_users A同学 SADD online_users B同学 SADD online_users A同学 # 返回0因为已存在 SMEMBERS online_users SINTER user:1001:friends user:1002:friendsSINTER求两个集合的交集可以用来做“共同好友”这个计算对Redis来说非常快。另外SPOP可以随机取出元素且自动删除常用于抽奖、随机推荐这类业务。判断元素是否存在用SISMEMBER一条命令就能给出结果。3.5 ZSet有序集合排行榜和延迟队列的“外挂”ZSet是最能体现Redis设计水平的一种结构。它在Set的基础上给每个元素加了一个分数score所有成员按分数从小到大排序。用它做排行榜只需要几条命令ZADD leaderboard 95 A同学 ZADD leaderboard 88 B同学 ZADD leaderboard 99 C同学 ZRANGE leaderboard 0 -1 WITHSCORES ZREVRANGE leaderboard 0 -1 WITHSCORESZREVRANGE按分数从高到低取数据这就是排行榜的天然口径。还有ZINCRBY可以在某一成员原分数基础上增加指定数值比如答对一题加5分一句命令搞定。除了排行榜ZSet还能做延迟队列把任务执行时间戳当成score每次轮询用ZRANGEBYSCORE取小于当前时间戳的任务。Redis的ZSet底层结构我在后面讲“为什么快”时再展开这里你先记住一个结论它对排序类操作的处理效率远高于普通数据库裸查。下面这张表总结了五种结构的记忆点类型一句话记法典型场景String一个键一个值可计数缓存、计数器、限流Hash键下面是多个字段存储对象的部分字段List有序列表两端推入弹出简单队列、最新列表Set无序、去重、集合运算标签、共同好友、抽奖ZSet去重加上加权排序排行榜、延迟队列4. 为什么Redis能那么快内存、单线程与IO多路复用背后的思路4.1 内存速度带来的“下限”Redis快最直接的原因是它压根不碰磁盘。你写一条命令数据就放在内存里不需要经过磁盘文件系统不需要等事务日志落盘所有的写操作几乎瞬间完成。这里有个关键点Redis的“快”是有代价的因为内存是易失的。所有数据默认只在内存中存在一旦宕机或重启没有持久化的话数据就没了。所以使用Redis的第一堂课其实是快很重要但快的前提是你要接受它的存储边界。4.2 单线程不等于慢专业和专注反而减少了开销Redis主线程是单线程的。我会先跟你解释一下“为什么单线程还能撑起高并发”绝大多数人理解的“高并发”是指同时处理很多请求。但单线程的Redis不是同时处理很多请求而是在一个极短的时间片里快速轮流处理很多请求。一个请求的耗时往往不到毫秒CPU在这些请求之间的切换成本极低因为根本没有线程上下文切换的负担。多线程服务最头疼的其实是并发控制两个线程同时往同一个数据上写怎么办需要加锁加锁之后线程又要等待。而Redis把这一切绕过去了——它只有一个执行者天然不存在写冲突、不存在死锁、不需要竞态条件保护。对很多内存数据操作来说单线程反而比多线程加锁更快、更可预测。Redis的命令执行的确是单线程但它的网络I/O部分用了多路复用。网络连接等待数据到达时一个进程同时监听很多客户端socket哪个连接有数据就处理哪个。这种方式和“为每个客户端开一个线程”完全不同线程多了反而会因为上下文切换和锁等待拖慢速度。4.3 每种数据结构的底层都不是简单的数组Redis快第三层原因是它在数据结构上花了很多心思。比如String底层会根据长度选择不同编码Hash在字段少的时候用紧凑的ziplist字段多了才转为标准哈希表ZSet的底层组合了跳表skip list和哈希表让排序、按分数区间查询、按键查成员都能在接近对数级的复杂度内完成。这些都说明Redis的“快”不是单一原因而是“内存单线程高效网络模型优化数据结构”四件事叠加的结果。任何一个环节做得不好它都不可能成为今天的样子。等你使用一段时间你可能会听到有人问“Redis单线程为什么线上把CPU打满了”很多时候CPU被打满不是因为Redis本身执行慢而是因为大键操作比如KEYS命令扫描所有键、复杂命令如SORT大集合、或者误用了需要全量遍历的命令。这是后话但提前知道有个概念会少踩很多坑。5. 数据放在内存里丢了怎么办持久化的两种基本思路5.1 RDB快照把某一瞬间的内存状态存到磁盘Redis默认开启了RDB持久化它会在指定时间点将全部数据dump成一个二进制文件默认叫dump.rdb。你可以想象成“拍了一张照片”整台服务瞬间保存下来恢复时直接加载照片。RDB的优点文件紧凑、恢复速度快、适合做全量备份。缺点两次快照之间写入的数据如果服务宕机这部分就丢了。比如我配置了“每5分钟至少1000次写入才保存一次快照”恰好第4分钟时宕机最近1分钟的数据就没了。5.2 AOF日志把每一次写操作都记录下来AOF持久化会把每条写命令追加到日志文件里。恢复时重新执行一遍日志里的命令机器就能回到事故前的状态。根据appendfsync配置可以是每个命令都fsync落盘、每秒落盘一次、或者交给操作系统决定always最安全每条写命令都同步到磁盘性能明显下降。everysec均衡选择每秒落盘一次宕机最多丢一秒数据。no最快由操作系统自行flush最不安全。推荐入门阶段先了解everysec它能满足绝大多数业务场景的可靠性要求。生产环境通常RDB和AOF一起开启RDB用于快速恢复冷数据AOF用于保障最近一秒的数据不丢。我想特别强调Redis的持久化解决的是“数据恢复”问题不是“数据库备份”问题。如果你的业务数据非常重要仍然需要定期把RDB文件或者通过主从复制来的副本备份到独立的地方单靠Redis自身的持久化机制并不够。5.3 生产环境还需要一份“防止雪崩”的预案回想起一次线上事故我记得很清楚某个项目没有给Redis设置maxmemory缓存数据不断增长最终内存被打满进程直接被操作系统杀掉。重启后Redis开始RDB恢复大量key同时涌入后端数据库瞬间被读请求击穿整个链路晾了十几分钟。事后复盘至少有四件事没做到位容器内存限制、maxmemory设置、持久化策略、兜底降级逻辑。这里也给刚入门的朋友一个最直接的提醒你在本机测试时可以不理会内存上限但任何要上线的Redis都必须设置maxmemory和合适的淘汰策略。比如CONFIG SET maxmemory 512mb CONFIG SET maxmemory-policy allkeys-lruallkeys-lru表示当内存达到上限时优先淘汰最少使用的键。这个设置保证了Redis在缓存场景下永远有空间可用不会因为缓存写满而直接崩溃。6. 初学者最容易踩的坑以及我踩过之后的经验6.1 把Redis当成超高性能的主存储而不考虑它的限制初学的时候很容易觉得Redis这么强干脆把所有数据都放进去算了。后果就是内存越用越多、持久化文件越来越大、故障恢复越来越慢而且一旦宕机连基础功能都不可用。经验是Redis承担“热数据”和“结构简单”的任务更合适。需要事务、复杂查询、报表交给它就不划算。这在架构决策时是非常关键的一项原则。6.2 忘记设置过期时间内存越积越多缓存最典型的用法就是“热点数据过期时间”。Redis支持EXPIRE、EX、SETEX等方案。但新手很容易写一遍SET key value之后完事完全没提过期时间。这个键将永远存在直到你手工删除或内存满触发淘汰策略。我见过一个项目因为没有设过期时间好几个无用的Session key活活把数据集撑大了好几个GB。所以当你在设计一个键的用途时先问自己这个数据有有效期吗如果有请务必在写入时一起设定。6.3 一次性取大量数据导致Redis卡顿Redis只管执行命令不会主动帮你筛选。如果执行KEYS *它会扫描整个键空间数据量大的时候会阻塞单线程导致其他命令排队。生产环境禁用KEYS需要用SCAN族命令做分批遍历。对这个问题意识薄弱的人往往都是在线上吃过亏才记住。List和Hash在使用时也有类似的注意事项如果列表或哈希里有成千上万个元素一次LRANGE 0 -1或HGETALL会带来明显开销。这也是我常提的“大Key”问题。任何结构都应避免单个键维护过多元素。6.4 完整可执行的学会路线学到这里你已经比很多只会用set/get的人强了不少。接下来怎么走我建议按这个顺序先把五种数据结构的命令刷一遍可以用redis-cli写成脚本批量练习然后看Redis的官方文档着重理解过期策略、淘汰策略和持久化配置再去了解主从复制、哨兵和集群模式。每一层都建立在你已经跑通一遍环境的基础上。我在很多技术交流里都会提到Redis上手不难难的是在真实场景里做出合理解释。比如为什么会丢数据、为什么一段命令会很慢、为什么要限制内存而不直接无穷大。这个系列接下来也会按“初识Redis”到“配置进阶”再到“常见问题排查”的顺序一步步展开。建议你自己动手把上面三种启动方式都试一遍然后随便写一个小项目用Redis做缓存和计数器跑上一两天感受一下。我自己在使用Redis时最大的体会是它是一个很“诚实”的工具设计思路清晰性能特性稳定但它的边界也很清楚——给对场景用它是利器把不适合的东西塞给它它会让整个架构变得很别扭。希望这篇入门能帮你跨过第一道坎体会到这种“诚实工具”的顺手之处。