# 前言
Redis
是key-value
存储系统,是跨平台的非关系型数据库。点我进入 Redis 官网。
Redis
支持数据持久化,可将内存中的数据周期性保存在磁盘文件中,用于故障恢复、数据存储恢复等。Redis
支持String
、list
、set
、Zset
、hash
等数据结构的数据存储Redis
支持主从复制、读写分离
Redis
可用来做 数据的缓存、计数(点赞、阅读、浏览、分享、每日注册、在线人数等)、数据的排行榜、消息队列、限流(提交次数、验证次数等)、抽奖、点赞 等场景。Redis 16 个常见使用场景
PHP
常 用Redis
库:PHPRedis
或Predis
。
点我查看 - PHP 使用 PHPRedis 和 Predis
点我查看 - Redis 订阅与 Redis Stream 技术
点我查看 - Redis 持久化 - RDB 与 AOF
废话不多说,基础知识开始。
一、 安装与配置
这里只列举 Linux 源码安装方式。
1.1 Linux 安装 Redis
# 安装
# 下载 redis 最新稳定版源文件
wget https://download.redis.io/redis-stable.tar.gz
# 解压
tar -zxvf redis-stable.tar.gz
# 进入目录
cd redis-stable
# 执行编译 make
make
# 执行安装
make install PREFIX=/usr/local/redis
# 查看是否安装成功, bin 目录存在则安装成功
cd /usr/local/redis
1.2 Redis 服务端启动
1.2.1 准备工作
# 1. 进入刚才的解压目录, 复制 redis.conf 文件至安装目录的 bin 目录中
cp /root/redis-stable/redis.conf /usr/local/redis/bin
# 2. 修改 redis.conf
vim redis.conf
# 3. 输入 / 进入 vim 末行模式, 搜索 daemonize, 将守护进程模式打开
# 4. 将 daemonize 改为 yes
# 5. vim 保存退出
1.2.2 启动 redis 服务端
# 1. 进入 /usr/local/redis/bin
cd /usr/local/redis/bin
# 2. 以 redis.conf 文件为配置文件, 启动 redis
./redis-server redis.conf
# 3. 查看是否启动成功
ps -ef | grep redis
1.2.3 关闭 redis 服务端
# 杀死进程
kill -9 redis的进程ID
# 关闭 redis 服务
./redis-cli shutdown
1.3 Redis 配置文件
# 只列举一些常用的配置
vim /usr/local/redis/bin/redis.conf
# 以下为配置内容
daemonize no|yes # 是否打开守护进程运行
pidfile /var/run/redis_6379.pid # 当打开守护进程时, 默认将 pid 写入 /var/run/redis_6379.pid 中
port 6379 # redis 默认监听的端口
bind 127.0.0.1 # 默认绑定的主机IP
timeout 300 # 客户端闲置最长时间后将关闭连接, 设置为 0 则代表关闭该功能
databases 16 # 默认 16 个数据库数量, 默认为 0 数据库
maxclients 128 # 设置同一时间最大客户端连接数量, 默认无限制
二、 Redis 数据类型
Redis 有五种常用的数据类型: 字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)。
# 再次启动 redis , 进入目录
cd /usr/local/redis/bin
# 启动 redis 服务端
./redis-server redis.conf
# 启动 redis 客户端
./redis-cli
# 以下命令需要在 Redis 客户端操作
2.1 字符串(strings)
字符串是
redis
基本的、常用的类型,一个key
对应一个value
最大值不能大于 512 MB
可以保存 字符串、json、二进制数据、图片、序列化值等
应用场景:存储用户信息(json 格式)、商品数量、缓存基本数据、计数器等、功能开关
常用命令:
exists
、set
、get
、mset
、mget
、del
、append
、decr
、incr
、decrby
、incrby
、incrbyfloat
、strlen
# redis-cli 客户端命令行
# 1. 是否存在键名: exists 键名
exists name # 0
# 2. 设置单个: set 键名 值
set name Chon
# 3. 获取单个: get 键名
get name
# 4. 设置多个: mset 键名1 值1 键名2 值 [键名n 值n]
mset domain bqhub.com author Chon
# 5. 获取多个: mget 键名1 键名2 [键名n]
mget domain author
# 6. 删除: del 键名
del name
# 7. 追加: append 键名 值
set name Chon # 先设置一个
append name Wang # 追加
get name # 输出: ChonWang
# 8. 减一(整数): decr 键名
set age 18 # 设置一个值为数字
decr age # 输出减 1 后的值 17
# 9. 加一(整数): incr 键名
set height 180 # 设置一个值为数字
incr height # 输出加 1 后的值 181
# 10. 减法(整数): decrby 键名 步长
set age 18 # 设置一个值为数字
decrby age 10 # 输出减 10 后的值 8
# 11. 加法(整数): incrby 键名 步长
set height 180 # 设置一个值为数字
incrby height 10 # 输出加 10 后的值 190
# 12. 加法(浮点数): incrbyfloat 键名 步长
set weight 65.2 # 设置一个值为数字
incrbyfloat weight 1.3 # 输出加 1.3 后的值 66.5
# 13. 获取值长度: strlen 键名
strlen domain
2.2 哈希(hashes)
- 哈希是键值对集合(key=>value)
- 常用于保存
对象
- 应用场景:
- 购物车:以用户标识编号为键名,商品标识编号为字段,购买数量为值
- 用户信息(字段名-值)
- 常用命令
hset
、hget
、hmset
、hmget
、hdel
、hgetall
、hkeys
、hvals
、hlen
、hstrlen
、hexists
、hincrby
# redis-cli 客户端命令行
# 1. 单个数据操作
# 设置单个: hset 键名 字段名 值
hset bqhub domain bqhub.com
# 获取单个: hget 键名 字段名
hget bqhub domain
# 2. 多个数据操作
# 设置多个: hmset 键名 字段名1 值1 字段名2 值2 [字段名n 值n]
hmset bqhub domain bqhub.com author Chon
# 获取多个: hmget 键值 字段名1 [字段名n]
# 返回数组格式显示
hmget bqhub domain author
# 删除单个: hdel 键名 字段名 [字段名n 值n]
hdel bqhub domain author
# 3. 获取全部数据(键名与值): hgetall 键名
hgetall bqhub
# 4. 获取全部字段名: hkeys 键名
hkeys bqhub
# 5. 获取全部值: hvals 键名
hvals bqhub
# 6. 获取字段数量: hlen 键名
hlen bqhub
# 7. 获取字段值的长度: hstrlen 键名 字段名
hstrlen bqhub domain
# 8. 是否存在该字段, 如果键名不存在, 也返回 0
hexists bqhub domain # 1
hexists bqhub name # 0
hexists aaaaa sex # 0
2.3 列表(lists)
- 列表是链表型结果,按插入的顺序排序,可从两端插入或弹出元素。
- 应用场景:例: 消息队列、秒杀
- 常用命令:
lpush
、lpushx
、rpush
、rpushx
、linsert
、lrange
、lindex
、lpop
、blpop
、rpop
、brpop
、lrem
、ltrim
、lset
、llen
# 1. 左侧插入(列表不存在则创建): lpush 列表名 value1 [value...]
lpush testLpush v1 v2 v3
# 2. 左侧插入(列表不存在, 没有操作): lpushx 列表名 value1 [value...]
lpushx testLpush v4 # 成功
lpushx aaa a1 # 失败, 因为 aaa list 不存在
# 3. 右侧插入(列表不存在则创建): rpush 列表名 value1 [value...]
rpush testRpush v1 v2 v3
# 4. 右侧插入(列表不存在, 没有操作): rpushx 列表名 value1 [value...]
rpushx testRpush v4 # 成功
rpushx bbb b1 # 失败, 因为 bbb list 不存在
# 5. 在元素前或后插入新元素: linsert 列表名 before|after 现有元素值 新元素值
linsert testLpush before v2 v22
linsert testLpush after v3 v33
# 6. 查询列表: lrange 列表名 开始索引 结束索引
# 0 1 是第 一 二 个元素, -1 -2 是倒数第一 二个元素, 依此类推
lrange testLpush 0 -1
# 7. 查询元素:根据索引获取元素: lindex 列表名 索引
lindex testLpush 1
# 8. 左侧第一个元素弹出: lpop 列表名
lpop testLpush
# 9. 左侧第一个元素弹出: blpop 列表名 超时秒数
# 如果列表为空, 则等待超时停止或超时内发现元素后弹出
blpop testLpush 3
# 10. 右侧第一个元素弹出: rpop 列表名
rpop testRpush
# 11. 右侧第一个元素弹出: brpop 列表名 超时秒数
# 如果列表为空, 则等待超时停止或超时内发现元素后弹出
brpop testRpush 3
# 12. 删除某数量的元素: lrem 列表名 数量 元素
# > 0 从左往右数
# < 0 从右往左数
# = 0 删除所有
lrem testLpush 2 'v3'
# 13. 保留某范围内的元素: ltrim 列表名 开始索引 结束索引
ltrim testLpush 1 4 # 只保留索引 1 到 4 的元素, 其它的全删除
# 14. 替换某索引的值: lset 列表名 索引 新值
lset testLpush 1 'v11'
# 15. 获取列表的长度: llen 列表名
llen testLpush
2.4 无序集合(sets)
- 字符串元素的集合
- 不允许有重复的元素
- 无序集合,没有下标
- 应用场景: 用户签到情况、用户标签包含的文章、用户关注的粉丝聚合等
- 常用命令:
sadd
、scard
、sdiff
、sdiffstore
、sinter
、sinterstore
、sunion
、sunionstore
、sismember
、smismember
、smembers
、srandmember
、smove
、spop
、srem
# 1. 向集合内添加元素: sadd 集合名 value1 [value...]
sadd myset 1 2 3 4 5 6
# 2. 获取集合元素总数: scard 集合名
scard myset
# 3. 差集: sdiff 集合一 集合二 [集合n]
# 只获取第一个集合与后续集合之间的差集
sadd setnum 1 2 3 11 12 13
sdiff myset setnum # 返回 4 5 6
# 4. 差集保存: sdiffstore 新集合名 比较集合1 比较集体2 [比较集合n]
sdiffstore diffset myset setnum # 将差集保存至 diffset 新集合中
# 5. 交集: sinter 集合一 集合二 [集合n]
# 返回所有集合间的交集
sinter myset setnum # 返回 1 2 3
# 6. 交集保存: sinterstore 新集合名 比较集合1 比较集体2 [比较集合n]
sinterstore interset myset setnum # 将交集保存至 interset 新集合中
# 7. 并集: sunion 集合一 集合二 [集合n]
# 返回所有集合间的并集
sunion myset setnum
# 8. 并集保存: sunionstore 新集合名 比较集合1 比较集体2 [比较集合n]
sunionstore unionset myset setnum # 将并集保存至 unionset 新集合中
# 9. 元素是否存在: sismember 集合名 value
sismember myset 1 # 1
sismember myset 100 # 0
# 10. 多个元素是否存在: smismember 集合名 value1 value2 [value...]
smismember myset 1 2 100 200
# 11. 获取所有元素: smembers 集合名
smembers myset
# 12. 随机返回 n 个元素: srandmember 集合名 数量n
srandmember myset 2 # 随机返回两个元素
# 13. 元素移动至新集合: smove 原集合 新集合 value
smove myset setnum 5
# 14. 随机移除 n 个元素: spop 集合名 数量n
spop myset 2 # 随机移除 2 个元素, 并返回这两个元素
# 15. 移除某元素: srem 集合名 value
srem myset 1
2.5 有序集合(Sorted Set)
- 字符串元素的集合
- 不允许有重复的元素
- 有序的排序,默认从小到大排序
- 每个元素设置一个分数,分数可相同,按分数作为排序依据
- 应用场景:各种规则的排行榜、顺序列表等
- 常用命令:
zadd
、zrandmember
、zrange
、zrangestore
、zrangebyscore
、zrank
、zscore
、zmscore
、zcard
、zcount
、zrevrange
、zrevrangebyscore
、zrevrank
、zmpop
、zpopmax
、zpopmin
、zrem
、zremrangebyrank
、zremrangebyscore
、zdiff
、zdiffstore
、zunion
、zunionstore
、zinter
、zinterstore
、zintercard
、zincrby
# 1. 新增元素或更新元素: zadd 集合名 [选项] 分数1 值1 分数2 值2 [...]
# 常用选项
# xx : 只更新不添加
# nx : 只添加不更新
# lt : 新分数 低于 当前分数才更新,更新后重新插入正确的排序位置
# gt : 新分数 大于 当前分数才更新,更新后重新插入正确的排序位置
zadd myzset 98 yuwen 112 shuxue 102 yingyu 91 wuli 98 zhengzhi
# 2. 获取元素 - 随机返回的 n 个: zrandmember 集合名 数量n [withscores]
zrandmember myzset 2 withscores # 随机返回两个元素, 并显示分数
# 3. 获取元素 - 根据索引范围: zrange 集合名 开始索引 结束索引 [withscores]
# 0 1 是第 一 二 个元素, -1 -2 是倒数第一 二个元素, 依此类推
zrange myzset 0 -1 withscores
# 4. 获取元素 - 根据索引范围 - 保存至新集合: zrangestore 新集合名 原集合名 开始索引 结束索引
# 0 1 是第 一 二 个元素, -1 -2 是倒数第一 二个元素, 依此类推
zrangestore new_myzset myzset 2 -1
# 5. 获取元素 - 根据分数范围内: zrangebyscore 集合名 最小分数值 最大分数值 [withscores]
zrangebyscore myzset 95 105 withscores
# 6. 获取索引下标 - 根据元素值: zrank 集合名 value
zrank myzset shuxue
# 7. 获取单个分数 - 根据元素值: zscore 集合名 value
zscore myzset shuxue
# 8. 获取多个分数 - 根据元素值: zmscore 集合名 value1 value2 [value...]
zmscore myzset shuxue yingyu
# 9. 获取元素总数: zcard 集合名
zcard myzset
# 10. 获取元素个数 - 根据分数范围内: zcount 集合名 最小分数值 最大分数值
zcount myzset 95 105
# 11. 获取元素 - 根据索引范围 - 从大到小排序: zrevrange 集合名 开始索引 结束索引 [withscores]
zrevrange myzset 0 2 withscores
# 12. 获取元素 - 根据分数范围内 - 从大到小排序: zrevrangebyscore 集合名 最大分数值 最小分数值 [withscores]
zrevrangebyscore myzset 105 60 withscores
# 13. 获取索引下标 - 根据元素值 - 从大到小排序: zrevrank 集合名 value
zrevrank myzset shuxue
# 14. 移除最大或最小元素: zmpop 集合数量 集合1 集合2 [集合n] min|max [count 数量]
zmpop 1 myzset max 2 # 弹出最大的两个元素
# 15. 移除最大元素: zpopmax 集合名
zpopmax myzset
# 16. 移除最小元素: zpopmin 集合名
zpopmin myzset
# 17. 移除元素 - 根据元素: zrem 集合名 value
zrem myzset shuxue
# 18. 移除元素 - 根据索引范围: zremrangebyrank 集合名 开始索引 结束索引
zremrangebyrank myzset 0 2
# 19. 移除元素 - 根据分数区间: zremrangebyscore 集合名 最小分数 最大分数
zremrangebyscore myzset 0 60
# 20. 差集: zdiff 集合数量 集合1 [集合n] [withscores]
# 只获取第一个集合与后续集合之间的差集
zdiff 2 myzset myzset1
# 21. 差集保存: zdiffstore 新集合名 集合数量 集合1 集合2 [集合n] [withscores]
zdiffstore diffzset 2 myzset myzset1 # 将差集保存至 diffzset 新集合中
# 22. 并集: zunion 集合数量 集合1 集合2 [集合n] [withscores]
# 返回所有集合间的并集
zunion 2 myzset myzset1
# 23. 并集保存: zunionstore 新集合名 集合数量 集合1 集合2 [集合n]
zunionstore unionzset myzset myzset1 # 将并集保存至 unionzset 新集合中
# 24. 交集: zinter 集合数量 集合1 集合2 [集合n] [withscores]
zinter 2 myzset myzset1
# 25. 交集保存: zinterstore 新集合名 集合数量 集合1 集合2 [集合n]
zinterstore interzset 2 myzset myzset1 # 将差集保存至 interzset 新集合中
# 26. 交集个数: zintercard 集合数量 集合1 集合2 [集合n]
zintercard 2 myzset myzset1
# 27. 加法(整数): zincrby 集合名 要加的分数 元素值
zadd my 180 height # 设置一个值为数字
zincrby my 2 height # 输出加 2 后的值 182
三、Redis 事务
Redis
一次可执行多个命令。Redis
批量操作在发送exec
命令之前被放入队列缓存,等待执行。- 因为已放入队列缓存,所以事务外的操作不会改变事务内的数据。
- 收到
exec
命令后执行操作, 如果命令格式有误,则不执行,如果无误,则执行,即使有一条命令执行失败,其余的命令仍然执行。
Redis
事务从开始到结束一共三个阶段: 开始事务 -> 命令入队 -> 执行事务/回滚事务。
3.1 执行事务的四要素(ACID)
数据库执行事务的四个基本要素: 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。简单描述一下子。
3.1.1 原子性(Atomicity)
原子性指事务是不可分割的,整个事务中的所有操作,要么全部提交成功,要么全部失败回滚,不可能停滞在中间某个环节或执行部分操作。
3.1.2 一致性(Consistency)
一致性指事务开始之前和完成以后,数据库的完整性约束没有被破坏。
- 数据库字段要求为整型,不可能存进一个字母
- 假如你跟发小玩玻璃球,5 个人一共 100 个玻璃球,赢来输去,玩儿一下午,不考虑丢了,碎了,又去小卖部买等特殊情况,总是这 100 个玻璃球在手里转,不可能越玩儿越多,也不可能越玩儿越少,就是 100 个。
3.1.3 隔离性(Isolation)
隔离性指多个事务之间的执行是互不干扰的,一个事务不可能获取或操作到其它事务的内容。
- 但是
脏读
怎么来的,这得去其他地方找答案了,本篇不作解释了哈。
3.1.4 持久性(Durability)
持久性是指事务完成后,对数据库所作的更改会持久的保存在数据库之中,不会被回滚。
- 是不会被回滚,但
幻读
、不可重复读
怎么回事,本篇也不作解释了哈。
3.2 Redis 事务相关命令
# 1. 监视一个或多个 key,如果在事务执行之前,相关 key 被其他命令所改动,则事务将被打断不执行
watch key1 key2 [key...]
# 2. 标记事务块的开始
multi
# 3. 执行所有事务块内的命令
exec
# 4. 取消事务,放弃执行当前事务区块内的所有命令
discard
# 5. 取消 watch 命令对所有 key 的监视
unwatch
3.3 案例事务
# 案例, 事务内, 语句执行成功返回 QUEUED
set coll_num 10
watch coll_num # 监视 收藏数量
multi # 开启事务
incr coll_num # 自增加1,
exec
unwatch
四、常用 Redis 服务器命令
# 1. 获取 Redis 所有统计信息
info
# 2. 设置当前连接名称
client setname testClient
# 3. 获取当前连接名称
client getname
# 4. 清空当前连接名称
client setname ""
# 5. 获取当前服务器时间
time
# 6. 删除所有数据库的 key
flushall
# 7. 删除当前数据库的 key
flushdb
# 8. 实时打印 Redis 服务器接收的命令
monitor
# 9. 数据保存至硬盘
save
# 10. 查看服务是否运行
ping
# 11. 切换到指定的数据库。前边提到过, 默认 16 个数据库数量, 默认为 0 数据库: select index
select 3 # 切换到 3 数据库
# 12. 关闭当前连接
quit
五、Redis 管道技术
管道技术就是把一大堆命令,一次性的执行,一次性的返回。当然,一大堆也指一条命令,哈哈
Redis
的客户端与服务器端是通过TCP
协议连接,由于Redis
是单线程,Redis 客户端
发送一个请求,并监听Socket
返回,等待服务器端的响应,执行完一条命令才继续往下执行,但在请求量很大的情况下,请求的时候就更长了,对性能产生一定的影响。
Redis
底层的通信协议提供了管道技术,管道技术可以一次性发送多条命令并在服务端执行完毕后一次性将执行结果返回。管道技术
减少了通信次数,降低了请求与响应的延时性,提高服务性能。
5.1 PHP 演示没有使用管道的情况
这种方法并不准确,但还好只是演示。但还是可以看出,使用
管道技术
后,速度提升了很多。
<?php
# 实例化并连接
$obj = new Redis();
$obj->connect('127.0.0.1', 6379);
# 开始时间
$start_time = time();
# 测试列表名
$key = 'test_list';
# 循环插入
for ($i = 0; $i < 100000; $i++){
$val = 'val_' . $i;
$obj->lPush($key, $val);
}
# 结束时间
$end_time = time();
# 时间差
echo ($end_time - $start_time); # 我这里约 3-4 秒
5.2 PHP 演示使用管道的情况
<?php
# 实例化并连接
$obj = new Redis();
$obj->connect('127.0.0.1', 6379);
# 开始时间 - 微秒
$start_time = microtime();
# 测试列表名
$key = 'test_list';
$pipeline = $obj->multi(Redis::PIPELINE);
# 循环插入
for ($i = 0; $i < 100000; $i++){
$val = 'val_' . $i;
$obj->lPush($key, $val);
}
$pipeline->exec();
# 结束时间 - 微秒
$end_time = microtime();
# 时间差
echo ($end_time - $start_time); # 我这里约 0.3-0.5 秒
PHP
常 用Redis
库:PHPRedis
或Predis
。
点我查看 - PHP 使用 PHPRedis 和 Predis
点我查看 - Redis 持久化 - RDB 与 AOF