阅读视图

Redis的公共操作命令

1.Key操作命令

Redis是Key-Value数据库,Key都是字符串且区分大小写,关于Redis的key操作,主要有常见的以下几个

Redis的命令是不区分大小写的

1.1 keys *

查看当前库所有的Key,类似于数据库的select * from tb_xxx

127.0.0.1:6379> keys *1) "k1"2) "k2"

1.2 exists <key]>

Key是否存在,返回bool,1代表true,0代表false

127.0.0.1:6379> exists k1(integer) 1127.0.0.1:6379> exists k2(integer) 1127.0.0.1:6379> exists k3(integer) 0

Redis的底层使用C语言实现,很多命令返回bool时,多用0和1表示

1.3 type <key>

key对应的value是什么类型

127.0.0.1:6379> type k1string

1.4 del <key>

删除数据,返回bool

127.0.0.1:6379> del k2(integer) 1

1.5 unlink <key>

非阻塞删除,仅仅将key从keyspace元数据中删除,真正的数据删除将在后续异步进行,返回bool

127.0.0.1:6379> unlink k1(integer) 1

1.6 ttl <key>

查看key还有多少秒过期,-1代表永不过期,-2代表已过期,通常和expire命令搭配使用

127.0.0.1:6379> ttl k1(integer) -1

1.7 expire <key> <秒数>

为指定的key设置过期时间

127.0.0.1:6379> expire k1 100(integer) 1127.0.0.1:6379> ttl k1(integer) 90127.0.0.1:6379> ttl k1(integer) 86

1.8 move <key> <index>

将当前key移动到指定的数据库中,返回bool

127.0.0.1:6379> move k1 2(integer) 1

2.库操作命令

2.1 select <index>

选中几号仓库。redis.conf配置文件默认Redis共16个数据库(0-15),默认选中0号库

127.0.0.1:6379> select 2OK127.0.0.1:6379[2]> select 3OK127.0.0.1:6379[3]> 

2.2 dbsize

查看当前库有多少key

127.0.0.1:6379[3]> dbsize(integer) 0127.0.0.1:6379[3]> set k1 v1OK127.0.0.1:6379[3]> dbsize(integer) 1127.0.0.1:6379[3]> 

2.3 flushdb

清空当前库中的所有key

127.0.0.1:6379> flushdbOK

2.4 flushall

清空整个Redis中的所有key

127.0.0.1:6379> flushallOK

3.其他命令

3.1 help @<type>

命令行下输入help @<type>命令,redis服务器会返回该数据类型的所有用法

127.0.0.1:6379> help @stringAPPEND key valuesummary: Appends a string to the value of a key. Creates the key if it doesn't exist.since: 2.0.0DECR keysummary: Decrements the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.since: 1.0.0DECRBY key decrementsummary: Decrements a number from the integer value of a key. Uses 0 as initial value if the key doesn't exist.since: 1.0.0GET keysummary: Returns the string value of a key.since: 1.0.0GETDEL keysummary: Returns the string value of a key after deleting the key.since: 6.2.0GETEX key [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|PERSIST]summary: Returns the string value of a key after setting its expiration time.since: 6.2.0GETRANGE key start endsummary: Returns a substring of the string stored at a key.since: 2.4.0GETSET key valuesummary: Returns the previous string value of a key after setting it to a new value.since: 1.0.0INCR keysummary: Increments the integer value of a key by one. Uses 0 as initial value if the key doesn't exist.since: 1.0.0INCRBY key incrementsummary: Increments the integer value of a key by a number. Uses 0 as initial value if the key doesn't exist.since: 1.0.0INCRBYFLOAT key incrementsummary: Increment the floating point value of a key by a number. Uses 0 as initial value if the key doesn't exist.since: 2.6.0LCS key1 key2 [LEN] [IDX] [MINMATCHLEN min-match-len] [WITHMATCHLEN]summary: Finds the longest common substring.since: 7.0.0MGET key [key ...]summary: Atomically returns the string values of one or more keys.since: 1.0.0MSET key value [key value ...]summary: Atomically creates or modifies the string values of one or more keys.since: 1.0.1MSETNX key value [key value ...]summary: Atomically modifies the string values of one or more keys only when all keys don't exist.since: 1.0.1PSETEX key milliseconds valuesummary: Sets both string value and expiration time in milliseconds of a key. The key is created if it doesn't exist.since: 2.6.0SET key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time-seconds|PXAT unix-time-milliseconds|KEEPTTL]summary: Sets the string value of a key, ignoring its type. The key is created if it doesn't exist.since: 1.0.0SETEX key seconds valuesummary: Sets the string value and expiration time of a key. Creates the key if it doesn't exist.since: 2.0.0SETNX key valuesummary: Set the string value of a key only when the key doesn't exist.since: 1.0.0SETRANGE key offset valuesummary: Overwrites a part of a string value with another by an offset. Creates the key if it doesn't exist.since: 2.2.0STRLEN keysummary: Returns the length of a string value.since: 2.2.0SUBSTR key start endsummary: Returns a substring from a string value.since: 1.0.0127.0.0.1:6379> 
  •  

Redis数据结构之Bitfleid

1.概述

bitfield将一个字符串看作一个由二进制位构成的数组,并对这个数组中任何偏移进行访问,通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果。

在实际应用中,bitfield并不是一种常用的数据结构。

  •  

Redis数据结构之Bitmap

1.概述

  • 由0和1状态表现的二进制位的bit数组(数组里每个元素只能是0或1)该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们称之为一个索引)。
  • 基于String数据类型的按位的操作。
  • Bitmap支持的最大位数是2³²位,它可以极大的节约存储空间,使用512M内存就可以存储多达42.9亿的字节信息(2³²= 4294967296)

2.常见操作

2.1 SETBIT

设置0或1到Bitmap

SETBIT key offset value
  • key
  • offset 偏移位,从0开始计算
  • value 只能是0或1

例:

127.0.0.1:6379> setbit k1 1 1(integer) 1127.0.0.1:6379> setbit k1 2 1(integer) 1127.0.0.1:6379> setbit k1 3 1(integer) 1

验证:Bitmap基于String数据类型的

127.0.0.1:6379> type k1string

2.2 GETBIT

获取0或1,不是1就默认0

127.0.0.1:6379> getbit k1 1(integer) 1127.0.0.1:6379> getbit k1 2(integer) 1127.0.0.1:6379> getbit k1 3(integer) 1127.0.0.1:6379> getbit k1 4(integer) 0

2.3 STRLEN

不是字符串长度而是Bitmap占据几个字节,按照8位一组扩容,统计有多少组

127.0.0.1:6379> setbit k3 0 1(integer) 0127.0.0.1:6379> setbit k3 7 1(integer) 0127.0.0.1:6379> strlen k3(integer) 1127.0.0.1:6379> setbit k3 8 1(integer) 0127.0.0.1:6379> strlen k3(integer) 2

2.4 BITCOUNT

一个bitmap中从START到END有多少个1,不加START|END统计全部

BITCOUNT KEY [START|END]

例:

127.0.0.1:6379> bitcount k3(integer) 3

2.5 BITOP

对bitmap进行二进制运算(与AND、或OR、非NOT、异或XOR)保存结果到DESTKEY KEY

BITOP AND|OR|NOT|XOR DESTKEY KEY [KEY ...]

例:

准备数据

127.0.0.1:6379> setbit bin1 0 1(integer) 0127.0.0.1:6379> setbit bin1 1 1(integer) 0127.0.0.1:6379> setbit bin1 2 0(integer) 0127.0.0.1:6379> setbit bin1 3 1(integer) 0127.0.0.1:6379> setbit bin2 0 0(integer) 0127.0.0.1:6379> setbit bin2 1 1(integer) 0127.0.0.1:6379> setbit bin2 2 1(integer) 0127.0.0.1:6379> setbit bin2 3 0

测试:BITOP AND

127.0.0.1:6379> BITOP AND bintemp bin1 bin2(integer) 1

查看写入到bintemp中的结果

127.0.0.1:6379> getbit bintemp 0(integer) 0127.0.0.1:6379> getbit bintemp 1(integer) 1127.0.0.1:6379> getbit bintemp 2(integer) 0127.0.0.1:6379> getbit bintemp 3(integer) 0

3.总结

命令作用时间复杂度
setbit给指定key的值的第offset赋值valO(1)
getbit获取指定key的第offset位O(1)
bitcount返回指定key中[start,end]中为1的数量O(n)
bitop对不同的二进制存储数据进行位运算(AND、OR、NOT、XOR)O(n)

使用场景:

1.统计签到、登录、打卡等,例如:ID为10001的用户2025年5月的第12个工作日未打卡

setbit sign:uid_10001:202505 12 0
  •  

Redis数据结构之GEO

1.概述

移动互联网时代LBS应用越来越多,交友软件中附近的人、外卖软件中附近的美食店铺等,那这种附近各种形形色色的地址位置选择是如何实现的?

地球上的地理位置是使用二维的经纬度表示,经度范围(-180,180],纬度范围(-90,90],只要我们确定一个点的经纬度就可以取得它在地球上的位置。

例如滴滴打车,最直观的操作就是实时记录更新各个车的位置,然后当我们要找车时,在数据库中查找距离我们(x0, y0)附近r公里范围内部的车辆,使用如下SQL即可:

select taxi from position where x0 - r < x < x0 + r and y0 - r < y < y0 + r

但是这样会有查询性能问题,如果并发高数据量大,这种查询会影响数据库性能,而且这个查询到的是一个矩形范围,而不是以点为中心r公里为半径的圆形范围。

为了解决这一问题,Redis在3.2版本之后支持了GEO这一数据结构,GEO主要用于存储地理位置信息,并对存储的信息进行操作,包括:

  • 添加地理位置的坐标
  • 获取地理位置的坐标
  • 计算两个位置之间的距离
  • 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合

2.常用命令

2.1 GEOADD

多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的key中

GEOADD key longitude latitude member [longitude latitude member ...]

经纬度可以通过百度地图坐标拾取获得:https://api.map.baidu.com/lbsapi/getpoint/index.html

例如:

127.0.0.1:6379> GEOADD city 116.515802 39.951893 "北京朝阳站" 116.385671 39.871141 "北京南站"(integer) 1

GEO的本质是Set的子类型:

127.0.0.1:6379> type cityzset127.0.0.1:6379> zrange city 0 -11) "\xe5\x8c\x97\xe4\xba\xac\xe5\x8d\x97\xe7\xab\x99"2) "\xe5\x8c\x97\xe4\xba\xac\xe7\xab\x99"3) "\xe5\x8c\x97\xe4\xba\xac\xe6\x9c\x9d\xe9\x98\xb3\xe7\xab\x99"127.0.0.1:6379> 

中文乱码的解决:修改客户端参数

127.0.0.1:6379> quit[root@localhost bin]# ./redis-cli --raw127.0.0.1:6379> auth lzjOK127.0.0.1:6379> zrange city 0 -1北京南站北京站北京朝阳站

2.2 GEOPOS

用于从给定的key里返回所有指定名称(member)的位置(经度和纬度),不存在的返回nil

GEOPOS key member [member ...]

127.0.0.1:6379> GEOPOS city 北京站116.4341434836387634339.90890963654599233127.0.0.1:6379> GEOPOS city  北京朝阳站116.5158006548881530839.95189343796607062

2.3 GEODIST

返回两个给定位置之间的距离

  • m
  • km千米
  • ft英尺
  • mi英里
GEODIST key memberl member2 [m|km|ft|mi]

例:高铁站之间的距离

127.0.0.1:6379> GEODIST city 北京站 北京朝阳站 km8.4477127.0.0.1:6379> GEODIST city 北京南站 北京朝阳站 km14.2804127.0.0.1:6379> GEOADD city 117.978057 40.893571 "承德南站"1127.0.0.1:6379> GEODIST city 承德南站 北京朝阳站 km162.1702

2.4 GEORADIUS

以给定的经纬度为中心,返回与中心的距离不超过给定最大距离的所有位置元素,说白了就是根据半径检索附近的POI(兴趣点)

GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
  • key: 存储地理位置的有序集合键名
  • longitude latitude: 中心点的经度和纬度
  • radius: 半径距离
  • m|km|ft|mi: 距离单位(米、千米、英尺、英里)
  • WITHCOORD: 将位置元素的经度和维度也一并返回。
  • WITHDIST: 在返回位置元素的同时,将位置元素与中心之间的距离也一并返回, 距离的单位和用户给定的范围单位保持一致
  • WITHHASH: 以52位有符号整数的形式,返回位置元素经过原始geohash编码的有序集合分值,这个选项主要用于底层应用或者调试,实际中的作用并不大
  • COUNT count: 限制返回结果数量
  • ASC|DESC: 按距离排序(升序/降序)
  • STORE key: 将结果存储为有序集合(存储 geohash)
  • STOREDIST key: 将结果存储为有序集合(存储距离)

例:以朝阳公园(116.486817,39.953201)为圆心,10千米为半径,搜索city中满足条件的地点

127.0.0.1:6379> GEORADIUS city 116.486817 39.953201 10 km WITHCOORD WITHDIST北京站6.6672116.4341434836387634339.90890963654599233北京朝阳站2.4755116.5158006548881530839.95189343796607062

2.5 GEORADIUSBYMEMBER

跟GEORADIUS类似

2.6 GEOHASH

返回一个或多个位置元素的GEOHASH表示,比位数众多的小数更好处理

GEOHASH算法生成的base32编码值,主要分为三步:
1.将三维的地球变为二维的坐标
2.将二维的坐标转换为一维的点块
3.最后将一维的点块转换为二进制再通过base32编码

GEOHASH key member [member ...]

127.0.0.1:6379> GEOHASH city 北京站 北京南站 北京朝阳站wx4g190y0p0wx4fb6m6nx0wx4g73h0wu0
  •  

Redis数据结构之Hash

1.概述

  • Hash是一个String类型的field(字段)和value(值)的映射表,而且value是一个键值对集合,类似Map<String, Map<Object, Object>>,Hash特别适合用于存储对象。
  • 每个Hash可以存储2³²-1个键值对 (40多亿)。

2.常见操作

2.1 H(M)SET/H(M)GET

HSET/HGET,设置和获取hash的键值对

语法:

hset key field value [field value ......] 

例:

127.0.0.1:6379> hset user:01 name liming(integer) 1hset user:01 id 1 name liming age 30

语法:

hget key field [field ......] 

例:

127.0.0.1:6379> hget user:01 name"liming"127.0.0.1:6379> hget user:01 age"30"127.0.0.1:6379> hget user:01 id"1"

HMSET,一次批量设置hash的多个值

127.0.0.1:6379> HMSET user:02 id 02 name lisiOK

从 Redis 4.0.0 开始,HSET 也支持批量设置,HMSET 被视为已弃用,但仍可使用。

HMGET,一次获取某个hash的多个值

127.0.0.1:6379> hmget user:02 id name 1) "02"2) "lisi"

2.2 HGETALL

获取一个hash中的所有键值

127.0.0.1:6379> hgetall user:011) "name"2) "liming"3) "id"4) "1"5) "age"6) "30"

2.3 HDEL

删除hash中的某个键值对

127.0.0.1:6379> hdel user:01 age(integer) 1127.0.0.1:6379> hgetall user:011) "name"2) "liming"3) "id"4) "1"

2.4 HLEN

hash中键值对数量

127.0.0.1:6379> hlen user:01(integer) 2

2.5 HEXISTS

hash中某个键是否存在

127.0.0.1:6379> HEXISTS user:01 age(integer) 0127.0.0.1:6379> HEXISTS user:01 name(integer) 1

2.6 HKEYS/HVALS

获取一个hash中所有的键/值

127.0.0.1:6379> HKEYS user:011) "name"2) "id"
127.0.0.1:6379> HVALS user:011) "liming"2) "1"

2.7 HINCRBY

对hash中某个键的值进行自增

127.0.0.1:6379> hmset user:03 age 13OK127.0.0.1:6379> hincrby user:03 age 1(integer) 14127.0.0.1:6379> hincrby user:03 age 2(integer) 16

2.8 HSETNX

不存在就赋值,如已存在则无效

127.0.0.1:6379> hgetall user:031) "age"2) "16"127.0.0.1:6379> HSETNX user:03 name liming(integer) 1127.0.0.1:6379> HSETNX user:03 age 3(integer) 0

3.总结

KV键值对的结构,适合早期的购物车等场景

  •  

Redis数据结构之HyperLogLog

1.概述

基数统计是一种去重复统计功能的基数估计算法,HyperLogLog是用来做基数统计的数据结构,HyperLogLog的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定且是很小的。

在Redis里面,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2⁶⁴个不同元素的基数,这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为HyperLogLog只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog不能像集合那样,返回输入的各个元素。

去重类似Java的HashSet,只是不能存储数据

2.常用命令

2.1 添加元素

PFADD key element [element ..]

127.0.0.1:6379> pfadd pf1 1 2 3 4 5 6 5 4 5 6 7 34 2 5 6 4(integer) 1127.0.0.1:6379> pfadd pf2 1 2 3 4 5 6 5 4 43 4 21 65 32 54 (integer) 1

hyperloglog本质上是string

127.0.0.1:6379> type pf1string

2.2 返回基数估算值

PFCOUNT key [key....]

例:去重后只有8个元素

127.0.0.1:6379> PFCOUNT pf1(integer) 8

2.3 合并hyperloglog

合并到新的destkey中

PFMERGE destkey sourcekey [sourcekey...]

例:合并pf1和pf2为pfm

127.0.0.1:6379> PFMERGE pfm pf1 pf2OK127.0.0.1:6379> pfcount pfm(integer) 13

3.总结

使用场景:

1.网站UV统计,文章阅读数UV统计

  •  

Redis数据结构之List

1.概述

  • List是简单的字符串列表,单key多个value,按照插入顺序排序。

  • 支持添加一个元素到列表的头部(左边)或者尾部(右边)

  • 它的底层实际是个双端链表,主要功能有push/pop等,用在栈,队列,消息队列等场景,left/right都可以插入添加,如果键不存在创建新的链表,键已存在,则新增内容,如果值全被移除了,对应的键也就消失了。

    双端链表两端操作的效率很高,通过索引下标的操作性能略有下降

  • 最多可以包含2³²-1个元素 (4294967295, 每个列表超过40亿个元素)。

2.常见操作

2.1 LPUSH/RPUSH/LRANGE

LPUSH将一个或多个值插入头部(左边),RPUSH是将一个或多个值插入尾部(右边)

案例:

127.0.0.1:6379> lpush list1 1 2 3 4 5(integer) 5127.0.0.1:6379> rpush list2 1 2 3 4 5(integer) 5

LRANGE从头部开始遍历,获取元素,下标从0开始,0到-1是全部遍历

案例:

127.0.0.1:6379> LRANGE list1 0 31) "5"2) "4"3) "3"4) "2"127.0.0.1:6379> LRANGE list2 0 -11) "1"2) "2"3) "3"4) "4"5) "5"127.0.0.1:6379> LRANGE list1 0 -11) "5"2) "4"3) "3"4) "2"5) "1"

不存在RRANGE命令

2.2 LPOP/RPOP

LPOP,从左侧弹出(移除)元素,RPOP从右侧弹出元素,被弹出的元素会被返回。

127.0.0.1:6379> lrange list1 0 -11) "5"2) "4"3) "3"4) "2"5) "1"127.0.0.1:6379> lpop list1 "5"127.0.0.1:6379> lpop list1 "4"127.0.0.1:6379> lpop list1 "3"127.0.0.1:6379> lpop list1 "2"127.0.0.1:6379> lpop list1 "1"
127.0.0.1:6379> lrange list2 0 -11) "1"2) "2"3) "3"4) "4"5) "5"127.0.0.1:6379> rpop list2"5"127.0.0.1:6379> rpop list2"4"127.0.0.1:6379> rpop list2"3"127.0.0.1:6379> rpop list2"2"127.0.0.1:6379> rpop list2"1"

2.3 LINDEX

根据下标获取元素

127.0.0.1:6379> rpush list1 1 2 3 4 5(integer) 5127.0.0.1:6379> lindex list1 0"1"127.0.0.1:6379> lindex list1 2"3"

2.4 LLEN

元素个数,list.size();

127.0.0.1:6379> lrange list1 0 -11) "1"2) "2"3) "3"4) "4"5) "5"127.0.0.1:6379> llen list1(integer) 5

2.5 LREM

删除num个值是value的元素

lrem key num value

例:

127.0.0.1:6379> lpush list3 1 2 3 4 5 5 5 5 5 6 7 8(integer) 12127.0.0.1:6379> lrange list3 0 -1 1) "8" 2) "7" 3) "6" 4) "5" 5) "5" 6) "5" 7) "5" 8) "5" 9) "4"10) "3"11) "2"12) "1"127.0.0.1:6379> lrem list3 2 5(integer) 2127.0.0.1:6379> lrange list3 0 -1 1) "8" 2) "7" 3) "6" 4) "5" 5) "5" 6) "5" 7) "4" 8) "3" 9) "2"10) "1"

2.6 LTRIM

截取指定范围的值后再赋给key

LTRIM key start end

例:

127.0.0.1:6379> rpush list1 1 2 3 4 5 6 (integer) 6127.0.0.1:6379> lrange list1 0 -11) "1"2) "2"3) "3"4) "4"5) "5"6) "6"127.0.0.1:6379> LTRIM list1 3 5OK127.0.0.1:6379> lrange list1 0 -11) "4"2) "5"3) "6"

2.7 RPOPLPUSH

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

RPOPLPUSH key source target

例:

127.0.0.1:6379> lrange list1 0 -11) "1"2) "2"3) "3"4) "4"127.0.0.1:6379> lrange list2 0 -11) "5"2) "6"3) "7"4) "8"127.0.0.1:6379> rpoplpush list1 list2"4"127.0.0.1:6379> lrange list1 0 -11) "1"2) "2"3) "3"127.0.0.1:6379> lrange list2 0 -11) "4"2) "5"3) "6"4) "7"5) "8"

2.8 LSET

设置某个下标的值

LSET key index value

例:

127.0.0.1:6379> lrange list2 0 -11) "4"2) "5"3) "6"4) "7"5) "8"127.0.0.1:6379> lset list2  3 abcOK127.0.0.1:6379> lrange list2 0 -11) "4"2) "5"3) "6"4) "abc"5) "8"

2.9 LINSERT

在某个已有值existValue前或后加个新的值newValue

LINSERT key before|after existValue newValue

例:

127.0.0.1:6379> lrange list2 0 -11) "4"2) "5"3) "6"4) "abc"5) "8"127.0.0.1:6379> LINSERT list2 before abc def(integer) 6127.0.0.1:6379> lrange list2 0 -11) "4"2) "5"3) "6"4) "def"5) "abc"6) "8"
  •  

Redis数据结构之Set

1.概述

  • Set是String类型的无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的数据,集合对象的编码可以是intset或者hashtable。
  • Set是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
  • Set中最大的成员数为2³²-1 (4294967295,每个集合可存储40多亿个成员)。

2.常见操作

2.1 SADD

添加元素到set中,不能重复添加

127.0.0.1:6379> sadd set1 a b c d e f g(integer) 7127.0.0.1:6379> sadd set2 a a a b b b c c c(integer) 3

2.2 SMEMBERS

查看set中所有元素

127.0.0.1:6379> SMEMBERS set11) "a"2) "g"3) "c"4) "f"5) "d"6) "e"7) "b"127.0.0.1:6379> SMEMBERS set21) "a"2) "c"3) "b"

2.3 SISMEMBER

判断set中是否有某个元素

127.0.0.1:6379> SISMEMBER set2 a(integer) 1127.0.0.1:6379> SISMEMBER set2 d(integer) 0

2.4 SREM

从set 中移除元素

127.0.0.1:6379> SMEMBERS set11) "a"2) "g"3) "c"4) "f"5) "d"6) "e"7) "b"127.0.0.1:6379> srem set1 g(integer) 1127.0.0.1:6379> SMEMBERS set11) "a"2) "c"3) "f"4) "d"5) "e"6) "b"

2.5 SCARD

set中元素个数

127.0.0.1:6379> SCARD set1(integer) 6

2.6 SRANDMEMBER

从set中随机选择n个元素(不会删除)

SRANDMEMBER key n

例:

127.0.0.1:6379> SMEMBERS set11) "a"2) "c"3) "f"4) "d"5) "e"6) "b"127.0.0.1:6379> SRANDMEMBER set1 21) "f"2) "e"127.0.0.1:6379> SRANDMEMBER set1 21) "a"2) "e"

2.7 SPOP

从set中随机弹出(删除)n个元素

SPOP key n

例:

127.0.0.1:6379> SMEMBERS set11) "a"2) "c"3) "f"4) "d"5) "e"6) "b"127.0.0.1:6379> SPOP set1 21) "c"2) "a"127.0.0.1:6379> SPOP set1 21) "b"2) "f"127.0.0.1:6379> SPOP set1 21) "d"2) "e"127.0.0.1:6379> SPOP set1 2(empty list or set)

2.8 SMOVE

将set1中的v移动到set2中去

smove key key value

例:

127.0.0.1:6379> sadd set1 1 2 3(integer) 3127.0.0.1:6379> sadd set2 a b c(integer) 3127.0.0.1:6379> smove set1 set2 2(integer) 1127.0.0.1:6379> SMEMBERS  set11) "1"2) "3"127.0.0.1:6379> SMEMBERS  set21) "a"2) "2"3) "c"4) "b"

2.9 集合运算

127.0.0.1:6379> smembers set31) "1"2) "c"3) "b"4) "a"5) "2"6) "3"127.0.0.1:6379> smembers set41) "c"2) "b"3) "2"4) "5"5) "4"6) "d"7) "3"

1.差集运算

set3-set4:set3有set4没有

127.0.0.1:6379> sdiff  1) "a"2) "1"

2.并集运算

语法

sunion key key [key ......]  

例:

127.0.0.1:6379> sunion set3 set41) "c"2) "4"3) "5"4) "1"5) "3"6) "b"7) "a"8) "2"9) "d"

3.交集运算

sinter,同时属于几个集合的公共部分

sinter key key [key ...]  

例:

127.0.0.1:6379> sinter set3 set41) "c"2) "b"3) "2"4) "3"

SINTERCARD,redis7新命令,不返回结果集,只返回结果的基数,返回由所有给定集合的交集产生的集合的基数

SINTERCARD numkeys key [key ...] [LIMIT limit]

例:2个集合,set3和set4,交集中共有4个元素,可以限制返回的个数,但是不能超过元素的总个数

127.0.0.1:6379> SINTERCARD  2 set3 set4(integer) 4127.0.0.1:6379> SINTERCARD  2 set3 set4 limit 1(integer) 1127.0.0.1:6379> SINTERCARD  2 set3 set4 limit 2(integer) 2127.0.0.1:6379> SINTERCARD  2 set3 set4 limit 3(integer) 3127.0.0.1:6379> SINTERCARD  2 set3 set4 limit 4(integer) 4127.0.0.1:6379> SINTERCARD  2 set3 set4 limit 5(integer) 4

3.总结

Set集合的使用场景很多,例如:

  1. 可能认识的人或共同感兴趣的话题,商品
    sdiff a b
    sdiff b a

  2. 年会抽奖活动
    sadd 活动key 用户ID:参与抽奖
    scard 活动key:统计参加总人数
    SRANDMEMBER 活动key n:抽取n个幸运的人,这几个人还能继续抽奖
    spop 活动key n:抽取n个幸运的人,这几个人不能继续抽奖

  •  

Redis数据结构之Stream

1.概述

Redis Stream是Redis 5.0版本新增加的数据结构。

Redis Stream主要用于消息队列(MQ,Message Queue),Redis本身是有一个Redis发布订阅(pub/sub)来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis宕机等,消息就会被丢弃,而且没有ACK机制来保证数据的可靠性,假设一个消费者都没有,那消息就直接被丢弃了,简单来说发布订阅(pub/sub)可以分发消息,但无法记录历史消息。

而Redis Stream提供了消息的持久化和主备复制功能,支持自动生成全局唯一ID、支持ack确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

Redis Stream是一个链表,会将所有加入的消息都串起来,每个消息都有一个唯一的ID和对应的内容

Redis Stream组成部分说明:

  • Message Content

    消息

  • Consumer group

    消费组,通过XGROUP CREATE命令创建,同一个消费组可以有多个消费者

  • Last_delivered_id

    游标,每个消费组会有个游标last_delivered_id,任意一个消费者读取了消息都会使游标last_delivered_id往前移动

  • Consumer

    消费者,包含在消费组当中

  • Pending_ids

    消费者会布一个状态变量,用于记录被当前消费者已读取但未ack的消息id,如果客户端没有ack,这个变量里面的消息ID会越来越多,一旦某个消息被ack,它就开始减少。这个pending_ids变量在Redis官方被称之为PEL(Pending Entries List),记录了当前已经被客户端读取的消息,但是还没有ack(Acknowledge character:确认字符),它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理

2.队列(生产)相关命令

2.1 XADD

添加消息到队列末尾,如果队列不存在,则会先创建队列

  • key
  • *|id 消息的ID,格式必须是时间戳-序列号这样的方式,下一条的ID必须比上一条的要大,写成*号,系统将自动生成。ID比较大小时,先比较时间戳大小,若相同再比较序列号
  • field value 键值对
XADD <key> <*|id> <field value> [<field value ...>]

127.0.0.1:6379> XADD mystream * name lzj age 28"1758432825026-0"127.0.0.1:6379> XADD mystream * name xxn age 24"1758432832463-0"

返回的1748835683065-01748835967616-0就是消息的ID,-前的数字为生成消息时的毫秒级时间戳,-后面的数字代表同一毫秒内产生的第几个序列号

在相同的毫秒下序列号从0开始递增,序列号是64位长度,理论上在同一毫秒内生成的数据量无法到达这个级别,因此不用担心序列号会不够用。毫秒数取的是Redis节点服务器的本地时间,如果存在当前的毫秒时间戳比以前已经存在的数据的时间戳小的话(本地时间钟后跳),那么系统将会采用以前相同的毫秒创建新的ID,也即redis在增加信息条目时会检査当前id与上一条目的id,自动纠正错误的情况,一定要保证后面的id比前面大,一个流中信息条目的ID必须是单调增的,这是stream的基础。

Stream的数据类型就是Stream

127.0.0.1:6379> type mystreamstream

2.2 XRANGE

获取消息列表(可以指定范围),忽略删除的消息

  • key 对应的stream
  • start 开始值,用-表示最小
  • end 结束值,用+表示最小
  • count 限制条数
XRANGE <key> <start> <end> [Count <count>]

127.0.0.1:6379> xrange mystream - + 1) 1) "1758432825026-0"   2) 1) "name"      2) "lzj"      3) "age"      4) "28"2) 1) "1758432832463-0"   2) 1) "name"      2) "xxn"      3) "age"      4) "24"

2.3 XREVRANGE

和XRANGE相比,区别在于反向获取,ID从大到小

127.0.0.1:6379> xrevrange mystream + - 1) 1) "1758432832463-0"   2) 1) "name"      2) "xxn"      3) "age"      4) "24"2) 1) "1758432825026-0"   2) 1) "name"      2) "lzj"      3) "age"      4) "28"

2.4 XDEL

删除消息,按照ID删除

XDEL <key> <id> [<id ...>]

XDEL mystream 1748835967616-0

2.5 XLEN

获取Stream中的消息长度

127.0.0.1:6379> XLEN mystream2

2.6 XTRIM

限制Stream的长度,如果已经超长会进行截取

  • MAXLEN 允许的最大长度,对流进行修剪限制长度
  • MINID 允许的最小id,从某个id值开始比该id值小的将会被抛弃
XTRIM <stream> MAXLEN|MINID <n>

127.0.0.1:6379> XTRIM mystream MAXLEN 24
127.0.0.1:6379> XTRIM mystream MINID 1748841640027-01

2.7 XREAD

获取消息(阻塞/非阻塞),返回大于指定ID的消息

  • COUNT 最多读取多少条
  • BLOCK 是否以阻塞的方式读取消息,默认不阻塞,如果miliseconds设置为0,表示永远阻塞
  • STREAMS 队列
  • ID 指定的ID,用$代表队列内现存最大的ID的后一个ID,用0-0(或0; 00)代表队列中最小的ID
XREAD [COUNT <count>] [BLOCK <milliseconds>] STREAMS <key> [<key ...>] ID [<id ...>]

例:用$代表队列内现存最大的ID的后一个ID,因为该ID并不存在所以会返回空

127.0.0.1:6379> Xrange mystream - +1) 1) "1758432825026-0"   2) 1) "name"      2) "lzj"      3) "age"      4) "28"2) 1) "1758432832463-0"   2) 1) "name"      2) "xxn"      3) "age"      4) "24"127.0.0.1:6379> XREAD  STREAMS mystream  $(nil)

例:用0-0代表队列中最小的ID,当指定为0-0的同时不指定count时,会返回队列中所有的元素

127.0.0.1:6379> Xrange mystream - +1) 1) "1758432825026-0"   2) 1) "name"      2) "lzj"      3) "age"      4) "28"2) 1) "1758432832463-0"   2) 1) "name"      2) "xxn"      3) "age"      4) "24"127.0.0.1:6379> XREAD  STREAMS mystream  0-01) 1) "mystream"   2) 1) 1) "1758432825026-0"         2) 1) "name"            2) "lzj"            3) "age"            4) "28"      2) 1) "1758432832463-0"         2) 1) "name"            2) "xxn"            3) "age"            4) "24"

例:阻塞:监听mystream中比最新的一条还靠后的一条,读取不到就会阻塞,一直监听

127.0.0.1:6379> XREAD COUNT 1 BLOCK 0  STREAMS mystream $

打开另一个命令窗口,生产消息

127.0.0.1:6379> xadd mystream * k1 v1"1758434052255-0"

XREAD停止阻塞,打印出新生产的一条消息和等待时间

127.0.0.1:6379> XREAD COUNT 1 BLOCK 0  STREAMS mystream $1) 1) "mystream"   2) 1) 1) "1758434052255-0"         2) 1) "k1"            2) "v1"(183.38s)

3.消费(组)相关命令

3.1 XGROUP CREATE

创建消费组

  • stresm 队列
  • group 消费组
  • id 创建消费组时必须指定ID,0代表从头消费,$代表从队尾消费(只消费最新消息)
XGROUP CREATE <stresm> <group> <id>

例:

127.0.0.1:6379> xgroup create mystream group1 $OK127.0.0.1:6379> xgroup create mystream group2 0OK

3.2 XREADGROUP GROUP

允许多个消费者作为一个组来合作消费同一个stream中的消息,同一个stream中的消息一旦被消费组里面的一个消费者读取了,同组的其他消费者就无法再次读取

  • group 消费组
  • consumer 消费者
  • stream 队列
  • id 指定从哪个ID开始读取,特殊写法:>代表获取组内未分发给其他消费者的新消息(未分发必然未ACK),0代表获取已分发但未被消费者ACK的消息
  • count 读取的数量,可以用于每个消费者读取一部分消息,实现消费的负载均衡
XREADGROUP GROUP <group> <consumer> [COUNT <count>] STREAMS <stream, ...> <id>

例:同组消费者不能重复消费消息

先建两个消费组

127.0.0.1:6379> xgroup create mystream groupA 0OK127.0.0.1:6379> xgroup create mystream groupB 0OK

groupA里面的新建消费者consumer1进行消费

127.0.0.1:6379> XREADGROUP GROUP groupA consumer1 STREAMS mystream >1) 1) "mystream"   2) 1) 1) "1758432825026-0"         2) 1) "name"            2) "lzj"            3) "age"            4) "28"      2) 1) "1758432832463-0"         2) 1) "name"            2) "xxn"            3) "age"            4) "24"      3) 1) "1758434052255-0"         2) 1) "k1"            2) "v1"

groupA中再去新建消费者consumer2进行消费,无法再消费消息

127.0.0.1:6379> XREADGROUP GROUP groupA consumer2 STREAMS mystream >(nil)

但是,groupB中新建一个消费者取消费,可以读取到消息

127.0.0.1:6379> XREADGROUP GROUP groupB consumer1 STREAMS mystream >1) 1) "mystream"   2) 1) 1) "1758432825026-0"         2) 1) "name"            2) "lzj"            3) "age"            4) "28"      2) 1) "1758432832463-0"         2) 1) "name"            2) "xxn"            3) "age"            4) "24"      3) 1) "1758434052255-0"         2) 1) "k1"            2) "v1"

例:负载均衡的消费,新建消费组groupC,三个消费者每个消费一条消息

127.0.0.1:6379> xgroup create mystream groupC 0OK127.0.0.1:6379> XREADGROUP GROUP groupC consumer1 COUNT 1 STREAMS mystream >1) 1) "mystream"   2) 1) 1) "1758432825026-0"         2) 1) "name"            2) "lzj"            3) "age"            4) "28"127.0.0.1:6379> XREADGROUP GROUP groupC consumer2 COUNT 1 STREAMS mystream >1) 1) "mystream"   2) 1) 1) "1758432832463-0"         2) 1) "name"            2) "xxn"            3) "age"            4) "24"127.0.0.1:6379> XREADGROUP GROUP groupC consumer3 COUNT 1 STREAMS mystream >1) 1) "mystream"   2) 1) 1) "1758434052255-0"         2) 1) "k1"            2) "v1"

3.3 消息的ACK机制

基于Stream的消息,怎样保证消费者发生故障或宕机以后,仍然能读取未处理完的消息?Stream采用的是一个内部队列pending_list,记录消费组中消费者的读取记录,直到消费者使用xack命令来通知stream消息已经处理完成,这种消费确认机制增强了消息的可靠性。

刚刚的消费操作仅仅是对消息的读取,实际上并没有ACK“签收”

例:通过命令XPENDING查询groupC中已读取,但未确认的消息

127.0.0.1:6379> XPENDING mystream groupC1) (integer) 3 #总数2) "1758432825026-0" #起始ID3) "1758434052255-0" #结束ID4) 1) 1) "consumer1" #每个消费组消费的消息数量      2) "1"   2) 1) "consumer2"      2) "1"   3) 1) "consumer3"      2) "1"

例:通过命令XPENDING查询groupB中consumer1已读取,但未确认的消息,从小到大查询10个

127.0.0.1:6379> XPENDING mystream groupB - + 10  consumer11) 1) "1758432825026-0"   2) "consumer1"   3) (integer) 2848434   4) (integer) 12) 1) "1758432832463-0"   2) "consumer1"   3) (integer) 2848434   4) (integer) 13) 1) "1758434052255-0"   2) "consumer1"   3) (integer) 2848434   4) (integer) 1

例:使用XACK向消息队列确认groupB组的消息1758432825026-0已经处理完成

127.0.0.1:6379> XACK mystream groupB 1758432825026-0(integer) 1
127.0.0.1:6379> XPENDING mystream groupB - + 10  consumer11) 1) "1758432832463-0"   2) "consumer1"   3) (integer) 3301753   4) (integer) 12) 1) "1758434052255-0"   2) "consumer1"   3) (integer) 3301753   4) (integer) 1

4.XINFO

XINFO命令用于打印出一些stream结构相关的信息

例:XINFO stream打印出mystream队列的详细信息

127.0.0.1:6379> XINFO stream  mystream 1) "length" 2) (integer) 3 3) "radix-tree-keys" 4) (integer) 1 5) "radix-tree-nodes" 6) (integer) 2 7) "last-generated-id" 8) "1758434052255-0" 9) "max-deleted-entry-id"10) "0-0"11) "entries-added"12) (integer) 313) "recorded-first-entry-id"14) "1758432825026-0"15) "groups"16) (integer) 517) "first-entry"18) 1) "1758432825026-0"    2) 1) "name"       2) "lzj"       3) "age"       4) "28"19) "last-entry"20) 1) "1758434052255-0"    2) 1) "k1"       2) "v1"

例:XINFO groups打印出mystream队列队列上存在的消费组信息

127.0.0.1:6379> XINFO  groups mystream1)  1) "name"    2) "group1"    3) "consumers"    4) (integer) 0    5) "pending"    6) (integer) 0    7) "last-delivered-id"    8) "1758434052255-0"    9) "entries-read"   10) (nil)   11) "lag"   12) (integer) 02)  1) "name"    2) "group2"    3) "consumers"    4) (integer) 0    5) "pending"    6) (integer) 0    7) "last-delivered-id"    8) "0-0"    9) "entries-read"   10) (nil)   11) "lag"   12) (integer) 33)  1) "name"    2) "groupA"    3) "consumers"    4) (integer) 2    5) "pending"    6) (integer) 3    7) "last-delivered-id"    8) "1758434052255-0"    9) "entries-read"   10) (integer) 3   11) "lag"   12) (integer) 04)  1) "name"    2) "groupB"    3) "consumers"    4) (integer) 1    5) "pending"    6) (integer) 2    7) "last-delivered-id"    8) "1758434052255-0"    9) "entries-read"   10) (integer) 3   11) "lag"   12) (integer) 05)  1) "name"    2) "groupC"    3) "consumers"    4) (integer) 3    5) "pending"    6) (integer) 3    7) "last-delivered-id"    8) "1758434052255-0"    9) "entries-read"   10) (integer) 3   11) "lag"   12) (integer) 0
  •  

Redis数据结构之String

1.概述

  • String是最常用的数据类型,一个key对应一个value。
  • String是二进制安全的,可以包含任何数据(例如图片和序列化对象),支持序列化。
  • 单个Value最大512MB。

2.常见操作

2.1 SET/GET

语法:

[ ]是可选的参数

SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]

SET命令有EXPXNXXX以及KEEPTTL五个可选参数,其中KEEPTTL为6.0版本添加的可选参数,其它为2.6.12版本添加的可选参数。

  • EX seconds 以秒为单位设置过期时间
  • PX milliseconds 以毫秒为单位设置过期时间
  • EXAT timestamp 设置以秒为单位的UNIX时间戳所对应的时间为过期时间
  • PXAT milliseconds-timestamp 设置以毫秒为单位的UNIX时间戳所对应的时间为过期时间
  • NX 键不存在的时候设置键值
  • XX 键存在的时候设置键值
  • KEEPTTL 保留设置前指定键的生存时间
  • GET 返回指定键原本的值,若键不存在时返回nil

SET命令使用EXPXNX参数,其效果等同于SETEXPSETEXSETNX命令。根据官方文档的描述,未来版本中SETEXPSETEXSETNX命令可能会被淘汰。

EXNX可用于分布式锁。

案例:最常用的set/get

127.0.0.1:6379> set k1 v1OK127.0.0.1:6379> get k1"v1"

案例:NX,键不存在才能创建,否则不能创建

127.0.0.1:6379> set k1 v1 nxOK127.0.0.1:6379> set k1 v1 nx(nil)

案例:XX,已存在的才创建,否则不能创建

127.0.0.1:6379> set k1 v1 OK127.0.0.1:6379> set k1 v1 xxOK127.0.0.1:6379> get k2(nil)127.0.0.1:6379> set k2 v2 xx(nil)

案例:GET,设置新的值前先把旧的值返回

127.0.0.1:6379> set k1 v1OK127.0.0.1:6379> set k1 v2 get"v1"

案例:EX,10秒过期

127.0.0.1:6379> set k1 v1 ex 10OK127.0.0.1:6379> ttl k1(integer) 8127.0.0.1:6379> ttl k1(integer) 6127.0.0.1:6379> ttl k1(integer) 4

set ex是原子操作,和先set key value然后expire key是不同的,后者不是原子的

案例:PX,9000毫秒过期

127.0.0.1:6379> set k1 v1 px 9000OK127.0.0.1:6379> ttl k1(integer) 7127.0.0.1:6379> ttl k1(integer) 5127.0.0.1:6379> ttl k1(integer) 4

案例:KEEPTTL

同一个key如果设置了新的值,又没有追加过期时间,redis会令其立即过期

127.0.0.1:6379> set k1 v1 ex 40OK127.0.0.1:6379> ttl k1(integer) 37127.0.0.1:6379> set k1 v2OK127.0.0.1:6379> ttl k1(integer) -1

如果需要续接过期时间,就需要用到参数KEEPTTL,设置新值后,过期时间会被续接下来

127.0.0.1:6379> set k1 v1 ex 50  OK127.0.0.1:6379> ttl k1(integer) 46127.0.0.1:6379> set k1 v1 keepttlOK127.0.0.1:6379> ttl k1(integer) 33

2.2 MSET/MGET/MSETNX

案例:MSET同时设置和获取多个值

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3OK127.0.0.1:6379> mget k1 k2 k31) "v1"2) "v2"3) "v3"

案例 MSETNX,同时设置多个key的值,且key不存在才设置,只会同时成功或同时失败

失败,因为k1已经存在,k1没有成功修改,k2也根本存不进去

127.0.0.1:6379> flushdbOK127.0.0.1:6379> set k1 v1OK127.0.0.1:6379> msetnx k1 a1 k2 v2(integer) 0127.0.0.1:6379> get k1"v1"127.0.0.1:6379> get k2(nil)

成功,k1,k2都不存在,全部添加成功

127.0.0.1:6379> flushdbOK127.0.0.1:6379> msetnx k1 v1 k2 v2(integer) 1127.0.0.1:6379> mget k1 k21) "v1"2) "v2"

2.3 GETRANGE/SETRANGE

GETRANGE,类似Java中的substring(),字符串截取, 0到-1代表不截取

案例:

127.0.0.1:6379> set k1 abcdefgOK127.0.0.1:6379> getrange k1 0 -1"abcdefg"127.0.0.1:6379> getrange k1 1 4"bcde"

SETRANGE,从第几个字符开始设置新的内容

案例:

127.0.0.1:6379> set k1 abcdefgOK127.0.0.1:6379> setrange k1 1 xxyy(integer) 7127.0.0.1:6379> get k1"axxyyfg"

2.4 INCR(BY)/DECR(BY)

数值的加减,值一定要是数字才能进行这个操作

案例:INCR,每次执行加1

127.0.0.1:6379> set k1 100OK127.0.0.1:6379> get k1"100"127.0.0.1:6379> incr k1(integer) 101127.0.0.1:6379> incr k1(integer) 102127.0.0.1:6379> incr k1(integer) 103127.0.0.1:6379> incr k1(integer) 104

案例:INCRBY,修改步长为5

127.0.0.1:6379> set k1 0OK127.0.0.1:6379> incrby k1 5(integer) 5127.0.0.1:6379> incrby k1 5(integer) 10127.0.0.1:6379> incrby k1 5

案例:DECR,递减1,DECRBY同理

127.0.0.1:6379> set k1 100OK127.0.0.1:6379> decr k1(integer) 99127.0.0.1:6379> decr k1(integer) 98127.0.0.1:6379> decr k1(integer) 97
127.0.0.1:6379> set k1 100OK127.0.0.1:6379> decrby k1 5(integer) 95127.0.0.1:6379> decrby k1 5(integer) 90127.0.0.1:6379> decrby k1 5(integer) 85

2.5 STRLEN

字符串长度

语法

strlen key

案例

127.0.0.1:6379> set k1 aaaOK127.0.0.1:6379> strlen k1(integer) 3

2.6 APPEND

字符串追加

语法

APPEND key value

案例

127.0.0.1:6379> set k1 aaaOK127.0.0.1:6379> append k1 bbb(integer) 6127.0.0.1:6379> get k1"aaabbb"

2.7 GETSET

getset,顾名思义,先取值在设置新的值进去,和set key value get命令相同

127.0.0.1:6379> set k1 v1OK127.0.0.1:6379> getset k1 v2"v1"127.0.0.1:6379> get k1"v2"

3.小结

字符串是一个最基本的数据结构,可用于分布式锁,点赞数量统计等场景。

  •  

Redis数据结构之ZSet

1.概述

  • ZSet和Set一样也是String类型元素的集合,且不允许重复的成员,不同的是ZSet每个元素都会关联一个double类型的分数,Redis正是通过分数来为集合中的成员进行从小到大的排序。
  • ZSet的成员是唯一的,但分数(score)却可以重复。
  • ZSet集合是通过哈希表实现的,所以添加,删除,査找的复杂度都是O(1)。
  • ZSet集合中最大的成员数为2³²-1。

2.常见操作

2.1 ZADD

向有序集合中添加元素和元素的分数

ZADD key score member [score member ...]

例:

127.0.0.1:6379> zadd zset1 10 v1 20 v2 30 v3 40 v4(integer) 4

2.2 ZRANGE

遍历,0到-1代表遍历所有,WITHSCORES结果带着分数

ZRANGE key start stop [WITHSCORES]

例:遍历

127.0.0.1:6379> zrange zset1 0 -11) "v1"2) "v2"3) "v3"4) "v4"

例:遍历,结果带着分数

127.0.0.1:6379> zrange zset1 0 -1 withscores1) "v1"2) "10"3) "v2"4) "20"5) "v3"6) "30"7) "v4"8) "40"

2.3 ZREVRANGE

根据分数反转

127.0.0.1:6379> ZREVRANGE zset1 0 -11) "v4"2) "v3"3) "v2"4) "v1"127.0.0.1:6379> ZREVRANGE zset1 0 -1 withscores1) "v4"2) "40"3) "v3"4) "30"5) "v2"6) "20"7) "v1"8) "10"

2.4 ZRANGEBYSCORE

获取指定分数范围的元素

ZRANGEBYSCORE key [(]min max [WITHSCORES] [LIMIT offset count]

(:不包含
min:分数from
max:分数to
offset:开始下标
count:数量

例:获取分数区间[20, 30]的元素

127.0.0.1:6379> ZRANGE zset1 0 -1 withscores1) "v1"2) "10"3) "v2"4) "20"5) "v3"6) "30"7) "v4"8) "40"127.0.0.1:6379> ZRANGEBYSCORE zset1 20 301) "v2"2) "v3"127.0.0.1:6379> ZRANGEBYSCORE zset1 20 30 withscores1) "v2"2) "20"3) "v3"4) "30"

例:获取分数区间(20, 40]的元素

127.0.0.1:6379> ZRANGE zset1 0 -1 withscores1) "v1"2) "10"3) "v2"4) "20"5) "v3"6) "30"7) "v4"8) "40"127.0.0.1:6379> ZRANGEBYSCORE zset1 (20 401) "v3"2) "v4"

例:limit限制返回的数量

127.0.0.1:6379> ZRANGE zset1 0 -1 withscores1) "v1"2) "10"3) "v2"4) "20"5) "v3"6) "30"7) "v4"8) "40"127.0.0.1:6379> ZRANGEBYSCORE zset1 10 40 limit 1 21) "v2"2) "v3"

2.5 ZSCORE

获取元素分数

127.0.0.1:6379> zscore zset1 v3"30"

2.6 ZCARD

元素个数

127.0.0.1:6379> zcard zset1 (integer) 4

2.6 ZREM

删除某个元素

127.0.0.1:6379> zrem zset1 v3 (integer) 1

2.7 ZINCRBY

为元素member增加分数increment

ZINCRBY key increment member

例:对元素v1加3分

127.0.0.1:6379> zrange zset1 0 -1 withscores1) "v1"2) "10"3) "v2"4) "20"5) "v4"6) "40"127.0.0.1:6379> zincrby zset1 3 v1"13"127.0.0.1:6379> zrange zset1 0 -1 withscores1) "v1"2) "13"3) "v2"4) "20"5) "v4"6) "40"

2.8 ZCOUNT

获得指定分数范围内的元素个数

min: 最小分数
max: 最大分数

ZCOUNT key min max

例:

127.0.0.1:6379> zrange zset1 0 -1 WITHSCORES 1) "v1" 2) "10" 3) "v2" 4) "20" 5) "v3" 6) "30" 7) "v4" 8) "40" 9) "v5"10) "50"11) "v6"12) "60"13) "v7"14) "70"127.0.0.1:6379> zcount zset1  30 50(integer) 3

2.9 ZMPOP

7.0新特性,在指定的numkeys个集合中,弹出分数最大(MAX)或最小(MIN)的count个元素(分数和值成对),可以实现在一个或多个集合中,取出最小或最大的几个元素

ZMPOP numkeys key [key ...] <MIN | MAX> [COUNT count]

例:在1个zset1集合中,弹出最小的1个元素

127.0.0.1:6379> zrange zset1 0 -1 WITHSCORES 1) "v1" 2) "10" 3) "v2" 4) "20" 5) "v3" 6) "30" 7) "v4" 8) "40" 9) "v5"10) "50"11) "v6"12) "60"13) "v7"14) "70"127.0.0.1:6379> ZMPOP 1 zset1 min count 11) "zset1"2) 1) 1) "v1"      2) "10"127.0.0.1:6379> 

2.10 ZRANK

正序下标,集合的某个元素,正序处于集合第几个

127.0.0.1:6379> zrange zset1 0 -11) "v2"2) "v3"3) "v4"4) "v5"5) "v6"6) "v7"127.0.0.1:6379> zrank zset1 v2(integer) 0127.0.0.1:6379> zrank zset1 v3(integer) 1127.0.0.1:6379> zrank zset1 v4(integer) 2127.0.0.1:6379> zrank zset1 v5(integer) 3

2.11 ZREVRANK

倒序下标,集合的某个元素,倒序处于集合第几个

127.0.0.1:6379> zrange zset1 0 -11) "v2"2) "v3"3) "v4"4) "v5"5) "v6"6) "v7"127.0.0.1:6379> zrevrank zset1 v2(integer) 5127.0.0.1:6379> zrevrank zset1 v3(integer) 4127.0.0.1:6379> zrevrank zset1 v4(integer) 3127.0.0.1:6379> zrevrank zset1 v5(integer) 2

3.总结

排序集合大量应用于项目,例如实时展示热销商品统计,打赏点赞数量排行榜统计。将销量和点赞打赏数作为分数绑定在值上面即可。

  •  

Redis安装

此处使用64位的Rocky Linux release 9.5环境编译安装Redis-7.2.6,Redis要发挥出最佳性能需要安装运行在Linux系统

1.下载

从官方GitHub地址 https://github.com/redis 下载7.2.6版本源码到服务器

cd /optwget  https://github.com/redis/redis/archive/refs/tags/7.2.6.tar.gz

2.编译安装

解压下载的tar包后,切换到解压后的目录redis-7.2.6里面,src就是redis的源代码,redis.conf是redis配置文件,sentinel.conf和哨兵模式配置有关,Makefile用于编译安装redis

[root@localhost opt]# cd redis-7.2.6/[root@localhost redis-7.2.6]# lltotal 252-rw-rw-r--.  1 root root  22388 Oct  3 03:13 00-RELEASENOTES-rw-rw-r--.  1 root root     51 Oct  3 03:13 BUGS-rw-rw-r--.  1 root root   5027 Oct  3 03:13 CODE_OF_CONDUCT.md-rw-rw-r--.  1 root root   2634 Oct  3 03:13 CONTRIBUTING.md-rw-rw-r--.  1 root root   1487 Oct  3 03:13 COPYING-rw-rw-r--.  1 root root     11 Oct  3 03:13 INSTALL-rw-rw-r--.  1 root root   6888 Oct  3 03:13 MANIFESTO-rw-rw-r--.  1 root root    151 Oct  3 03:13 Makefile-rw-rw-r--.  1 root root  22607 Oct  3 03:13 README.md-rw-rw-r--.  1 root root   1695 Oct  3 03:13 SECURITY.md-rw-rw-r--.  1 root root   3628 Oct  3 03:13 TLS.mddrwxrwxr-x.  8 root root    133 Oct  3 03:13 deps-rw-rw-r--.  1 root root 107512 Oct  3 03:13 redis.conf-rwxrwxr-x.  1 root root    279 Oct  3 03:13 runtest-rwxrwxr-x.  1 root root    283 Oct  3 03:13 runtest-cluster-rwxrwxr-x.  1 root root   1772 Oct  3 03:13 runtest-moduleapi-rwxrwxr-x.  1 root root    285 Oct  3 03:13 runtest-sentinel-rw-rw-r--.  1 root root  14700 Oct  3 03:13 sentinel.confdrwxrwxr-x.  4 root root   8192 Oct  3 03:13 srcdrwxrwxr-x. 11 root root   4096 Oct  3 03:13 testsdrwxrwxr-x.  9 root root   4096 Oct  3 03:13 utils

编译安装前,要先安装必要的软件包

yum install -y make gcc

要保证gcc版本 >= 4.8.5

[root@localhost local]# gcc -vUsing built-in specs.COLLECT_GCC=gccCOLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/11/lto-wrapperOFFLOAD_TARGET_NAMES=nvptx-noneOFFLOAD_TARGET_DEFAULT=1Target: x86_64-redhat-linuxConfigured with: ../configure --enable-bootstrap --enable-host-pie --enable-host-bind-now --enable-languages=c,c++,fortran,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=https://bugs.rockylinux.org/ --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --enable-plugin --enable-initfini-array --without-isl --enable-multilib --with-linker-hash-style=gnu --enable-offload-targets=nvptx-none --without-cuda-driver --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_64=x86-64-v2 --with-arch_32=x86-64 --build=x86_64-redhat-linux --with-build-config=bootstrap-lto --enable-link-serialization=1Thread model: posixSupported LTO compression algorithms: zlib zstdgcc version 11.5.0 20240719 (Red Hat 11.5.0-2) (GCC) 

在解压后的redis-7.2.6目录下执行以下命令,编译安装,将redis安装到/usr/local/redis/bin这个目录下

make PREFIX=/usr/local/redis install

编译完成,切换到/usr/local/redis/bin目录,就会看到编译好的redis二进制文件了,里面包含几个命令

  • redis-benchmark 性能测试工具
  • redis-check-aof 修复有问题的AOF文件
  • redis-check-dump 修复有问题的dump.rdb文件
  • redis-cli 客户端,操作入口
  • redis-sentinel redis集群使用
  • redis-server redis服务器启动命今
[root@localhost redis-7.2.6]# cd /usr/local/redis/bin[root@localhost bin]# lltotal 28496-rwxr-xr-x. 1 root root  6446056 Feb  4 13:14 redis-benchmarklrwxrwxrwx. 1 root root       12 Feb  4 13:14 redis-check-aof -> redis-serverlrwxrwxrwx. 1 root root       12 Feb  4 13:14 redis-check-rdb -> redis-server-rwxr-xr-x. 1 root root  7030624 Feb  4 13:14 redis-clilrwxrwxrwx. 1 root root       12 Feb  4 13:14 redis-sentinel -> redis-server-rwxr-xr-x. 1 root root 15699832 Feb  4 13:14 redis-server

返回redis-7.2.6目录,将目录下的redis.conf拷贝到安装目标位置/usr/local/redis/bin,这个是redis的配置文件

cp redis.conf  /usr/local/redis/bin

3.运行测试

使用redis.conf配置启动redis

./redis-server ./redis.conf 
[root@localhost bin]# ./redis-server ./redis.conf5837:C 04 Feb 2025 13:15:52.211 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.5837:C 04 Feb 2025 13:15:52.212 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo5837:C 04 Feb 2025 13:15:52.213 * Redis version=7.2.6, bits=64, commit=00000000, modified=0, pid=5837, just started5837:C 04 Feb 2025 13:15:52.213 * Configuration loaded5837:M 04 Feb 2025 13:15:52.214 * Increased maximum number of open files to 10032 (it was originally set to 1024).5837:M 04 Feb 2025 13:15:52.215 * monotonic clock: POSIX clock_gettime                _._                                                             _.-``__ ''-._                                                   _.-``    `.  `_.  ''-._           Redis 7.2.6 (00000000/0) 64 bit  .-`` .-```.  ```\/    _.,_ ''-._                                   (    '      ,       .-`  | `,    )     Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379 |    `-._   `._    /     _.-'    |     PID: 5837  `-._    `-._  `-./  _.-'    _.-'                                    |`-._`-._    `-.__.-'    _.-'_.-'|                                   |    `-._`-._        _.-'_.-'    |           https://redis.io         `-._    `-._`-.__.-'_.-'    _.-'                                    |`-._`-._    `-.__.-'    _.-'_.-'|                                   |    `-._`-._        _.-'_.-'    |                                    `-._    `-._`-.__.-'_.-'    _.-'                                         `-._    `-.__.-'    _.-'                                                 `-._        _.-'                                                         `-.__.-'                                               5837:M 04 Feb 2025 13:15:52.224 * Server initialized5837:M 04 Feb 2025 13:15:52.224 * Ready to accept connections tcp

出现这个界面就说明启动成功了。

4.简单设置

vim redis.conf配置文件,进行以下修改,完成后重启redis-server

1.默认daemonize no 改为 daemonize yes,让redis从后台启动。

# By default Redis does not run as a daemon. Use 'yes' if you need it.# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.# When Redis is supervised by upstart or systemd, this parameter has no impact.daemonize yes

2.默认protected-mode yes 改为 protected-mode no,关闭保护模式,让其他服务(例如spring-boot后端服务)可以连接到这个redis。

# Protected mode is a layer of security protection, in order to avoid that# Redis instances left open on the internet are accessed and exploited.## When protected mode is on and the default user has no password, the server# only accepts local connections from the IPv4 address (127.0.0.1), IPv6 address# (::1) or Unix domain sockets.## By default protected mode is enabled. You should disable it only if# you are sure you want clients from other hosts to connect to Redis# even if no authentication is configured.protected-mode no

3.默认bind 127.0.0.1改为直接注释掉(默认bind 127.0.0.1只能本机访问)或改成0.0.0.0,否则影响远程IP连接

# By default, if no "bind" configuration directive is specified, Redis listens# for connections from all available network interfaces on the host machine.# It is possible to listen to just one or multiple selected interfaces using# the "bind" configuration directive, followed by one or more IP addresses.# Each address can be prefixed by "-", which means that redis will not fail to# start if the address is not available. Being not available only refers to# addresses that does not correspond to any network interface. Addresses that# are already in use will always fail, and unsupported protocols will always BE# silently skipped.## Examples:## bind 192.168.1.100 10.0.0.1     # listens on two specific IPv4 addresses# bind 127.0.0.1 ::1              # listens on loopback IPv4 and IPv6# bind * -::*                     # like the default, all available interfaces## ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the# internet, binding to all the interfaces is dangerous and will expose the# instance to everybody on the internet. So by default we uncomment the# following bind directive, that will force Redis to listen only on the# IPv4 and IPv6 (if available) loopback interface addresses (this means Redis# will only be able to accept client connections from the same host that it is# running on).## IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES# COMMENT OUT THE FOLLOWING LINE.## You will also need to set a password unless you explicitly disable protected# mode.# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~bind 0.0.0.0

4.添加redis密码,requirepass一行的注释打开,并将默认密码改为requirepass 自己设置的密码,设置密码更加安全防止被黑客利用攻击。

# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatibility# layer on top of the new ACL system. The option effect will be just setting# the password for the default user. Clients will still authenticate using# AUTH <password> as usually, or more explicitly with AUTH default <password># if they follow the new protocol: both will work.## The requirepass is not compatible with aclfile option and the ACL LOAD# command, these will cause requirepass to be ignored.#requirepass lzj

5.客户端redis-cli

1.客户端连接
使用redis-cli命令进入交互模式。
-a设置密码
-p设置端口,不写默认6379
-h设置主机(没有用到)
进入交互模式后,客户端发送ping指令,服务端返回pong即为连接成功。
使用quit命令断开连接,退出交互模式。

[root@localhost bin]# ./redis-cli -a lzj  -p 6379 Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.127.0.0.1:6379> pingPONG127.0.0.1:6379> quit[root@localhost bin]#

如果忘记输入密码,交互模式下无法执行命令,使用auth命令补上密码,即可连接成功。

[root@localhost bin]# ./redis-cli   -p 6379 127.0.0.1:6379> ping(error) NOAUTH Authentication required.127.0.0.1:6379> auth lzjOK127.0.0.1:6379> pingPONG127.0.0.1:6379> 

服务端不设置密码的话,-a, auth都是不需要的。

2.测试存储数据到Redis

127.0.0.1:6379> set k1 v1OK127.0.0.1:6379> get k1"v1"

3.使用客户端命令关闭redis服务器,该命令关闭的是redis-server。

./redis-cli -a lzj  -p 6379 shutdown

6.用Docker安装Redis

docker run -d \--privileged=true \-v /data/redis/conf:/usr/local/etc/redis \-v /data/redis/data:/data \-p 16379:6379 \--name redis7 \redis:7.0.11  \redis-server /usr/local/etc/redis/redis.conf
  •  

Redis开篇

栏目持续更新中

一、Redis概述

Redis即Remote Dictionary Server(远程字典服务)是完全开源的,使用ANSIC语言编写的,遵守BSD协议的高性能的Key-Value数据库。Redis提供了丰富的数据结构,例如String、Hash、List、Set、ZSet等等。数据是存在内存中的,同时Redis支持事务、持久化、LUA脚本、发布/订阅、缓存淘汰、流技术等多种功能特性,提供了主从模式、Redis Sentinel和Redis Cluster集群架构方案。

Redis的作者是意大利程序员Antirez,作者个人博客:https://antirez.com/latest/

Redis的官网是:https://redis.io,源码位于GitHub上:https://github.com/redis

二、Redis的主要用途

  • 配合关系型数据库快速读取

    主流应用基本都是80%的读取和20%写入,Redis拿来配合MyMQL等实现读写分离,MySQL数据存储在硬盘,关系型数据库需要执行复杂SQL,相比下Redis基于内存按key读取明显效率更高,Redis在一些场景下的使用明显优于MySQL,例如计数器,排行榜,抢红包等。Redis通常用于一些特定场景,需要与MySQL一起配合使用,两者并不是相互替换和竞争关系,而是共用和配合使用

  • 分布式锁

    synchronized关键字和各种锁只能在一个JVM进程中有效,多服务器的集群环境下利用Redis的单线程特点,可以做分布式环境下的并发控制

  • 队列

    Reids提供list和set操作,这使得Redis能作为一个很好的消息队列平台来使用。我们常通过Reids的队列功能做购买限制。比如到节假日或者推广期间,进行一些活动,对用户购买行为进行限制,限制今天只能购买几次商品或者一段时间内只能购买一次,也比较适合适用。

  • 消息中间件

    Reids具有发布订阅消息功能,因此可以作为一个简单的消息中间件来使用,例如修改了数据字典后通知应用程序执行刷新缓存的方法

  • 分布式会话

    将session或token对应的用户信息保存到Redis,实现集群环境下会话共享

三、Redis的优势

  • 性能极高

    Redis能读的速度是110000次/秒,写的速度是81000次/秒

  • 数据类型丰富

    不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储

  • 支持数据的持久化

    可以将内存中的数据保持在磁盘中重启的时候可以再次加载进行使用Redis支持数据的备份,即master-slave模式的数据备份

四、Redis版本历史

  • 2009,诞生
  • 2010,1.0,Redis Data Types
  • 2012,2.6,Lua,PubSub,Redis Sentinel V1
  • 2013,2.8,Redis Sentinel V2,IPv6
  • 2015,3.0,Redis Cluster,GEO
  • 2016,4.0,RDB,AOF
  • 2017,5.0,Stream
  • 2020,6.0,ACLs,SSL,Threaded I/O
  • 2022,7.0,ACLv2,Redis Functions,listpack代替ziplist,Multi-part AOF

五、Redis版本规则

Redis版本号第二位是奇数的为非稳定版本,例如2.7、2.9、3.1。第二位是偶数的为稳定版本,例如2.6、2.8、3.0。当前奇数版本就是下一个稳定版本的开发版本,例如3.0就是2.9的稳定版,2.9就是3.0的开发版。

根据官网安全漏洞提示,升级redis6一定要选择6.0.8以上版本,或者直接升级到7。

六、Redis7新特性

  • 多AOF文件支持

    7.0版本中一个比较大的变化就是aof文件由一个变成了多个,主要分为两种类型:基本文件(base files)、增量文件(incr files),请注意这些文件名称是复数形式说明每一类文件不仅仅只有一个。在此之外还引入了一个清单文件(manifest)用于跟踪文件以及文件的创建和应用顺序(恢复)

  • Config命令增强

    对于Config Set和Get命令,支持在一次调用过程中传递多个配置参数。例如我们可以在执行一次Config Set命令中更改多个参数: config set maxmemory 10000001 maxmemory-clients 50% port 6399

  • 限制客户端内存使用 Client-eviction

    一旦Redis连接较多,再加上每个连接的内存占用都比较大的时候,Redis总连接内存占用可能会达到maxmemory的上限,可以增加允许限制所有客户端的总内存使用量配置项。
    redis.config中可以用两种配置形式:指定内存大小 maxmemory-clients 1g、基于maxmemory的百分比 maxmemory-clients 10%

  • listpack紧凑列表调整

    listpack是用来替代ziplist的新数据结构,在7.0版本已经没有ziplist的配置了(6.0版本仅部分数据类型作为过渡阶段在使用),listpack已经替换了ziplist类似hash-max-ziplist-entries的配置

  • 访问安全性增强ACLV2

    在redis.conf配置文件中,protected-mode默认为yes,只有当你希望你的客户端在没有授权的情况下可以连接到Redis Server的时候可以将protected-mode设置为no

  • Redis Functions

    Redis函数,一种新的通过服务端脚本扩展Redis的方式,函数与数据本身一起存储。

  • RDB保存时间调整

    持久化文件RDB的保存规则发生了改变,尤其是时间记录频度变化

  • 命令新增和变动

    1.ZSet(有序集合)增加ZMPOP、BZMPOP、ZINTERCARD 等命令。
    2.Set(集合)增加SINTERCARD命令。
    3.LIST(列表)增加LMPOP、BLMPOP,从提供的键名列表中的第一个非空列表键中弹出一个或多个元素。

  • 性能资源利用率、安全性等改进

    自身底层部分优化改动:Redis核心在许多方面进行了重构和改进:
    1.主动碎片整理V2:增强版主动碎片整理,配合Jemalloc版本更新,更快更智能,延时更低。
    2.HyperLogLog改进:在Redis5.0中,HyperLogLog算法得到改进,优化了计数统计时的内存使用效率,7.x更好的内存统计报告。
    3.如果不为了API向后兼容,我们将不再使用slave(奴隶)一词(政治正确)

七、Redis基础篇

使用Redis前需要编译安装以及进行基本的配置,Redis的编译安装、运行,基本配置和客户端命令使用具体见:

7.1 数据结构

Redis的数据结构指的是Value的数据结构类型,Key都是字符串。Redis官网的介绍:https://redis.io/technology/data-structures/

截止到目前的7.x版本,Redis共有10大数据结构,常用的经典数据结构类型有String、List、Hash、Set、ZSet

序号文章名概述
1Redis数据结构之String字符串
2Redis数据结构之List列表
3Redis数据结构之Hash哈希表
4Redis数据结构之Set集合
5Redis数据结构之ZSet(SortedSet) 有序集合

Redis进化过程中又陆陆续续推出了GEO、HyperLogLog、Bitmap、Bitfleid、Stream这几种更加高级的数据结构

序号文章名概述
1Redis数据结构之HyperLogLog用来做基数统计的算法
2Redis数据结构之GEO地理空间
3Redis数据结构之Bitmap位图,二进制位的bit数组
4Redis数据结构之Bitfleid位域
5Redis数据结构之Stream流,主要用于消息队列

参考

  1. 尚硅谷Redis零基础到进阶,作者:尚硅谷,哔哩哔哩,2023.02.21
  •  

再谈WP更新逻辑

最近发现一个问题就是,wp 的后台打开速度越来越慢了,不过用屁股想想也能猜到肯定是 wp 后台的各种更新检查导致的。

网上通用的办法是直接禁用掉各种更新检查,但是鉴于安全问题其实我并不想直接这么干。现在各种漏洞扫描利用太频繁了,新版本能一定程度上降低被利用的风险。

wp 的更新的代码,本质上是有缓存机制的:

function _maybe_update_core() {
    $current = get_site_transient( 'update_core' );

    if ( isset( $current->last_checked, $current->version_checked )
        && 12 * HOUR_IN_SECONDS > ( time() - $current->last_checked )
        && wp_get_wp_version() === $current->version_checked
    ) {
        return;
    }

    wp_version_check();
}

检查更新的时候会判断是否在 12 小时内已经执行过,然而,这个破玩儿的问题在于,写入transient的时候不知道为什么失败了,于是每次进入后台都看到一堆 http 请求。

这 tm 就贼啦智障了,多的时候七八个请求,一个请求一秒钟,全部请求完都十几秒了。

所以,我直接基于 redis 做了个缓存机制,避免重复请求。在 24 小时内请求过就不会再次请求了。直接对请求进行拦截。

既避免了无法检查更新,也解决了每次都检查更新的问题。

代码如下:

<?php
/**
 * 使用 Redis 缓存控制所有 WordPress 更新检查
 * 
 * 控制以下更新检查的频率:
 * 1. wp_version_check() - WordPress 核心版本检查
 * 2. wp_update_themes() - 主题更新检查
 * 3. wp_update_plugins() - 插件更新检查
 * 4. wp_check_browser_version() - 浏览器版本检查
 * 5. wp_check_php_version() - PHP 版本检查
 * 
 * 不依赖 WordPress 的 transient,直接使用 Redis 缓存
 * 如果 24 小时内更新过,跳过更新检查
 * 
 * 使用方法:
 * 将此代码添加到主题的 functions.php 文件末尾
 * 或添加到插件的初始化函数中
 * https://h4ck.org.cn/wp-admin/index.php?debug_all_checks=1#qm-overview
 */

// ==========================================
// 配置项
// ==========================================

// 缓存键名
define('WP_CORE_UPDATE_CHECK_KEY', 'wp_core_update_check_time');
define('WP_THEMES_UPDATE_CHECK_KEY', 'wp_themes_update_check_time');
define('WP_PLUGINS_UPDATE_CHECK_KEY', 'wp_plugins_update_check_time');
define('WP_BROWSER_VERSION_CHECK_KEY', 'wp_browser_version_check_time');
define('WP_PHP_VERSION_CHECK_KEY', 'wp_php_version_check_time');

// 缓存有效期(24 小时)
define('WP_UPDATE_CHECK_EXPIRATION', DAY_IN_SECONDS);


// ==========================================
// 1. WordPress 核心版本检查 (wp_version_check)
// ==========================================

/**
 * 拦截 _maybe_update_core 函数
 * 在 admin_init 优先级 0 中移除 _maybe_update_core,使用 Redis 缓存控制
 */
add_action('admin_init', function() {
    // 移除 WordPress 原来的 _maybe_update_core 钩子
    remove_action('admin_init', '_maybe_update_core', 1);
    
    // 检查 Redis 缓存,看是否在 24 小时内更新过
    $last_check_time = wp_cache_get(WP_CORE_UPDATE_CHECK_KEY);
    
    if ($last_check_time !== false && is_numeric($last_check_time)) {
        $time_since_check = time() - $last_check_time;
        
        // 如果在 24 小时内,直接 return,跳过更新检查
        if ($time_since_check < WP_UPDATE_CHECK_EXPIRATION) {
            error_log('[Redis Core Check] 跳过核心版本检查 - 距离上次检查: ' . human_time_diff($last_check_time, time()));
            return; // 直接返回,不执行更新检查
        }
    }
    
    // 如果缓存过期或不存在,执行原来的更新检查逻辑
    _maybe_update_core();
}, 0); // 优先级 0,在 _maybe_update_core (优先级 1) 之前执行

/**
 * 监控 wp_version_check 的执行,设置 Redis 缓存
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_action('wp_version_check', function() {
    $existing_cache = wp_cache_get(WP_CORE_UPDATE_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_CORE_UPDATE_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
        error_log('[Redis Core Check] wp_version_check 执行,已设置 Redis 缓存');
    }
}, 10);

/**
 * 通过过滤器拦截 update_core transient 设置,设置 Redis 缓存
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_filter('pre_set_site_transient_update_core', function($value, $transient_name) {
    $existing_cache = wp_cache_get(WP_CORE_UPDATE_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_CORE_UPDATE_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
    }
    return $value;
}, 999, 2);


// ==========================================
// 2. 主题更新检查 (wp_update_themes)
// ==========================================

/**
 * 拦截 _maybe_update_themes 函数
 * 在 admin_init 优先级 0 中移除 _maybe_update_themes,使用 Redis 缓存控制
 */
add_action('admin_init', function() {
    // 移除 WordPress 原来的 _maybe_update_themes 钩子
    remove_action('admin_init', '_maybe_update_themes', 1);
    
    // 检查 Redis 缓存,看是否在 24 小时内更新过
    $last_check_time = wp_cache_get(WP_THEMES_UPDATE_CHECK_KEY);
    
    if ($last_check_time !== false && is_numeric($last_check_time)) {
        $time_since_check = time() - $last_check_time;
        
        // 如果在 24 小时内,直接 return,跳过更新检查
        if ($time_since_check < WP_UPDATE_CHECK_EXPIRATION) {
            error_log('[Redis Themes Check] 跳过主题更新检查 - 距离上次检查: ' . human_time_diff($last_check_time, time()));
            return; // 直接返回,不执行更新检查
        }
    }
    
    // 如果缓存过期或不存在,执行原来的更新检查逻辑
    _maybe_update_themes();
}, 0); // 优先级 0,在 _maybe_update_themes (优先级 1) 之前执行

/**
 * 监控 wp_update_themes 的执行,设置 Redis 缓存
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_action('wp_update_themes', function() {
    $existing_cache = wp_cache_get(WP_THEMES_UPDATE_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_THEMES_UPDATE_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
        error_log('[Redis Themes Check] wp_update_themes 执行,已设置 Redis 缓存');
    }
}, 10);

/**
 * 通过过滤器拦截 update_themes transient 设置,设置 Redis 缓存
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_filter('pre_set_site_transient_update_themes', function($value, $transient_name) {
    $existing_cache = wp_cache_get(WP_THEMES_UPDATE_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_THEMES_UPDATE_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
    }
    return $value;
}, 999, 2);


// ==========================================
// 3. 插件更新检查 (wp_update_plugins)
// ==========================================

/**
 * 方法1:在 HTTP 请求级别拦截插件更新 API
 * 直接拦截到 api.wordpress.org/plugins/update-check/ 的请求
 */
add_filter('pre_http_request', function($preempt, $args, $url) {
    // 检查是否是插件更新检查的 API 请求
    if (strpos($url, 'api.wordpress.org/plugins/update-check/') !== false) {
        // 检查 Redis 缓存,看是否在 24 小时内更新过
        $last_check_time = wp_cache_get(WP_PLUGINS_UPDATE_CHECK_KEY);
        
        if ($last_check_time !== false && is_numeric($last_check_time)) {
            $time_since_check = time() - $last_check_time;
            
            // 如果在 24 小时内,返回假数据,跳过 API 请求
            if ($time_since_check < WP_UPDATE_CHECK_EXPIRATION) {
                error_log('[Redis Plugins Check] 跳过插件更新检查 API 请求 - 距离上次检查: ' . human_time_diff($last_check_time, time()));
                
                // 返回一个符合 WordPress API 格式的响应
                // WordPress 期望的完整格式:
                // - plugins: 需要更新的插件数组
                // - no_update: 不需要更新的插件数组(必需!)
                // - translations: 翻译更新数组
                $fake_response_data = array(
                    'plugins' => array(), // 需要更新的插件(空数组表示没有更新)
                    'no_update' => array(), // 不需要更新的插件(必需字段)
                    'translations' => array() // 翻译更新(空数组表示没有更新)
                );
                
                // 编码为 JSON,确保是数组格式
                $fake_response = json_encode($fake_response_data, JSON_UNESCAPED_SLASHES);
                
                // 验证 JSON 编码是否成功
                if ($fake_response === false || json_last_error() !== JSON_ERROR_NONE) {
                    error_log('[Redis Plugins Check] JSON 编码失败: ' . json_last_error_msg());
                    // 如果编码失败,返回一个包含所有必需字段的简单有效 JSON
                    $fake_response = '{"plugins":[],"no_update":[],"translations":[]}';
                }
                
                return array(
                    'body' => $fake_response,
                    'response' => array('code' => 200, 'message' => 'OK'),
                    'headers' => array('Content-Type' => 'application/json; charset=UTF-8'),
                );
            }
        }
    }
    
    return $preempt;
}, 5, 3); // 提高优先级,确保在其他过滤器之前执行

/**
 * 方法2:拦截 _maybe_update_plugins 函数
 * 在 admin_init 优先级 0 中移除 _maybe_update_plugins,使用 Redis 缓存控制
 */
add_action('admin_init', function() {
    // 移除 WordPress 原来的 _maybe_update_plugins 钩子
    remove_action('admin_init', '_maybe_update_plugins', 1);
    
    // 检查 Redis 缓存,看是否在 24 小时内更新过
    $last_check_time = wp_cache_get(WP_PLUGINS_UPDATE_CHECK_KEY);
    
    if ($last_check_time !== false && is_numeric($last_check_time)) {
        $time_since_check = time() - $last_check_time;
        
        // 如果在 24 小时内,直接 return,跳过更新检查
        if ($time_since_check < WP_UPDATE_CHECK_EXPIRATION) {
            error_log('[Redis Plugins Check] 跳过插件更新检查 - 距离上次检查: ' . human_time_diff($last_check_time, time()));
            return; // 直接返回,不执行更新检查
        }
    }
    
    // 如果缓存过期或不存在,执行原来的更新检查逻辑
    _maybe_update_plugins();
}, 0); // 优先级 0,在 _maybe_update_plugins (优先级 1) 之前执行

/**
 * 在 HTTP 响应时设置 Redis 缓存(插件检查)
 * 只有在实际执行了 API 请求并成功获取响应后,才设置缓存
 * 同时确保响应格式正确
 */
add_filter('http_response', function($response, $args, $url) {
        // 只有在缓存不存在或已过期时,才设置缓存
        // 这样可以避免频繁刷新时不断重置缓存时间
        $existing_cache = wp_cache_get(WP_PLUGINS_UPDATE_CHECK_KEY);
        if ($existing_cache === false) {
            // 缓存不存在,说明这是真正的更新检查,设置缓存
            wp_cache_set(
                WP_PLUGINS_UPDATE_CHECK_KEY,
                time(),
                    '',
                WP_UPDATE_CHECK_EXPIRATION
            );
            error_log('[Redis Plugins Check] 插件更新检查 API 请求完成,已设置 Redis 缓存');
        } else {
            error_log('[Redis Plugins Check] 缓存已存在,不更新缓存时间');
        }
    // 检查是否是插件更新检查的 API 请求
    if (strpos($url, 'api.wordpress.org/plugins/update-check/') !== false) {
        if (!is_wp_error($response)) {

            
            // 确保响应体格式正确
            $body = wp_remote_retrieve_body($response);
            if (!empty($body)) {
                $decoded = json_decode($body, true);
                // 如果解析失败或格式不正确,修复它
                if (!is_array($decoded)) {
                    error_log('[Redis Plugins Check] JSON 解析失败,创建默认响应');
                    $decoded = array();
                }
                
                // 确保所有必需字段存在且是正确的类型
                if (!isset($decoded['plugins']) || !is_array($decoded['plugins'])) {
                    $decoded['plugins'] = array();
                }
                if (!isset($decoded['no_update']) || !is_array($decoded['no_update'])) {
                    $decoded['no_update'] = array();
                }
                if (!isset($decoded['translations']) || !is_array($decoded['translations'])) {
                    $decoded['translations'] = array();
                }
                
                // 重新编码响应体(只有在需要修复时才重新编码)
                $response['body'] = json_encode($decoded, JSON_UNESCAPED_SLASHES);
            }
        }
    }
    return $response;
}, 10, 3);

/**
 * 监控 wp_update_plugins 的执行,设置 Redis 缓存
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_action('wp_update_plugins', function() {
    // 只有在缓存不存在时,才设置缓存
    // 这样可以避免在缓存有效期内不断重置缓存时间
    $existing_cache = wp_cache_get(WP_PLUGINS_UPDATE_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_PLUGINS_UPDATE_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
        error_log('[Redis Plugins Check] wp_update_plugins 执行,已设置 Redis 缓存');
    }
}, 10);

/**
 * 通过过滤器拦截 update_plugins transient 设置,设置 Redis 缓存
 * 同时确保 transient 数据格式正确
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_filter('pre_set_site_transient_update_plugins', function($value, $transient_name) {
    // 确保格式正确
    if ($value && is_object($value)) {
        // 确保 response 是数组
        if (isset($value->response) && !is_array($value->response)) {
            error_log('[Redis Plugins Check] 修复 response 格式,从 ' . gettype($value->response) . ' 转换为数组');
            $value->response = is_array($value->response) ? $value->response : array();
        }
        // 确保 translations 是数组
        if (isset($value->translations) && !is_array($value->translations)) {
            $value->translations = is_array($value->translations) ? $value->translations : array();
        }
        // 确保 checked 是数组
        if (isset($value->checked) && !is_array($value->checked)) {
            $value->checked = is_array($value->checked) ? $value->checked : array();
        }
    }
    
    // 只有在缓存不存在时,才设置 Redis 缓存
    // 这样可以避免在缓存有效期内不断重置缓存时间
    $existing_cache = wp_cache_get(WP_PLUGINS_UPDATE_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_PLUGINS_UPDATE_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
        error_log('[Redis Plugins Check] transient 设置,已设置 Redis 缓存');
    }
    
    return $value;
}, 999, 2);


// ==========================================
// 4. 浏览器版本检查 (wp_check_browser_version)
// ==========================================

/**
 * 拦截 wp_check_browser_version 函数
 * 这个函数通过 HTTP 请求到 api.wordpress.org/core/browse-happy/
 * 我们使用过滤器拦截 HTTP 请求
 */
add_filter('pre_http_request', function($preempt, $args, $url) {
    // 检查是否是浏览器版本检查的 API 请求
    if (strpos($url, 'api.wordpress.org/core/browse-happy/') !== false) {
        // 检查 Redis 缓存,看是否在 24 小时内更新过
        $last_check_time = wp_cache_get(WP_BROWSER_VERSION_CHECK_KEY);
        
        if ($last_check_time !== false && is_numeric($last_check_time)) {
            $time_since_check = time() - $last_check_time;
            
            // 如果在 24 小时内,返回假数据,跳过 API 请求
            if ($time_since_check < WP_UPDATE_CHECK_EXPIRATION) {
                error_log('[Redis Browser Check] 跳过浏览器版本检查 - 距离上次检查: ' . human_time_diff($last_check_time, time()));
                // 返回一个成功的响应(避免重复请求)
                return array(
                    'body' => '{"success":true}',
                    'response' => array('code' => 200, 'message' => 'OK'),
                    'headers' => array(),
                );
            }
        }
    }
    
    return $preempt;
}, 10, 3);

/**
 * 在 HTTP 响应时设置 Redis 缓存(浏览器检查)
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_filter('http_response', function($response, $args, $url) {
    // 检查是否是浏览器版本检查的 API 请求
    if (strpos($url, 'api.wordpress.org/core/browse-happy/') !== false && !is_wp_error($response)) {
        $existing_cache = wp_cache_get(WP_BROWSER_VERSION_CHECK_KEY);
        if ($existing_cache === false) {
            wp_cache_set(
                WP_BROWSER_VERSION_CHECK_KEY,
                time(),
                '',
                WP_UPDATE_CHECK_EXPIRATION
            );
            error_log('[Redis Browser Check] wp_check_browser_version 执行,已设置 Redis 缓存');
        }
    }
    return $response;
}, 10, 3);

/**
 * 监控 wp_check_browser_version 的执行,设置 Redis 缓存
 * 作为备用方案,通过动作钩子捕获
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_action('wp_check_browser_version', function() {
    $existing_cache = wp_cache_get(WP_BROWSER_VERSION_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_BROWSER_VERSION_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
        error_log('[Redis Browser Check] wp_check_browser_version 执行,已设置 Redis 缓存');
    }
}, 10);


// ==========================================
// 5. PHP 版本检查 (wp_check_php_version)
// ==========================================

/**
 * 拦截 wp_check_php_version 函数
 * 这个函数通过 HTTP 请求到 api.wordpress.org/core/serve-happy/
 * 我们使用过滤器拦截 HTTP 请求
 */
add_filter('pre_http_request', function($preempt, $args, $url) {
    // 检查是否是 PHP 版本检查的 API 请求
    if (strpos($url, 'api.wordpress.org/core/serve-happy/') !== false) {
        // 检查 Redis 缓存,看是否在 24 小时内更新过
        $last_check_time = wp_cache_get(WP_PHP_VERSION_CHECK_KEY);
        
        if ($last_check_time !== false && is_numeric($last_check_time)) {
            $time_since_check = time() - $last_check_time;
            
            // 如果在 24 小时内,返回假数据,跳过 API 请求
            if ($time_since_check < WP_UPDATE_CHECK_EXPIRATION) {
                error_log('[Redis PHP Check] 跳过 PHP 版本检查 - 距离上次检查: ' . human_time_diff($last_check_time, time()));
                // 返回一个成功的响应(避免重复请求)
                return array(
                    'body' => '{"success":true}',
                    'response' => array('code' => 200, 'message' => 'OK'),
                    'headers' => array(),
                );
            }
        }
    }
    
    return $preempt;
}, 10, 3);

/**
 * 在 HTTP 响应时设置 Redis 缓存(PHP 检查)
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_filter('http_response', function($response, $args, $url) {
    // 检查是否是 PHP 版本检查的 API 请求
    if (strpos($url, 'api.wordpress.org/core/serve-happy/') !== false && !is_wp_error($response)) {
        $existing_cache = wp_cache_get(WP_PHP_VERSION_CHECK_KEY);
        if ($existing_cache === false) {
            wp_cache_set(
                WP_PHP_VERSION_CHECK_KEY,
                time(),
                '',
                WP_UPDATE_CHECK_EXPIRATION
            );
            error_log('[Redis PHP Check] wp_check_php_version 执行,已设置 Redis 缓存');
        }
    }
    return $response;
}, 10, 3);

/**
 * 监控 wp_check_php_version 的执行,设置 Redis 缓存
 * 作为备用方案,通过动作钩子捕获
 * 注意:只有在缓存不存在时才设置,避免频繁刷新时重置缓存时间
 */
add_action('wp_check_php_version', function() {
    $existing_cache = wp_cache_get(WP_PHP_VERSION_CHECK_KEY);
    if ($existing_cache === false) {
        wp_cache_set(
            WP_PHP_VERSION_CHECK_KEY,
            time(),
            '',
            WP_UPDATE_CHECK_EXPIRATION
        );
        error_log('[Redis PHP Check] wp_check_php_version 执行,已设置 Redis 缓存');
    }
}, 10);


// ==========================================
// 兜底方案:在 shutdown 时检查并设置缓存
// ==========================================

/**
 * 在页面加载结束时检查
 * 如果更新检查执行了但没有设置缓存,在这里设置
 */
add_action('shutdown', function() {
    // 检查核心更新
    $core_transient = get_site_transient('update_core');
    if ($core_transient && is_object($core_transient)) {
        $last_check = wp_cache_get(WP_CORE_UPDATE_CHECK_KEY);
        if ($last_check === false && isset($core_transient->last_checked)) {
            wp_cache_set(
                WP_CORE_UPDATE_CHECK_KEY,
                $core_transient->last_checked,
                '',
                WP_UPDATE_CHECK_EXPIRATION
            );
        }
    }
    
    // 检查主题更新
    $themes_transient = get_site_transient('update_themes');
    if ($themes_transient && is_object($themes_transient)) {
        $last_check = wp_cache_get(WP_THEMES_UPDATE_CHECK_KEY);
        if ($last_check === false && isset($themes_transient->last_checked)) {
            wp_cache_set(
                WP_THEMES_UPDATE_CHECK_KEY,
                $themes_transient->last_checked,
                '',
                WP_UPDATE_CHECK_EXPIRATION
            );
        }
    }
    
    // 检查插件更新
    $plugins_transient = get_site_transient('update_plugins');
    if ($plugins_transient && is_object($plugins_transient)) {
        $last_check = wp_cache_get(WP_PLUGINS_UPDATE_CHECK_KEY);
        if ($last_check === false && isset($plugins_transient->last_checked)) {
            wp_cache_set(
                WP_PLUGINS_UPDATE_CHECK_KEY,
                $plugins_transient->last_checked,
                '',
                WP_UPDATE_CHECK_EXPIRATION
            );
        }
    }
    
    // 浏览器和 PHP 检查可能没有标准的 transient,使用当前时间
    // 但这里我们已经在动作钩子中处理了
}, 999);


// ==========================================
// 在后台显示状态(可选,用于调试)
// ==========================================

add_action('admin_notices', function() {
    // 只在管理员权限时显示
    if (!current_user_can('manage_options')) {
        return;
    }
    
    // 只在 URL 参数包含 debug_all_checks 时显示
    // 或者在后台任何页面都显示(方便调试)
    $show_debug = isset($_GET['debug_all_checks']) || 
                  (defined('WP_DEBUG') && WP_DEBUG && defined('WP_DEBUG_DISPLAY') && WP_DEBUG_DISPLAY);
    
    if (!$show_debug) {
        return;
    }
    
    // 确保常量已定义
    if (!defined('WP_CORE_UPDATE_CHECK_KEY')) {
        return;
    }
    
    $checks = array(
        'WordPress 核心版本检查' => WP_CORE_UPDATE_CHECK_KEY,
        '主题更新检查' => WP_THEMES_UPDATE_CHECK_KEY,
        '插件更新检查' => WP_PLUGINS_UPDATE_CHECK_KEY,
        '浏览器版本检查' => WP_BROWSER_VERSION_CHECK_KEY,
        'PHP 版本检查' => WP_PHP_VERSION_CHECK_KEY,
    );
    
    echo '<div class="notice notice-info is-dismissible">';
    echo '<p><strong>Redis 更新检查状态:</strong></p>';
    echo '<ul style="margin-left: 20px;">';
    
    foreach ($checks as $name => $key) {
        $last_check = wp_cache_get($key);
        
        if ($last_check !== false && is_numeric($last_check)) {
            $time_since = time() - $last_check;
            $hours_remaining = (WP_UPDATE_CHECK_EXPIRATION - $time_since) / 3600;
            
            echo '<li><strong>' . esc_html($name) . ':</strong>';
            echo '上次检查:' . date('Y-m-d H:i:s', $last_check) . ' | ';
            echo '距离现在:' . human_time_diff($last_check, time()) . ' | ';
            
            if ($hours_remaining > 0) {
                echo '剩余时间:' . number_format($hours_remaining, 1) . ' 小时 | ';
            } else {
                echo '已过期 | ';
            }
            
            echo '下次检查:' . date('Y-m-d H:i:s', $last_check + WP_UPDATE_CHECK_EXPIRATION);
            echo '</li>';
        } else {
            echo '<li><strong>' . esc_html($name) . ':</strong>未设置缓存,下次访问后台将执行检查。</li>';
        }
    }
    
    echo '</ul>';
    echo '<p><small>提示:访问 <code>?debug_all_checks=1</code> 可查看此状态,或启用 WP_DEBUG 模式</small></p>';
    echo '</div>';
}, 10); // 使用优先级 10,确保在 admin_init 之后执行


// ==========================================
// 工具函数:手动清除所有缓存(用于测试)
// ==========================================

/**
 * 手动清除所有 Redis 缓存,强制下次检查更新
 * 访问:/?clear_all_check_cache=1
 */
add_action('init', function() {
    if (isset($_GET['clear_all_check_cache']) && current_user_can('manage_options')) {
        wp_cache_delete(WP_CORE_UPDATE_CHECK_KEY);
        wp_cache_delete(WP_THEMES_UPDATE_CHECK_KEY);
        wp_cache_delete(WP_PLUGINS_UPDATE_CHECK_KEY);
        wp_cache_delete(WP_BROWSER_VERSION_CHECK_KEY);
        wp_cache_delete(WP_PHP_VERSION_CHECK_KEY);
        wp_die('所有 Redis 缓存已清除,下次访问后台将执行更新检查。', '缓存已清除', array('back_link' => true));
    }
}, 1);

也可以直接用插件,插件地址:

https://github.com/obaby/baby-wp-update-config-tool

⚠ 重要提示

在使用本插件之前,您需要首先配置 WordPress 的 Redis 缓存功能。

本插件依赖于 Redis 缓存系统来存储更新检查的时间戳。如果您的 WordPress 站点未配置 Redis 缓存,插件将无法正常工作。请确保:

  1. 已安装并配置 Redis 服务器
  2. 已安装 WordPress Redis 缓存插件(如 Redis Object Cache、WP Redis 等)
  3. Redis 缓存功能已正常启用并运行

如果 Redis 缓存未配置,插件在初始化时会记录警告信息,但不会阻止插件运行。

  •