PHP安装、使用Redis,学习笔记。

基础知识

Redis相关知识:https://www.runoob.com/redis/redis-tutorial.html

Github:https://github.com/phpredis/phpredis#close

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

1.字符串

string 是 redis 最基本的类型,一个 key 对应一个 value。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。

#设置指定 key 的值。
SET key value

#获取指定 key 的值。
GET key

#返回 key 中字符串值的子字符
GETRANGE key start end

#将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
GETSET key value

#对 key 所储存的字符串值,获取指定偏移量上的位(bit)。
GETBIT key offset

#获取所有(一个或多个)给定 key 的值。
MGET key1 [key2..]

#对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit)。
SETBIT key offset value

#将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)。
SETEX key seconds value

#只有在 key 不存在时设置 key 的值。
SETNX key value

#用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。
SETRANGE key offset value

#返回 key 所储存的字符串值的长度。
STRLEN key

#同时设置一个或多个 key-value 对。
MSET key value [key value ...]

#同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
MSETNX key value [key value ...]

#这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位。
PSETEX key milliseconds value

#将 key 中储存的数字值增一。
INCR key

#将 key 所储存的值加上给定的增量值(increment) 。
INCRBY key increment

#将 key 所储存的值加上给定的浮点增量值(increment) 。
INCRBYFLOAT key increment

#将 key 中储存的数字值减一。
DECR key

#key 所储存的值减去给定的减量值(decrement) 。
DECRBY key decrement

#如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。
APPEND key value

2.HASH 

Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
每个 hash 可以存储 232 -1 键值对(40多亿)。

#删除一个或多个哈希表字段
HDEL key field1 [field2]

#查看哈希表 key 中,指定的字段是否存在。
HEXISTS key field

#获取存储在哈希表中指定字段的值。
HGET key field

#获取在哈希表中指定 key 的所有字段和值
HGETALL key

#为哈希表 key 中的指定字段的整数值加上增量 increment 。
HINCRBY key field increment

#为哈希表 key 中的指定字段的浮点数值加上增量 increment 。
HINCRBYFLOAT key field increment

#获取哈希表中的所有字段
HKEYS key

#获取哈希表中字段的数量
HLEN key

#获取所有给定字段的值
HMGET key field1 [field2]

#同时将多个 field-value (域-值)对设置到哈希表 key 中。
HMSET key field1 value1 [field2 value2 ]

#将哈希表 key 中的字段 field 的值设为 value 。
HSET key field value

#只有在字段 field 不存在时,设置哈希表字段的值。
HSETNX key field value

#获取哈希表中所有值。
HVALS key

#迭代哈希表中的键值对。
HSCAN key cursor [MATCH pattern] [COUNT count]

3.列表

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

#移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BLPOP key1 [key2 ] timeout

#移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOP key1 [key2 ] timeout

#从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
BRPOPLPUSH source destination timeout

#通过索引获取列表中的元素
LINDEX key index

#在列表的元素前或者后插入元素
LINSERT key BEFORE|AFTER pivot value

#获取列表长度
LLEN key

#移出并获取列表的第一个元素
LPOP key

#将一个或多个值插入到列表头部
LPUSH key value1 [value2]

#将一个值插入到已存在的列表头部
LPUSHX key value

#获取列表指定范围内的元素
LRANGE key start stop

#移除列表元素
LREM key count value

#通过索引设置列表元素的值
LSET key index value

#对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
LTRIM key start stop

#移除列表的最后一个元素,返回值为移除的元素。
RPOP key

#移除列表的最后一个元素,并将该元素添加到另一个列表并返回
RPOPLPUSH source destination

#在列表中添加一个或多个值到列表尾部
RPUSH key value1 [value2]

#为已存在的列表添加值
RPUSHX key value

4.集合

Redis 的 Set 是 string 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

#向集合添加一个或多个成员
sSADD key member1 [member2]  

#获取集合的成员数
SCARD key   

#返回第一个集合与其他集合之间的差异。
SDIFF key1 [key2] 

#返回给定所有集合的差集并存储在 destination 中
SDIFFSTORE destination key1 [key2]

#返回给定所有集合的交集
SINTER key1 [key2]

#返回给定所有集合的交集并存储在 destination 中
SINTERSTORE destination key1 [key2]

#判断 member 元素是否是集合 key 的成员
SISMEMBER key member

#返回集合中的所有成员
SMEMBERS key

#将 member 元素从 source 集合移动到 destination 集合
SMOVE source destination member

#移除并返回集合中的一个随机元素
SPOP key

#返回集合中一个或多个随机数
SRANDMEMBER key [count]

#移除集合中一个或多个成员
SREM key member1 [member2]

#返回所有给定集合的并集
SUNION key1 [key2]

#所有给定集合的并集存储在 destination 集合中
SUNIONSTORE destination key1 [key2]

#迭代集合中的元素
SSCAN key cursor [MATCH pattern] [COUNT count]

5.有序集合

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。

#向有序集合添加一个或多个成员,或者更新已存在成员的分数
ZADD key score1 member1 [score2 member2]

#获取有序集合的成员数
ZCARD key

#计算在有序集合中指定区间分数的成员数
ZCOUNT key min max

#有序集合中对指定成员的分数加上增量 increment
ZINCRBY key increment member

#计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 destination 中
ZINTERSTORE destination numkeys key [key ...]

#在有序集合中计算指定字典区间内成员数量
ZLEXCOUNT key min max

#通过索引区间返回有序集合指定区间内的成员
ZRANGE key start stop [WITHSCORES]

#通过字典区间返回有序集合的成员
ZRANGEBYLEX key min max [LIMIT offset count]

#通过分数返回有序集合指定区间内的成员
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]

#返回有序集合中指定成员的索引
ZRANK key member

#移除有序集合中的一个或多个成员
ZREM key member [member ...]

#移除有序集合中给定的字典区间的所有成员
ZREMRANGEBYLEX key min max

#移除有序集合中给定的排名区间的所有成员
ZREMRANGEBYRANK key start stop

#移除有序集合中给定的分数区间的所有成员
ZREMRANGEBYSCORE key min max

#返回有序集中指定区间内的成员,通过索引,分数从高到低
ZREVRANGE key start stop [WITHSCORES]

#返回有序集中指定分数区间内的成员,分数从高到低排序
ZREVRANGEBYSCORE key max min [WITHSCORES]

#返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
ZREVRANK key member

#返回有序集中,成员的分数值
ZSCORE key member

#计算给定的一个或多个有序集的并集,并存储在新的 key 中
ZUNIONSTORE destination numkeys key [key ...]

#迭代有序集合中的元素(包括元素成员和元素分值)
ZSCAN key cursor [MATCH pattern] [COUNT count]

6.相关补充

补充
官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
Redis多线程
Redis 6.0中的多线程,也只是针对处理网络请求过程采用了多线程,而数据的读写命令,仍然是单线程处理的。

Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。

Redis是一个字典结构的存储服务器,而实际上一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。

每个数据库对外都是一个从0开始的递增数字命名,Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库

redis命令大全:https://redis.io/commands/http://www.redis.cn/

redis数据库相关参考,命令行连上的默认为0号数据库:https://www.runoob.com/redis/redis-data-types.html

安装redis

1.下载,解压,编译

$ wget http://download.redis.io/releases/redis-3.2.8.tar.gz
$ tar -xzf redis-3.2.8.tar.gz
$ cd redis-3.2.8
$ make

编译完成后,在src目录下,有四个可执行文件redis-server、redis-benchmark、redis-cli和redis.conf。

  • redis-server,用于启动Redis外。
  • redis.conf,redis配置文件。
  • redis-cli,redis命令行工具。
  • redis-benchmark可以为Redis做基准性能测试

redis.config在src所在的目录下,redis的安装目录就是make所在的目录。

执行文件详解:https://blog.csdn.net/baidu_41388533/article/details/108438878

Redis配置文件详解:https://www.runoob.com/redis/redis-conf.html

2.启动redis

将上面提到的可执行文件移动到任意目录,然后执行,例如:

$ /usr/redis/redis-server   /usr/redis/redis.conf   

后台运行redis,将配置文件中daemonize no,改为daemonize yes,再启动即可。

参考文章:https://www.cnblogs.com/ygw1010/p/7446003.html

make命令:https://www.cnblogs.com/linshengqian/p/15585858.html

3.安装PHP Redis拓展

Github:https://github.com/phpredis/phpredis

$ wget https://github.com/phpredis/phpredis/archive/3.1.4.tar.gz
$ tar zxvf 3.1.4.tar.gz                  # 解压
$ cd phpredis-3.1.4                      # 进入 phpredis 目录
$ /usr/local/php/bin/phpize              # php安装后的路径
$ ./configure --with-php-config=/usr/local/php/bin/php-config
$ make && make install

修改php.ini,加入extension=redis.so;重启LNMP,大功告成。

PHP Redis相关操作方法:https://www.cnblogs.com/githup/p/13397405.html

PHP保存数组到Redis,先json或者序列化存入,取回来再反序列化,json解码。

4.查看安装目录

[root@localhost ~]# which Redis
/usr/local/redis #redis安装目录
#查看redis进程
ps -aux | grep redis 
#或者
ps -ef|grep redis
#假设得到redis的进程号123,然后使用以下命令查看安装位置。
ll /proc/123/cwd

redis应用

1.PHP 操作Redis

<?php
/*连接本地的 Redis 服务*/
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
echo "Connection to server successfully";
/*查看服务是否运行*/
echo "Server is running: " . $redis->ping();
?>
  • set、del、get(ket,[value]),存入和取出字符串数据。
  • $redis->setex('key', 3600, 'value'); // setex 带生存时间的写入值
  • $redis->setnx(key,value); 如果key不存在才设置它的值;
  • $redis->rawCommand - 对服务器执行任何通用命令(redis命令行命令)。
  • $redis->subscribe - 订阅
  • $redis->setOption();//设置redis模式
  • $redis->getOption();//查看redis设置的模式
  • $redis->ping();//查看连接状态
  • $redis->close();//断开连接
提示
connect:脚本结束之后连接就释放了。
pconnect:脚本结束之后连接不释放,连接保持在php-fpm进程中。
所以使用pconnect代替connect,可以减少频繁建立redis连接的消耗。

2.redis常用命令行

cd打开redis.cli所在目录,然后输入命令进行连接:redis-cli -h 127.0.0.1 -p 6379,以下为常用命令:

  1. 登录redis:redis-cli -h 127.0.0.1 -p 6379
  2. 查看所有的key值:keys *
  3. 删除指定索引的值:del key
  4. 清空整个redis服务器的数据:fiushall
  5. 清空当前库中的所有key:flushdb
  6. shutdown,退出并关闭redis

3.redis最常用的应用场景

1.缓存、2.数据共享分布式、3.分布式锁、4.全局ID、5.计数器、6.限流、7.位统计、8.购物车、9.用户消息时间线timeline、10.消息队列、11.抽奖、12.点赞、签到、打卡、13.商品标签、14.商品筛选、15.用户关注、推荐模型、16.排行榜

4.Redis数据有效期

参考文章:https://www.php.cn/redis/437105.html

例如
$redis->setex('key', 3600, 'value'); // setex 带生存时间的写入值

Redis订阅与发布

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

1.订阅

#根据频道名字订阅
SUBSCRIBE Channel Channel ...
#指定匹配规则订阅,可以使用*通配符
PSUBSCRIBE pattern [pattern ...]

2.取消订阅

#退订指定频道。
UNSUBSCRIBE Channel Channel ...

#退订所有给定模式的频道。
PUNSUBSCRIBE [pattern [pattern ...]]

3.发布消息

#将信息发送到指定的频道。
PUBLISH channel message

4.缺点

Redis可以提供基本的发布订阅功能,但毕竟不像消息队列那种专业级别,所以会存在以下缺点:

  • redis无法对消息持久化存储,消息一旦被发送,如果没有订阅者接收,数据会丢失消息队列提供了消息传输保障,当客户端连接超时或
  • 事物回滚的等情况发生时,消息会重新发布给订阅者,redis没有该保障,导致的结果就是在订阅者断线超时或其他异常情况时,将会丢失所有发布者发布的信息
  • 若订阅者订阅了频道,但自己读取消息的速度很慢的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃
  • 缓冲区的默认配置:client-output-buffer-limit pubsub 32mb 8mb 60。它的参数含义如下:
    32mb:缓冲区一旦超过 32MB,Redis 直接强制把消费者踢下线。
    8mb + 60:缓冲区超过 8MB,并且持续 60 秒,Redis 也会把消费者踢下线。
PHP redis订阅
PHP调用订阅命令后将进入阻塞状态,除了退出无法主动取消。在swoole协程中可以通过主动取消协程中断订阅,然后在defer回调内close关闭redis,取消订阅

Redis事务

1.事务介绍

Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段: 开始事务 -> 命令入队 -> 执行事务。

2.相关命令

redis事务相关的命令如下:

  • DISCARD,取消事务,放弃执行事务块内的所有命令。
  • EXEC,执行所有事务块内的命令。
  • MULTI,标记一个事务块的开始。
  • UNWATCH,取消 WATCH 命令对所有 key 的监视。
  • WATCH key [key ...],监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
注意
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

3.命令行示例代码

redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"

4.PHP示例代码

try {
    //监视一个(或多个)key,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
    $redis->watch(array($key1, $key2));

    //模拟监视 key 被打断
    //$redis->set($key1, '12345');

    $redis->multi();
    $redis->set($key1, '1123');
    $redis->set($key2, '2123');
    //执行事务块内的所有命令
    $status = $redis->exec();
    //失败则取消事务
    if (!$status) {
        $redis->discard();
    }
} catch (Exception $e){
    echo $e->getMessage();
    exit;
}

Redis消息队列

1.什么是消息队列?

“消息队列”是在消息的传输过程中保存消息的容器。“消息”是在两台计算机间传送的数据单位。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。一般具有如下特点:

  • 支持阻塞等待拉取消息
  • 支持发布 / 订阅模式
  • 消费失败,可重新消费,消息不丢失
  • 实例宕机,消息不丢失,数据可持久化
  • 消息可堆积

2.消费者、消费者组、消息之间的关系

每个消费组都有一份消息队列中完整的消息,不同消费组之间消费进度彼此不受影响。

消息队列中的一条消息被 Consumer Group1 消费过,也会再给 Consumer Group2 消费。 消费组中包含多个消费者,同一个组内的消费者是竞争消费的关系,每个消费者负责消费组 内的一部分消息。如果一条消息被消费者 Consumer1 消费了,那同组的其他消费者就不 会再收到这条消息。

说明
先重建消息组,消费者再通过消息组读取消息队列

3.业务流程

例如一个抢购的场景,消息队列有100份消息,一个消费组内100个人进行消费。

4.消息队列相关命令

  • XADD - 添加消息到末尾,向队列添加消息,如果指定的队列不存在,则创建一个队列
  • XTRIM - 对流进行修剪,限制长度,裁剪消息到指定个数(保留指定个数的最新消息,http://www.redis.cn/commands/xtrim.html
  • XDEL - 删除消息
  • XLEN - 获取流包含的元素数量,即消息长度
  • XRANGE - 获取消息列表,会自动过滤已经删除的消息
  • XREVRANGE - 反向获取消息列表,ID 从大到小
  • XREAD - 以阻塞或非阻塞方式获取消息列表,命令用于从一个或多个 key 中读取消息,仅返回 ID 值大于参数中传入的 ID 的消息。
消息新增和读取
xadd和xread功能上和sub、pub类型,可以用来实现发布与订阅
# XADD key ID field value [field value …]
# key :队列名称,如果不存在就创建
# ID :消息 id,我们使用 * 表示由 redis 生成,可以自定义,但是要自己保证递增性,*表示让 Redis 自动生成唯一的消息 ID。
# field value : 记录。

127.0.0.1:6379> XADD queue * name a 
"1618470740565-0"
127.0.0.1:6379> XADD queue * name b 
"1618470743793-0"

# XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
# [COUNT count],用来获取消息的数量
# [BLOCK milliseconds],用来设置阻塞模式和阻塞超时时间,默认为非阻塞
# id [id ...],用来设置读取的起始 ID,相当于 where id > $id,阻塞模式中可以使用 $ 来获取最新的消息 ID,非阻塞模式下无意义。
# key,指定 stream 的名字

xread streams message 0 

5.消费者组相关命令

  • XGROUP CREATE - 创建消费者组
  • XREADGROUP GROUP - 读取消费者组中的消息
  • XACK - 将消息标记为"已处理"
  • XGROUP SETID - 为消费者组设置新的最后递送消息ID
  • XGROUP DELCONSUMER - 删除消费者
  • XGROUP DESTROY - 删除消费者组
  • XPENDING - 显示待处理消息的相关信息
  • XCLAIM - 转移消息的归属权
  • XINFO - 查看流和消费者组的相关信息;
  • XINFO GROUPS - 打印消费者组的信息;
  • XINFO STREAM - 打印流信息

可以把从Stream中读取内容的进程理解成一个消费者,那么好几个消费者绑定到一块就形成了一个消费者组。

对于一个消费者组有如下约束:

  • 对于一个Stream的消息只会被一个消费者组中的一个消费者消费
  • 一个消费者组中的每一个消费者以一个String类型的name来唯一标识
  • 每一个消费者组都有一个叫first ID never consumed的字段来记录最小的还没有被消费的Entry ID,当下一个消费者来请求的时候就会给到这个Entry
# 读取消息,>表示拉取最新数据
xreadgroup group read_group read streams message >

# 删除指定的消费者组
XGROUP DESTORY key groupName

# 给指定的消费者组添加消费者
XGROUP CREATECONSUMER key groupname consumername

# 删除消费者组中的指定消费者
XGROUP DELCONSUMER key groupname consumername

6.消息堆积时,Stream 是怎么处理的?

其实,当消息队列发生消息堆积时,一般只有 2 个解决方案:

  • 生产者限流:避免消费者处理不及时,导致持续积压
  • 丢弃消息:中间件丢弃旧消息,只保留固定长度的新消息

而 Redis 在实现 Stream 时,采用了第 2 个方案。在发布消息时,可以指定队列的最大长度,防止队列积压导致内存爆炸。

// 队列长度最大10000
127.0.0.1:6379> XADD queue MAXLEN 10000 * name zhangsan
"1618473015018-0"

Redis数据备份与恢复

1.数据备份

Redis SAVE 命令用于创建当前数据库的备份,命令基本语法如下:

redis 127.0.0.1:6379> SAVE 

该命令将在 redis 安装目录中创建dump.rdb文件。

2.数据恢复 

如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令,如下所示:

redis 127.0.0.1:6379> CONFIG GET dir
1) "dir"
2) "/usr/local/redis/bin"

3.后台备份

创建 redis 备份文件也可以使用命令 BGSAVE,该命令在后台执行。

使用unix socket

UNIX Domain Socket是在socket架构上发展起来的用于同一台主机的进程间通讯(IPC),它不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程,当用户连接到Redis通过TCP/IP连接或Unix域连接,千兆网络的典型延迟大概200us,而Unix Domain Socket可能低到30us。

# 打开 redis.conf

# bind 127.0.0.1 
port 0
unixsocket /tmp/redis.sock
unixsocketperm 700
提示
unixsocket选项没有默认值,不指定unixsocket就不会监听任何。如果不指定unixsocketperm,unix socket⽂件将使⽤默认权限(umask相关)
提示
如果PHP链接Redis报错Uncaught RedisException: Permission denied,请将上方700改成777(或者查查SELinux)
<?php

$redis = new Redis();
$redis->connect("/tmp/redis.sock");

//命令行如下
redis-cli -s /tmp/redis.sock

Redis版本相关

Redis发展至今已历经7个大版本,而每个大版本都有大量的新特性产生。例如从3.0开始支持cluster集群模式;4.0开发的lazyfree和PSYNC2解决了Redis长久的大key删除阻塞问题及同步中断无法续传的问题;5.0新增了stream数据结构使Redis具备功能完整的轻量级消息队列能力;6.0更是发布了诸多企业级特性如threaded-io、TLS和ACL等,大幅提升了Redis的性能和安全性。

Redis社区目前主流维护版本是6.0,5.0版本已经进入低维护阶段,而国内还有大量2.8,3.0,4.0版本在使用中。这就很矛盾,一方面,一些对用法,性能,稳定性和抖动控制的能力都贡献在了新版本上,另一方面,国内用户却“看的到,用不上”。

除去6.0,7.0上的高级稳定性优化不说,比如在4.0前,没有lazyfree删除一个大key是同步的会卡住数据库引擎直接导致业务中断。又比如,Redis其实安全性尤其是lua是一直有较大问题的,这些升级也都“隐含”在了新版本中。

Redis补充

Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。

1.命令补充

  • mset、mget可以批量对多个字符串进行读写,节省网络耗时开销。
  • expire命令,让指定的键在指定时间内失效。
  • ttl,查看指定键的失效时间。
  • setnx,如果不存在才设置。
  • Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象
  • 阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻醒过来。消息的延迟几乎为零。用 blpop/brpop 替代前面的 lpop/rpop。

2.位图

用户一年的签到记录,,签了是 1,没签是 0,要记录 365 天。如果使用普通的 key/value,每个用户要记录 365 个,当用户上亿的时候,需要的存储空间是惊人的。为了解决这个问题,Redis 提供了位图数据结构,这样每天的签到记录只占据一个位,365 天就是 365 个位,46 个字节 (一个稍长一点的字符串) 就可以完全容纳下,这就大大节约了存储空间

setbit/getbit,位数据操作。「零存」就是使用 setbit 对位值进行逐个设置,「整存」就是使用字符串一次性填充所有位数组,覆盖掉旧值。
Redis 提供了位图统计指令 bitcount 和位图查找指令 bitpos,bitcount 用来统计指定位置范围内 1 的个数,bitpos 用来查找指定范围内出现的第一个 0 或 1。

3.2 版本以后新增了一个功能强大的指令,有了这条指令,不用管道也可以一次进行多个位的操作。 bitfield 有三个子指令,分别是get/set/incrby,它们都可以对指定位片段进行读写,但是最多只能处理 64 个连续的位,如果超过 64 位,就得使用多个子指令,bitfield 可以一次执行多个子指令。

3.布隆过滤器

Redis 官方提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。布隆过滤器作为一个插件加载到 Redis Server 中,给 Redis 提供了强大的布隆去重功能。

布隆过滤器有二个基本指令,bf.add 添加元素,bf.exists 查询元素是否存在,它的用法和 set 集合的 sadd 和 sismember 差不多。注意 bf.add 只能一次添加一个元素,如果想要一次添加多个,就需要用到 bf.madd 指令。同样如果需要一次查询多个元素是否存在,就需要用到 bf.mexists 指令。

布隆过滤器的基本原理为:使用大小为mbit的数组作为存储空间,使用k个哈希函数进行计算。每次查找一个元素是否存在时,首先通过k个哈希函数分别计算该元素的哈希值,然后判断哈希值对应位置的比特数组元素是否为1。如果有任意一位不为1,则可以判断该元素不存在。
举例说明,假设m为8、k为2,则数组大小为8bit(即1个字节)。初始时该8bit均为0,此时如果插入leveldb这个元素,通过两个哈希函数分别进行计算,得到的值分别为1和5。

5.key搜索

Redis 提供了一个简单暴力的指令 keys 用来列出所有满足特定正则字符串规则的 key

keys 算法是遍历算法,复杂度是 O(n),如果实例中有千万级以上的 key,这个指令就会导致 Redis 服务卡顿,所有读写 Redis 的其它的指令都会被延后甚至会超时报错,因为Redis 是单线程程序,顺序执行所有指令,其它指令必须等到当前的 keys 指令执行完了才可以继续。

scan 参数提供了三个参数,第一个是 cursor 整数值,第二个是 key 的正则模式,第三个是遍历的 limit hint。第一次遍历时,cursor 值为 0,然后将返回结果中第一个整数值作为下一次遍历的 cursor。一直遍历到返回的 cursor 值为 0 时结束。

scan 指令是一系列指令,除了可以遍历所有的 key 之外,还可以对指定的容器集合进行遍历。比如 zscan 遍历 zset 集合元素,hscan 遍历 hash 字典的元素、sscan 遍历 set 集合的元素

6.线程IO模型

事件循环
IO多路复用:用一个线程专门接收请求,然后让请求者自行等待,把处理过程丢到事件循环,事件循环处理完了自动通知这个等待者,等待者拿到返回接口,请求结束。
事件循环
Reactor专门接收请求,但不处理请求,接收后丢给Worker进程处理,处理完了再让Reactor进程丢回去。
IO多路复用
在网络服务中,IO多路复用起的作用是「一次性把多个连接的事件通知业务代码处理」。至于这些事件的处理方式,到底是业务代码循环着处理、丢到队列里,还是交给线程池处理,由业务代码决定。

7.RESP

RESP(Redis Serialization Protocol)是 Redis 序列化协议的简写。它是一种直观的文本协议,优势在于实现异常简单,解析性能极好。

相关文档:https://juejin.cn/post/6844903577459113998

8.持久化

Redis 的持久化机制有两种,第一种是快照,第二种是 AOF 日志。快照是一次全量备份,AOF 日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。

AOF 日志在长期的运行过程中会变的无比庞大,数据库重启时需要加载 AOF 日志进行指令重放,这个时间就会无比漫长。所以需要定期进行 AOF 重写,给 AOF 日志进行瘦身。

Redis 使用操作系统的多进程 COW(Copy On Write) 机制来实现快照持久化。Redis 在持久化时会调用 glibc 的函数 fork 产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。

子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段。这时你可以将父子进程想像成一个连体婴儿,共享身体。这是 Linux 操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来。在进程分离的一瞬间,内存的增长几乎没有明显变化。

子进程做数据持久化,它不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。但是父进程不一样,它必须持续服务客户端请求,然后对内存数据结构进行不间断的修改。

这个时候就会使用操作系统的 COW 机制来进行数据段页面的分离。数据段是由很多操作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改。这时子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据。

RDB
SAVE命令和BGSAVE命令都可以生成RDB文件,这两种命令以不用的方式调用 rdbSave 函数完成RDB文件的生成:

  • SAVE命令是阻塞式的创建RDB文件,阻塞Redis服务器进程,直到RDB文件创建完毕;
  • BGSAVE则会fork一个子进程,由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令;

9.AOF

9.1AOF原理

AOF 日志存储的是 Redis 服务器的顺序指令序列,AOF 日志只记录对内存进行修改的指令记录。

假设 AOF 日志记录了自 Redis 实例创建以来所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例顺序执行所有的指令,也就是「重放」,来恢复 Redis 当前实例的内存数据结构的状态。

9.2AOF重写

Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了

Linux 的 glibc 提供了 fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。只要 Redis 进程实时调用 fsync 函数就可以保证 aof 日志不丢失。但是 fsync 是一个磁盘 IO 操作,它很慢!

如果 Redis 执行一条指令就要 fsync 一次,那么 Redis 高性能的地位就不保了。所以在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync 操作,周期 1s是可以配置的。这是在数据安全性和性能之间做了一个折中,在保持高性能的同时,尽可能使得数据少丢失。

所以通常 Redis 的主节点是不会进行持久化操作,持久化操作主要在从节点进行。从节点是备份节点,没有来自客户端请求的压力,它的操作系统资源往往比较充沛。

10.Redis管道

Redis管道(Pipeline) 本身并不是 Redis 服务器直接提供的技术,这个技术本质上是由客户端提供的,跟服务器没有什么直接的关系。

PHP安装、使用Redis,学习笔记。
Redis管道

客户端通过对管道中的指令列表改变读写顺序就可以大幅节省 IO 时间。管道中指令越多,效果越好。

easyswoole:http://www.easyswoole.com/Components/Redis/pipe.html

phpredis:$pipe = $redis->multi(Redis::PIPELINE); //开启管道、$pipe->exec(); //提交管道里操作命令

优化
当一个事务内部的指令较多时,需要的网络 IO 时间也会线性增长。所以通常 Redis 的客户端在执行事务时都会结合 pipeline 一起使用,这样可以将多次 IO 操作压缩为单次 IO 操作。

11.Watch

Redis 提供了这种 watch 的机制,它是一种乐观锁。

watch 会在事务开始之前盯住 1 个或多个关键变量,当事务执行时,也就是服务器收到了 exec 指令要顺序执行缓存的事务队列时,Redis 会检查关键变量自 watch 之后,是否被修改了 (包括当前事务所在的客户端)。

如果关键变量被人动过了,exec 指令就会返回 null回复告知客户端事务执行失败;

> watch books
OK
> incr books # 被修改了
(integer) 1
> multi
OK
> incr books
QUEUED
> exec # 事务执行失败
(nil)

12.内存回收机制

Redis 并不总是可以将空闲内存立即归还给操作系统。如果当前 Redis 内存有 10G,当你删除了 1GB 的 key 后,再去观察内存,会发现内存变化不会太大。

原因是操作系统回收内存是以页为单位,如果这个页上只要有一个 key还在使用,那么它就不能被回收。Redis 虽然删除了 1GB 的 key,但是这些 key 分散到了很多页面中,每个页面都还有其它 key 存在,这就导致了内存不会立即被回收 。

可以通过flushdb、flushall清空redis数据库。
Redis 虽然无法保证立即回收已经删除的 key 的内存,但是它会重用那些尚未回收的空闲内存。
Redis 为了保持自身结构的简单性,在内存分配这里直接做了甩手掌柜,将内存分配的细节丢给了第三方内存分配库去实现。

目前 Redis 可以使用 jemalloc(facebook) 库来管理内存,也可以切换到 tcmalloc(google)。因为 jemalloc 相比 tcmalloc 的性能要稍好一些,所以Redis 默认使用了 jemalloc。

13.主从同步

Redis 同步支持主从同步和从从同步,从从同步功能是 Redis 后续版本增加的功能,为
了减轻主库的同步负担。

13.1增量同步

Redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一遍向主节点反馈自己同步到哪里了 (偏移量)。

因为内存的 buffer 是有限的,所以 Redis 主库不能将所有的指令都记录在内存 buffer中。Redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。

PHP安装、使用Redis,学习笔记。
环形数组

13.2快照同步

快照同步是一个非常耗费资源的操作,它首先需要在主库上进行一次 bgsave 将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点。

从节点将快 照文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续进行增量同步。

文字块标题
Redis 的复制是异步进行的,wait 指令可以让异步复制变身同步复制,确保系统的强一致性 (不严格)。wait 指令是 Redis3.0 版本以后才出现的。

wait 提供两个参数,第一个参数是从库的数量 N,第二个参数是时间 t,以毫秒为单位。它表示等待 wait 指令之前的所有写操作同步到 N 个从库 (也就是确保 N 个从库的同步没有滞后),最多等待时间 t。如果时间 t=0,表示无限等待直到 N 个从库同步完成达成一致。

14.Redis Sentinel(哨兵) 

负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接集群时,会首先连接 sentinel,通过 sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。

当主节点发生故障时,客户端会重新向 sentinel 要地址,sentinel 会将最新的主节点地址告诉客户端。如此应用程序将无需重启即可自动完成节点切换。

PHP安装、使用Redis,学习笔记。
哨兵
第三方Redis集群项目

14.Redis Cluster

Redis 集群节点采用 Gossip 协议来广播自己的状态以及自己对整个集群认知的改变。比如一个节点发现某个节点失联了 (PFail),它会将这条信息向整个集群广播,其它节点也就可以收到这点失联信息。

如果一个节点收到了某个节点失联的数量 (PFail Count) 已经达到了集群的大多数,就可以标记该节点为确定下线状态 (Fail),然后向整个集群广播,强迫其它节点也接收该节点已经下线的事实,并立即对该失联节点进行主从切换

15.Info指令

相关文档:https://redis.io/commands/info/

  • 每秒执行指令数, redis-cli info stats |grep ops
  • 连接的客户端数量:redis-cli info clients
  • 内存消耗: redis-cli info memory | grep used | grep human
  • ...

16.异步指令

  • unlink 指令,它能对删除操作进行懒处理,执行之后这个键将无法被访问到,丢给后台线程来异步回收内存。
  • Redis 提供了 flushdb 和 flushall 指令,用来清空数据库,这也是极其缓慢的操作。Redis 4.0 同样给这两个指令也带来了异步化,在指令后面增加 async 参数就可以将整棵大树连根拔起,扔给后台线程慢慢焚烧。

问题总结

1.取值错误

某日使用redis,通过setex设置键值,取出来的时候都是false;排查许久,设置前的变量是存在值的,setex返回的也是true;命令行取值发现确实是空;

又排查了许久,难道同步的PHP代码,出现了JS那样的异步问题?又排查发现设置的时候键名后面有一个空格。。。。。

2.链接超时

PHPredis默认连接60s,就超时关闭链接;如下设置为永不超时:

<?php
    $redis->setOption(3, -1);

3.批量删除

redis-cli -h 192.168.0.1 -a 123456 KEYS "newHand*" | xargs redis-cli -h 192.168.0.1 -a 123456 DEL

4.查看活跃连接

info clients 

5.如何定位大 key

#大key扫描
redis-cli -h 127.0.0.1 -p 7001 –-bigkeys
#每隔 100 条 scan 指令就会休眠 0.1s
redis-cli -h 127.0.0.1 -p 7001 –-bigkeys -i 0.1