Redis是一个开源的内存键值数据库,因为读写速度极快、支持多种数据结构,几乎成了后端系统的标配组件。但不少人只是照着教程装好、能存能取就以为掌握了Redis,真正上了生产环境才发现问题一堆:数据莫名丢失、某个key拖垮整个实例、缓存一失效数据库瞬间被打爆。这篇文章就把Redis是什么、怎么用、哪些坑不能踩,系统地讲一遍。

Redis到底是什么,和MySQL有什么区别
Redis的全称是REmote DIctionary Server,本质上是把数据放在内存中进行读写的一个键值存储系统。内存的随机读写速度比磁盘快几个数量级,Redis官方给出的基准性能是单机每秒可以处理10万次以上的读写请求,这是传统关系型数据库很难达到的水平。
但Redis和MySQL并不是替代关系,而是互补关系。MySQL把数据持久化在磁盘上,保证数据不丢失,适合作为主数据存储;Redis把数据放在内存里,速度快但容量受内存限制,适合做缓存、计数器、排行榜这类对性能要求高的场景。一个典型的架构是:请求先查Redis,命中就直接返回,未命中再查MySQL,查完写入Redis并设置过期时间。
Redis还有一个重要特性是单线程处理命令(网络IO在较新版本中已多线程化,但命令执行仍是单线程)。这意味着所有命令串行执行,天然避免了锁竞争,同时也意味着一个耗时命令会阻塞所有请求。理解这一点对后面避坑非常关键。
五种常用数据类型及适用场景
Redis不只是简单的key-value存储,它提供了丰富的数据结构,选对数据类型是用好Redis的第一步。
String(字符串)是最基础的类型,可以存字符串、数字甚至序列化后的JSON对象。适合做缓存对象、计数器、分布式锁等。常用命令如下:
SET user:1001 '{"name":"张三","age":25}' # 存储JSON对象
GET user:1001 # 读取
INCR article:5001:views # 阅读量自增1
SETNX lock:order:2001 1 EX 30 # 设置带过期时间的锁
Hash(哈希)适合存储对象的多个字段,比如用户资料。相比把整个对象序列化成String,Hash可以只修改某个字段而不需要读出整个对象再写回去,省去了序列化开销。
HSET user:1001 name "李四" age 30 city "北京" HGET user:1001 name HGETALL user:1001 HINCRBY user:1001 age 1 # 年龄字段单独加1
List(列表)是一个双向链表,可以从两端push和pop,适合做消息队列、最新动态列表、时间轴等场景。Set(集合)是无序且不重复的,适合做标签、去重、共同好友这类需要交并差运算的场景,比如用SINTER求两个用户共同关注的人。ZSet(有序集合)给每个成员附加一个分数并按分数排序,是做排行榜的最佳选择。
ZADD rank:game 95 "玩家A" 88 "玩家B" 99 "玩家C" ZREVRANGE rank:game 0 9 WITHSCORES # 取分数最高的前10名 ZINCRBY rank:game 5 "玩家B" # 玩家B加分
持久化机制:RDB和AOF怎么选
很多人以为Redis重启数据就会全部丢失,其实Redis提供了两种持久化方案。RDB是定期把内存数据全量快照写入磁盘,文件紧凑、恢复速度快,但两次快照之间的数据在宕机时会丢失。AOF是把每条写命令追加到日志文件,配合everysec刷盘策略最多丢一秒数据,安全性更高但文件更大、恢复更慢。
生产环境通常两者同时开启:用AOF保证数据尽量少丢,用RDB做备份和快速恢复。Redis 4.0之后还支持混合持久化(aof-use-rdb-preamble),AOF重写时把全量数据以RDB格式写入,之后追加增量命令,兼顾了恢复速度和数据安全性,建议开启。
# redis.conf 关键配置 save 900 1 # 900秒内至少1次修改则触发RDB appendonly yes # 开启AOF appendfsync everysec # 每秒刷盘一次 aof-use-rdb-preamble yes
需要强调的是,持久化只能降低宕机丢数据的风险,并不能让Redis替代数据库。持久化恢复也有时间成本,内存越大恢复越慢,这也是不要往Redis里塞太多冷数据的原因之一。
新手最容易踩的几个坑
第一坑:把Redis当数据库用。Redis重启、主从切换、内存满了触发淘汰策略时,数据都可能丢失。所有关键业务数据必须落库,Redis只做缓存或加速层。写代码时要考虑缓存未命中的兜底逻辑,而不是假设数据一定在。
第二坑:大key问题。一个String存了几十MB的值,或者一个List积压了几百万个元素,读写这样的key会造成主线程长时间阻塞。规范做法是拆分key,比如大List按固定长度分段存储;删除大key用UNLINK代替DEL,让删除在后台线程执行;上线前用redis-cli的bigkeys参数做扫描检查。
第三坑:缓存穿透和缓存雪崩。穿透是查询一个根本不存在的数据,每次都打到数据库,攻击者可以利用这点打垮数据库,解决方案是对空结果也做短时缓存,或者在接口层做参数校验、使用布隆过滤器。雪崩是大量key同一时间过期,请求全部涌向数据库,解决方案是给过期时间加随机偏移,比如基础时间加上0到300秒的随机数,让过期点分散开。还有一个变种是缓存击穿,热点key过期的瞬间大量请求并发打到数据库,可以用互斥锁保证只有一个请求去回源,其余请求短暂等待。
第四坑:滥用KEYS命令。KEYS会遍历所有key,key数量大时直接阻塞服务。线上需要模糊查询时改用SCAN命令游标式迭代,虽然可能返回重复key,但不会阻塞主线程。
KEYS user:* # 线上禁用,key多时会卡死实例 SCAN 0 MATCH user:* COUNT 100 # 用SCAN代替,分批迭代
第五坑:不设置过期时间。很多新手只写不删,内存慢慢被占满,最后触发maxmemory淘汰策略甚至OOM。写入缓存时应显式设置TTL,同时结合maxmemory-policy配置合理的淘汰策略,缓存场景推荐allkeys-lru。
生产环境还需要知道的高可用方案
单机Redis再快也有两个短板:容量上限和单点故障。解决思路是主从复制加哨兵或集群。主从复制是一台master多台slave,写操作走master,读操作可以分摊到slave,既扩了读能力又有了数据副本。哨兵(Sentinel)负责监控master健康状态,master挂掉后自动把某个slave提升为新的master,实现故障自动转移。
当数据量大到单机内存放不下,或者写入量单机扛不住时,就要上Redis Cluster。集群把key按哈希槽分片到多个节点(一共16384个槽),实现水平扩展。需要注意集群模式下多key操作要求key在同一个槽,跨槽操作会报错,通常用hash tag(key中用大括号包住相同部分)来解决,例如user:{1001}:profile和user:{1001}:orders会被分配到同一个槽。
对于大多数中小系统来说,一主一从加哨兵已经足够稳定;真正需要Cluster时再上,不要为了技术炫技增加运维复杂度。
总结
Redis的核心价值在于用内存换取速度,用丰富的数据结构解决多样化的业务问题。掌握它的正确姿势是:明确它只是缓存和加速层而非数据库;根据业务选对数据类型;开启合理的持久化配置;警惕大key、KEYS命令、不设过期时间这些常见坑;数据量上来了再考虑主从、哨兵和集群。把这些要点记住,Redis用起来基本不会出大问题。