Redis,是目前世界上,最流行、最强大的内存数据库和缓存系统。

对于程序员来说,Redis,已经成为了必备技能。无论是缓存加速,还是会话存储,还是消息队列,还是排行榜,还是分布式锁,Redis,都能帮助我们,更高效地完成工作。可以说,不会用Redis的程序员,在2015年,已经开始落伍了。

我自己,从2013年开始接触Redis,到现在,已经用了两年多了。从最初的,觉得Redis很神奇,到后来,能够熟练使用Redis,做缓存、做队列、做排行榜、做分布式锁,一路走来,踩了不少坑,也总结了不少经验。我见过太多的开发者,因为不会用缓存,而导致网站性能差,数据库压力大;因为不会用Redis,而导致各种功能,实现起来,非常复杂。其实,只要用了Redis,这些问题,大部分都能解决。

2015年,Redis,已经成为了,内存数据库的绝对主流。Redis 3.0,在2015年4月正式发布,支持了集群(Cluster)功能,这是Redis发展史上的一个重要里程碑。Redis,被越来越多的公司,用于生产环境,包括,Twitter、GitHub、Stack Overflow、阿里巴巴、腾讯、百度等。Redis,已经从一个,小众的技术,变成了,主流的技术。

今天,分享Redis入门与实战,从什么是Redis,到Redis的安装和使用,到Redis的数据结构,到Redis的持久化,到Redis的集群,到Redis的最佳实践,全方位讲解Redis的使用方法和技巧,帮你掌握这个强大的内存数据库。

什么是Redis

Redis(Remote Dictionary Server),是一个开源的、基于内存的、高性能的键值对(key-value)数据库。

Redis,由意大利人Salvatore Sanfilippo(网名antirez)在2009年开发。最初,是为了解决,他自己的一个网站的性能问题,而开发的。后来,开源出来,受到了,广泛的欢迎。

Redis,和传统的关系型数据库(如MySQL、PostgreSQL),有很大的不同:

  1. 基于内存:Redis的数据,存储在内存中,读写速度,非常快。官方数据,Redis的读速度,可以达到11万次/秒,写速度,可以达到8万次/秒。这,是传统的磁盘数据库,无法比拟的。
  1. 键值对存储:Redis,是键值对数据库,数据,以key-value的形式存储。但和Memcached等简单的键值对数据库不同,Redis的value,支持多种数据结构,包括,字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。
  1. 支持持久化:虽然Redis的数据,存储在内存中,但它,支持持久化,可以把数据,保存到磁盘上,重启后,可以恢复数据。Redis,支持两种持久化方式:RDB(快照)和AOF(追加文件)。
  1. 支持多种语言:Redis,支持几乎所有的主流编程语言,包括,PHP、Python、Java、C/C++、C#、JavaScript、Ruby、Go等。每种语言,都有成熟的客户端库。
  1. 功能丰富:Redis,不仅,是一个缓存,还可以,用作消息队列、发布订阅、排行榜、计数器、分布式锁、会话存储等。功能,非常丰富。
  1. 支持集群:Redis 3.0以后,支持集群(Cluster),可以,横向扩展,处理更大的数据量和更高的并发。

Redis的安装

1. Linux安装

在Linux上安装Redis,非常简单。以Ubuntu为例:

# 下载Redis
wget http://download.redis.io/releases/redis-3.0.5.tar.gz

# 解压
tar xzf redis-3.0.5.tar.gz
cd redis-3.0.5

# 编译
make

# 安装
sudo make install

# 验证安装
redis-server --version
redis-cli --version

也可以用apt-get安装(但版本可能比较旧):

sudo apt-get update
sudo apt-get install -y redis-server

2. 启动Redis

# 启动Redis服务器(前台运行)
redis-server

# 启动Redis服务器(后台运行)
redis-server --daemonize yes

# 使用配置文件启动
redis-server /etc/redis/redis.conf

# 连接Redis
redis-cli
redis-cli -h 127.0.0.1 -p 6379

# 测试连接
redis-cli ping
# 返回 PONG,表示连接成功

3. Windows安装

Redis,官方不支持Windows,但微软的开源团队,维护了一个Windows版本的Redis。

下载地址:https://github.com/MSOpenTech/redis/releases

下载后,解压,运行redis-server.exe,即可启动Redis。运行redis-cli.exe,可以连接Redis。

注意:Windows版本的Redis,主要用于开发和测试,不建议在生产环境使用。生产环境,建议使用Linux。

4. PHP安装Redis扩展

在PHP中使用Redis,需要安装Redis扩展。

# 下载phpredis
git clone https://github.com/phpredis/phpredis.git
cd phpredis

# 编译安装
phpize
./configure
make && make install

# 在php.ini中添加扩展
echo "extension=redis.so" >> /etc/php5/php.ini

# 重启PHP-FPM或Apache
sudo service php5-fpm restart

也可以用pecl安装:

sudo pecl install redis

安装完成后,用phpinfo()或php -m,查看redis扩展是否加载成功。

Redis数据结构

Redis,支持多种数据结构,这是Redis,最强大的地方。

1. 字符串(String)

字符串,是Redis,最基本的数据结构。一个key,对应一个value,value,可以是字符串、数字、二进制数据(如图片、序列化对象),最大不能超过512MB。

# 设置值
SET name "张三"
SET age 25

# 获取值
GET name
# 返回 "张三"
GET age
# 返回 "25"

# 追加内容
APPEND name "你好"
# name 变成 "张三你好"

# 获取字符串长度
STRLEN name

# 自增/自减(用于计数器)
INCR age
# age 变成 26
INCRBY age 5
# age 变成 31
DECR age
# age 变成 30
DECRBY age 5
# age 变成 25

# 设置过期时间(秒)
SETEX code 60 "123456"
# code 60秒后自动删除

# 只有key不存在时才设置(用于分布式锁)
SETNX lock "1"
# 如果lock不存在,设置成功,返回1;如果已存在,返回0

# 批量设置/获取
MSET name "张三" age 25 email "zhangsan@example.com"
MGET name age email

# 获取子字符串
GETRANGE name 0 1
# 返回 "张"

# 设置子字符串
SETRANGE name 2 "你好"

字符串,是最常用的数据结构,可以用来,缓存数据、存储会话、做计数器、做分布式锁等。

2. 哈希(Hash)

哈希,是一个键值对的集合,适合存储对象。一个key,对应一个哈希,哈希里面,有多个field-value对。

# 设置字段
HSET user:1 name "张三"
HSET user:1 age 25
HSET user:1 email "zhangsan@example.com"

# 获取字段
HGET user:1 name
# 返回 "张三"

# 获取所有字段和值
HGETALL user:1
# 返回 name, 张三, age, 25, email, zhangsan@example.com

# 获取所有字段名
HKEYS user:1

# 获取所有字段值
HVALS user:1

# 获取字段数量
HLEN user:1

# 检查字段是否存在
HEXISTS user:1 name
# 存在返回1,不存在返回0

# 删除字段
HDEL user:1 email

# 批量设置
HMSET user:1 name "张三" age 25 email "zhangsan@example.com"

# 批量获取
HMGET user:1 name age email

# 字段自增
HINCRBY user:1 age 1
# age 变成 26

哈希,非常适合存储对象,比如,用户信息、文章信息、商品信息等。相比,把对象序列化后,用字符串存储,哈希,可以单独修改某个字段,不需要,读取整个对象,再序列化回去,效率更高。

3. 列表(List)

列表,是一个有序的字符串列表,按照插入顺序排序。可以,在列表的头部(左边)或尾部(右边),添加元素。列表,最多可以包含,2^32-1个元素(约40亿个)。

# 从左边添加元素
LPUSH list "a"
LPUSH list "b"
# list 现在是 ["b", "a"]

# 从右边添加元素
RPUSH list "c"
# list 现在是 ["b", "a", "c"]

# 获取列表长度
LLEN list
# 返回 3

# 获取列表元素(按索引范围)
LRANGE list 0 -1
# 返回所有元素 ["b", "a", "c"]
LRANGE list 0 1
# 返回 ["b", "a"]

# 从左边弹出元素
LPOP list
# 返回 "b",list 现在是 ["a", "c"]

# 从右边弹出元素
RPOP list
# 返回 "c",list 现在是 ["a"]

# 获取指定索引的元素
LINDEX list 0
# 返回 "a"

# 设置指定索引的元素
LSET list 0 "x"
# list 现在是 ["x"]

# 在指定元素前/后插入元素
LINSERT list BEFORE "x" "y"
# list 现在是 ["y", "x"]

# 删除元素
LREM list 1 "x"
# 删除1个值为"x"的元素

# 修剪列表(只保留指定范围的元素)
LTRIM list 0 9
# 只保留前10个元素

# 阻塞式弹出(用于消息队列)
BLPOP list 30
# 从左边弹出,如果列表为空,阻塞等待30秒
BRPOP list 30
# 从右边弹出

列表,可以用来,做消息队列(生产者消费者模式)、最新列表(如最新文章、最新评论)、任务队列等。

4. 集合(Set)

集合,是一个无序的、不重复的字符串集合。集合中的元素,是唯一的,不会重复。集合,最多可以包含,2^32-1个元素。

# 添加元素
SADD set "a"
SADD set "b" "c"
# set 现在是 {"a", "b", "c"}

# 获取所有元素
SMEMBERS set
# 返回 ["a", "b", "c"](顺序不确定)

# 获取集合元素数量
SCARD set
# 返回 3

# 检查元素是否存在
SISMEMBER set "a"
# 存在返回1,不存在返回0

# 删除元素
SREM set "a"
# set 现在是 {"b", "c"}

# 随机弹出一个元素
SPOP set
# 随机返回并删除一个元素

# 随机获取一个元素(不删除)
SRANDMEMBER set

# 交集
SADD set1 "a" "b" "c"
SADD set2 "b" "c" "d"
SINTER set1 set2
# 返回 ["b", "c"]

# 并集
SUNION set1 set2
# 返回 ["a", "b", "c", "d"]

# 差集
SDIFF set1 set2
# 返回 ["a"](set1中有,set2中没有的)

# 交集并存储到新集合
SINTERSTORE set3 set1 set2
# set3 = {"b", "c"}

集合,可以用来,做标签系统、共同好友、抽奖(去重)、独立IP统计等。

5. 有序集合(Sorted Set)

有序集合,和集合类似,也是,不重复的字符串集合。但不同的是,有序集合的每个元素,都关联一个分数(score),元素,按照分数,从小到大排序。分数,可以重复。

# 添加元素
ZADD rank 100 "张三"
ZADD rank 90 "李四" 80 "王五"
# rank 现在按分数排序:王五(80), 李四(90), 张三(100)

# 获取元素数量
ZCARD rank
# 返回 3

# 获取元素的分数
ZSCORE rank "张三"
# 返回 100

# 获取元素的排名(按分数从小到大,从0开始)
ZRANK rank "张三"
# 返回 2

# 获取元素的排名(按分数从大到小)
ZREVRANK rank "张三"
# 返回 0

# 获取指定排名范围的元素(按分数从小到大)
ZRANGE rank 0 -1
# 返回 ["王五", "李四", "张三"]
ZRANGE rank 0 -1 WITHSCORES
# 返回元素和分数

# 获取指定排名范围的元素(按分数从大到小)
ZREVRANGE rank 0 -1
# 返回 ["张三", "李四", "王五"]

# 获取指定分数范围的元素
ZRANGEBYSCORE rank 80 90
# 返回 ["王五", "李四"](分数在80到90之间的)

# 增加元素的分数
ZINCRBY rank 5 "张三"
# 张三的分数变成105

# 删除元素
ZREM rank "张三"

# 获取指定分数范围的元素数量
ZCOUNT rank 80 100

# 删除指定排名范围的元素
ZREMRANGEBYRANK rank 0 1

# 删除指定分数范围的元素
ZREMRANGEBYSCORE rank 80 90

有序集合,是Redis,最强大的数据结构之一,可以用来,做排行榜(如游戏排行榜、文章热度排行榜)、延时队列(用时间戳作为分数)、范围查找等。

Redis常用命令

1. Key相关命令

# 查找所有符合模式的key
KEYS *
KEYS user:*
# 注意:KEYS命令会遍历所有key,生产环境慎用,会阻塞

# 检查key是否存在
EXISTS name
# 存在返回1,不存在返回0

# 删除key
DEL name
DEL name age

# 设置key的过期时间(秒)
EXPIRE name 60
# name 60秒后自动删除

# 设置key的过期时间(毫秒)
PEXPIRE name 60000

# 查看key的剩余过期时间(秒)
TTL name
# 返回剩余秒数,-1表示没有过期时间,-2表示key不存在

# 查看key的剩余过期时间(毫秒)
PTTL name

# 移除key的过期时间
PERSIST name

# 查看key的数据类型
TYPE name
# 返回 string, hash, list, set, zset 等

# 重命名key
RENAME oldname newname

# 随机返回一个key
RANDOMKEY

# 清空当前数据库的所有key
FLUSHDB
# 慎用!

# 清空所有数据库的所有key
FLUSHALL
# 慎用!

2. 数据库相关命令

Redis,默认有16个数据库(编号0-15),可以用SELECT命令,切换数据库。

# 切换数据库
SELECT 0
SELECT 1

# 查看当前数据库的key数量
DBSIZE

# 清空当前数据库
FLUSHDB

# 清空所有数据库
FLUSHALL

注意:Redis集群模式下,不支持多数据库,只能使用db 0。

3. 事务相关命令

Redis,支持事务,可以,把多个命令,打包成一个事务,原子性地执行。

# 开始事务
MULTI

# 事务中的命令
SET name "张三"
SET age 25

# 执行事务
EXEC
# 按顺序返回所有命令的结果

# 取消事务
DISCARD

注意:Redis的事务,不是,严格意义上的原子性。如果,事务中的某个命令,执行失败,其他命令,仍然会继续执行。Redis的事务,只能保证,事务中的命令,按顺序执行,不会被其他命令打断。

如果需要,严格的原子性,可以使用Lua脚本。

4. 发布订阅命令

Redis,支持发布订阅(Pub/Sub)模式,可以,用作简单的消息队列。

# 订阅频道
SUBSCRIBE channel1
# 订阅后,会一直监听,收到消息会打印

# 发布消息
PUBLISH channel1 "hello"
# 所有订阅了channel1的客户端,都会收到消息

# 取消订阅
UNSUBSCRIBE channel1

# 按模式订阅
PSUBSCRIBE channel.*
# 订阅所有以channel.开头的频道

# 按模式取消订阅
PUNSUBSCRIBE channel.*

5. 其他常用命令

# 查看服务器信息
INFO
INFO replication
INFO memory

# 查看慢查询
SLOWLOG GET 10

# 查看客户端连接
CLIENT LIST

# 关闭客户端连接
CLIENT KILL ip:port

# 保存数据到磁盘(RDB)
SAVE
# 阻塞式保存,会阻塞服务器

BGSAVE
# 后台保存,不阻塞服务器

# 查看最后一次保存时间
LASTSAVE

# 查看配置
CONFIG GET maxmemory
CONFIG GET *

# 设置配置
CONFIG SET maxmemory 1gb

# 重写配置文件
CONFIG REWRITE

# 查看大key(Redis 4.0+)
# redis-cli --bigkeys

Redis持久化

Redis,虽然是内存数据库,但支持持久化,可以把数据,保存到磁盘上,重启后,恢复数据。

Redis,支持两种持久化方式:RDB和AOF。

1. RDB(快照)

RDB,是Redis,默认的持久化方式。它,会在指定的时间间隔内,把内存中的数据,生成一个快照(snapshot),保存到磁盘上,文件名为dump.rdb。

RDB的触发方式:

  • 自动触发:根据配置文件中的save规则,自动触发。比如,save 900 1,表示900秒内,至少有1个key被修改,就自动触发BGSAVE。
  • 手动触发:执行SAVE或BGSAVE命令。
  • 退出时触发:执行SHUTDOWN命令时,会自动触发SAVE。

RDB的优点:

  • 文件紧凑,适合备份和恢复
  • 恢复速度快,直接加载RDB文件即可
  • 对性能影响小,使用子进程生成快照,不阻塞主进程

RDB的缺点:

  • 可能会丢失数据。如果,Redis在两次快照之间,崩溃了,那么,这期间修改的数据,就会丢失。
  • 数据量大时,生成快照,可能会占用较多CPU和内存资源。

2. AOF(追加文件)

AOF(Append Only File),是另一种持久化方式。它,会把Redis,执行的每一条写命令,都追加到AOF文件中。重启时,Redis,会重新执行AOF文件中的所有命令,恢复数据。

AOF的同步策略:

  • always:每执行一条写命令,就同步到磁盘。最安全,但性能最差。
  • everysec:每秒同步一次。默认策略。兼顾安全和性能。最多丢失1秒的数据。
  • no:由操作系统决定何时同步。性能最好,但最不安全,可能丢失较多数据。

AOF的优点:

  • 数据安全性高。使用everysec策略,最多丢失1秒的数据。
  • AOF文件,是文本格式,可读性好,可以手动修改。
  • 支持AOF重写,可以压缩AOF文件的大小。

AOF的缺点:

  • AOF文件,通常比RDB文件大。
  • 恢复速度,比RDB慢。
  • 某些情况下,AOF文件,可能会有bug,导致数据恢复不完整。

3. 如何选择

  • 如果,可以容忍少量数据丢失,追求性能和恢复速度,选择RDB。
  • 如果,要求数据安全性高,不能丢失数据,选择AOF。
  • 最好的方式,是同时开启RDB和AOF。Redis重启时,会优先加载AOF文件,恢复数据;AOF文件不存在时,才加载RDB文件。这样,既保证了数据安全,又有RDB作为备份。

配置示例(redis.conf):

# 开启RDB
save 900 1
save 300 10
save 60 10000

# 开启AOF
appendonly yes
appendfsync everysec

# RDB文件名
dbfilename dump.rdb

# AOF文件名
appendfilename "appendonly.aof"

# 数据文件目录
dir /var/lib/redis

Redis缓存最佳实践

1. 缓存策略

常见的缓存策略,有以下几种:

Cache-Aside(旁路缓存)

  • 读:先读缓存,缓存命中,直接返回;缓存未命中,读数据库,然后,写入缓存。
  • 写:先写数据库,然后,删除缓存。
  • 这是,最常用的缓存策略。

Read-Through(读穿透)

  • 读:先读缓存,缓存未命中,由缓存层,自动读数据库,然后,写入缓存。
  • 应用层,只和缓存层交互,不需要,关心数据库。

Write-Through(写穿透)

  • 写:先写缓存,由缓存层,自动同步写数据库。
  • 应用层,只和缓存层交互。

Write-Behind(异步写)

  • 写:先写缓存,然后,异步批量写数据库。
  • 性能好,但可能会丢失数据。

对于大多数Web应用,推荐使用Cache-Aside策略。

2. 缓存穿透

缓存穿透,是指,查询一个,数据库中不存在的数据,导致,每次请求,都要查询数据库,缓存,形同虚设。

解决方法:

  1. 缓存空值:如果,数据库中,查询不到数据,也缓存一个空值(如NULL或空字符串),设置较短的过期时间。这样,下次查询同样的key,就会直接返回空值,不会再查数据库。
  2. 布隆过滤器(Bloom Filter):在缓存层之前,加一个布隆过滤器。查询时,先查布隆过滤器,如果,布隆过滤器说,这个key不存在,就直接返回,不再查缓存和数据库。布隆过滤器,可以判断,一个元素,一定不存在,或可能存在。

3. 缓存击穿

缓存击穿,是指,一个热点key,在缓存过期的瞬间,有大量的并发请求,同时,查询数据库,导致,数据库压力骤增。

解决方法:

  1. 互斥锁:缓存失效时,不是,所有请求,都去查数据库,而是,用分布式锁,只让一个请求,去查数据库,其他请求,等待。查完数据库后,写入缓存,然后,其他请求,直接读缓存。
  2. 永不过期:热点key,不设置过期时间,而是,在value中,存储一个逻辑过期时间。后台,有一个定时任务,定期更新热点key的缓存。这样,缓存,永远不会"物理过期",也就不会有击穿的问题。

4. 缓存雪崩

缓存雪崩,是指,大量的key,在同一时间,集体过期,导致,大量的请求,同时,查询数据库,数据库压力骤增,甚至,宕机。

解决方法:

  1. 过期时间加随机值:在设置缓存过期时间时,在基础时间上,加一个随机值(如,基础时间1小时,随机0-300秒)。这样,大量的key,就不会,在同一时间,集体过期。
  2. 多级缓存:使用本地缓存(如Caffeine、Guava Cache)+ Redis缓存。本地缓存,作为一级缓存,Redis,作为二级缓存。即使Redis缓存雪崩,本地缓存,还能挡住一部分请求。
  3. 服务降级和熔断:当数据库压力过大时,启用服务降级,返回默认值或兜底数据,保护数据库。
  4. Redis高可用:使用Redis主从复制、哨兵、集群,保证Redis的高可用,避免,Redis宕机,导致缓存全部失效。

5. 缓存更新

缓存更新,有以下几种策略:

  1. 先更新数据库,再删除缓存:这是,最常用的策略。先更新数据库,然后,删除缓存。下次查询时,缓存未命中,会重新读数据库,写入缓存。
  2. 先删除缓存,再更新数据库:先删除缓存,再更新数据库。这种方式,有问题。如果,删除缓存后,更新数据库前,有一个请求,读缓存未命中,读数据库(旧数据),写入缓存。然后,数据库更新完成。这时,缓存中,是旧数据,数据库中,是新数据,数据不一致。
  3. 先更新数据库,再更新缓存:先更新数据库,再更新缓存。这种方式,也有问题。如果,两个请求,同时更新,可能会导致,缓存和数据库不一致。

推荐使用,第一种策略:先更新数据库,再删除缓存。这是,最安全、最常用的策略。

6. 缓存一致性

缓存和数据库,的数据一致性,是缓存系统,最核心的问题之一。

理论上,只要,使用缓存,就不可能,做到强一致性(除非,所有读写,都加锁,但性能太差)。我们,只能做到,最终一致性。

对于大多数Web应用,最终一致性,已经足够了。只要,缓存的过期时间,不要太长(如,不要超过1小时),数据不一致的时间,就不会太长。

如果,要求,更高的一致性,可以:

  • 使用更短的过期时间
  • 更新数据库后,主动删除缓存
  • 使用消息队列,异步更新缓存
  • 使用Canal等工具,监听MySQL的binlog,自动更新缓存

Redis在PHP中的使用

1. 连接Redis

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// $redis->auth('password'); // 如果有密码
// $redis->select(0); // 选择数据库

// 测试连接
echo $redis->ping(); // 返回 +PONG

2. 字符串操作

<?php
// 设置
$redis->set('name', '张三');
$redis->setex('code', 60, '123456'); // 带过期时间
$redis->setnx('lock', '1'); // 不存在才设置

// 获取
$name = $redis->get('name');

// 自增
$redis->incr('views');
$redis->incrBy('views', 10);

// 批量
$redis->mset(['name' => '张三', 'age' => 25]);
$data = $redis->mget(['name', 'age']);

3. 哈希操作

<?php
// 设置字段
$redis->hSet('user:1', 'name', '张三');
$redis->hMSet('user:1', ['age' => 25, 'email' => 'zhangsan@example.com']);

// 获取字段
$name = $redis->hGet('user:1', 'name');
$user = $redis->hGetAll('user:1');

// 检查字段
$exists = $redis->hExists('user:1', 'name');

// 删除字段
$redis->hDel('user:1', 'email');

4. 列表操作

<?php
// 添加
$redis->lPush('list', 'a', 'b'); // 左边
$redis->rPush('list', 'c', 'd'); // 右边

// 获取
$list = $redis->lRange('list', 0, -1); // 所有元素

// 弹出
$left = $redis->lPop('list'); // 左边弹出
$right = $redis->rPop('list'); // 右边弹出

// 阻塞弹出(消息队列)
$item = $redis->blPop('queue', 30); // 阻塞30秒

5. 集合操作

<?php
// 添加
$redis->sAdd('set', 'a', 'b', 'c');

// 获取所有
$set = $redis->sMembers('set');

// 检查是否存在
$exists = $redis->sIsMember('set', 'a');

// 交集
$inter = $redis->sInter('set1', 'set2');

// 并集
$union = $redis->sUnion('set1', 'set2');

6. 有序集合操作

<?php
// 添加
$redis->zAdd('rank', 100, '张三');
$redis->zAdd('rank', 90, '李四', 80, '王五');

// 获取排名范围
$top10 = $redis->zRevRange('rank', 0, 9, true); // 前10名(带分数)

// 获取排名
$rank = $redis->zRevRank('rank', '张三');

// 增加分数
$redis->zIncrBy('rank', 5, '张三');

7. 缓存示例

<?php
function getArticle($id) {
    global $redis;
    
    $key = "article:{$id}";
    
    // 先读缓存
    $data = $redis->get($key);
    if ($data !== false) {
        return json_decode($data, true);
    }
    
    // 缓存未命中,读数据库
    $article = DB::query("SELECT * FROM articles WHERE id = ?", [$id]);
    
    if ($article) {
        // 写入缓存,过期时间1小时
        $redis->setex($key, 3600, json_encode($article));
    } else {
        // 缓存空值,防止缓存穿透,过期时间5分钟
        $redis->setex($key, 300, json_encode(null));
    }
    
    return $article;
}

function updateArticle($id, $data) {
    global $redis;
    
    // 先更新数据库
    DB::query("UPDATE articles SET ... WHERE id = ?", [$id]);
    
    // 再删除缓存
    $redis->del("article:{$id}");
}

Redis安全配置

1. 设置密码

在redis.conf中,设置密码:

requirepass yourpassword

连接时,需要认证:

redis-cli -a yourpassword
$redis->auth('yourpassword');

2. 绑定IP

默认情况下,Redis,监听所有网络接口。为了安全,应该,只绑定内网IP:

bind 127.0.0.1 192.168.1.100

3. 禁用危险命令

Redis,有一些危险命令,如FLUSHALL、FLUSHDB、KEYS、CONFIG等,如果,被恶意使用,可能会导致,数据丢失或信息泄露。可以,在配置文件中,禁用或重命名这些命令:

rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command CONFIG "CONFIG_MY_SECRET"

4. 防火墙

使用防火墙(如iptables、ufw),限制,只有指定的IP,才能访问Redis的6379端口。

# 只允许内网IP访问
iptables -A INPUT -p tcp --dport 6379 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP

Redis性能优化

1. 内存优化

  • 使用合适的数据结构,避免,大key。单个key的value,不要太大(建议不超过10KB)。
  • 使用哈希,存储对象,而不是,把对象序列化后,用字符串存储。
  • 设置合理的过期时间,避免,无用的key,长期占用内存。
  • 使用Redis的内存淘汰策略,当内存不足时,自动淘汰旧数据。
# 最大内存
maxmemory 1gb

# 内存淘汰策略
# allkeys-lru:所有key中,最近最少使用的淘汰
# volatile-lru:设置了过期时间的key中,最近最少使用的淘汰
# allkeys-random:所有key中,随机淘汰
# volatile-random:设置了过期时间的key中,随机淘汰
# volatile-ttl:设置了过期时间的key中,即将过期的淘汰
# noeviction:不淘汰,写操作报错
maxmemory-policy allkeys-lru

2. 网络优化

  • 客户端和Redis,部署在同一个内网,减少网络延迟。
  • 使用连接池,避免,频繁创建和销毁连接。
  • 批量操作,使用MGET、MSET、Pipeline等,减少网络往返次数。

3. 持久化优化

  • 如果,对数据丢失不敏感,可以关闭RDB和AOF,纯内存运行,性能最高。
  • 如果,需要持久化,使用AOF的everysec策略,兼顾安全和性能。
  • RDB的save规则,不要设置得太频繁,避免,频繁生成快照,影响性能。

4. 集群优化

  • 当单台Redis,无法满足需求时,使用Redis集群,横向扩展。
  • 使用主从复制,读写分离,主库写,从库读,提升读性能。
  • 使用哨兵(Sentinel),实现高可用,主库宕机时,自动故障转移。

学习资源

推荐书籍

  • 《Redis设计与实现》:黄健宏写的,深入讲解Redis的内部实现,非常适合想深入了解Redis的读者。
  • 《Redis实战》(Redis in Action):Josiah L. Carlson写的,实战导向,讲解Redis的各种使用场景和最佳实践。
  • 《Redis开发与运维》:付磊、张益军写的,国内作者,讲解Redis的开发和运维,非常实用。

在线教程

  • Redis官方文档:https://redis.io/documentation ,最权威、最全面的Redis文档。
  • Redis命令参考:http://redisdoc.com/ ,中文的Redis命令参考,非常详细。
  • 菜鸟教程Redis教程:https://www.runoob.com/redis/redis-tutorial.html
  • 阮一峰的Redis入门教程:http://www.ruanyifeng.com/blog/2018/01/redis-guide.html

在线工具

  • Redis在线测试:https://try.redis.io/ ,可以在线练习Redis命令,不需要安装。
  • Redis Desktop Manager:Redis的图形化管理工具,支持Windows、macOS、Linux。

写在最后

Redis,是目前世界上,最流行、最强大的内存数据库和缓存系统。对于程序员来说,Redis,已经成为了必备技能。掌握了Redis,你就能更高效地做缓存、做队列、做排行榜、做分布式锁,提升网站的性能和用户体验。

学习Redis,不要死记硬背命令,而要理解它的数据结构和使用场景。理解了字符串、哈希、列表、集合、有序集合这五种数据结构的特点和适用场景,很多命令,就自然而然地理解了。

另外,学习Redis,最好的方式,就是多实践。在实际项目中,多使用Redis,遇到问题,多查文档,多尝试,慢慢就熟练了。

2015年,Redis 3.0发布,支持了集群功能,Redis的发展,进入了一个新的阶段。未来,Redis,会越来越强大,应用场景,会越来越广泛。

"工欲善其事,必先利其器。"希望这篇文章,能帮你掌握Redis这个强大的工具,让你的开发工作,更加高效、更加顺畅。

最后,记住:Redis,只是工具。好的工具,能提升效率,但真正决定系统性能的,还是架构设计和代码本身。 不要为了用Redis而用Redis,要根据项目的实际需求,合理使用。

祝你,在Redis的世界里,游刃有余,高效开发!