Redis7

Redis7总结

1.安装

1.环境准备

sudo yum -y install gcc-c++

2.解压

下载地址:https://download.redis.io/releases/redis-7.0.0.tar.gz

tar -zxvf redis-7.0.0.tar.gz -C /opt/module/

3.修改配置

[atguigu@hadoop102 src]$ vim Makefile

#修改如下

PREFIX?=/home/atguigu

4.执行

[atguigu@hadoop104 redis-7.0.0]$ pwd
/opt/module/redis-7.0.0
[atguigu@hadoop104 redis-7.0.0]$ make && make install
​

检查:Hint: It's a good idea to run 'make test' ;)出现这个说明安装成功

5.修改redis配置文件

[atguigu@hadoop105 redis-7.0.0]$ mkdir myredis
[atguigu@hadoop105 myredis]$ cp /opt/module/redis-7.0.0/redis.conf /opt/module/redis-7.0.0/myredis/
[atguigu@hadoop105 myredis]$ vim redis.conf 

1 默认daemonize no 改为 daemonize yes

2 默protected-mode yes 改为 protected-mode no

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

4 添加redis密码 改为requirepass你自己设置的密码 ----这个是密码 例如设置为123456


6.启动服务端

[atguigu@hadoop105 myredis]$ redis-server /opt/module/redis-7.0.0/myredis/redis.conf

7.启动客户端

[atguigu@hadoop105 myredis]$ redis-cli -a 123456

-a 是密码

8.测试

[atguigu@hadoop105 myredis]$ ps -ef | grep redis
127.0.0.1:6379> ping
PONG

2.10大数据类型

1.官网命令查询

英文Commands | Redis

中文Redis命令中心(Redis commands) -- Redis中国用户组(CRUG)

2.10大数据类型总体介绍

redis字符串(String):

string是redis最基本的类型,一个key对应一个value。

string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

redis列表(List):

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

redis哈希表(Hash):

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

Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)

redis集合(Set):

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,集合对象的编码可以是 intset 或者 hashtable。

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

集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)

redis有序集合(ZSet):

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

zset的成员是唯一的,但分数(score)却可以重复。

zset集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1

redis地理空间(GEO):

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,包括

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

redis基数统计(HyperLogLog):

HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定且是很小的。

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

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

redis位图(bitmap):

由0和1状态表现的二进制位的bit数组

redis位域(bitfield):

通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果。

说白了就是通过bitfield命令我们可以一次性对多个比特位域进行操作。

redis流(Stream):

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

Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。

简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。

而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失

3.redis键(key)常用命令

keys * 查看当前库所有的key

exists key 判断某个key是否存在

type key 查看你的key是什么类型

del key 删除指定的key数据

unlink key 非阻塞删除,仅仅将keys从keyspace元数据中删除,真正的删除会在后续异步中操作。

ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期

expire key 秒钟 为给定的key设置过期时间

move key dbindex【0-15】 将当前数据库的 key移动到给定的数据库 db 当中

select dbindex 切换数据库【0-15】,默认为0

dbsize 查看当前数据库key的数量

flushdb 清空当前库

flushall 通杀全部库

4.10大数据类型细说

1.redis字符串(String)

最常用

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

set key value

get key

同时设置/获取多个键值

MSET key value [key value ....]

MGET key [key ....]

mset/mget/msetnx

获取指定区间范围内的值

getrange/setrange

数值增减(一定要是数字才能进行加减)

递增数字:INCR key

增加指定的整数:INCRBY key increment

递减数值:DECR key

减少指定的整数:DECRBY key decrement

获取字符串长度和内容追加

STRLEN key

APPEND key value

getset(先get再set)

2.redis列表(List)

一个双端链表的结构,容量是2的32次方减1个元素,大概40多亿,主要功能有push/pop等,一般用在栈、队列、消息队列等场景。

lpush/rpush/lrange

lpop/rpop

lindex,按照索引下标获得元素(从上到下)

llen,获取列表中元素的个数

lrem key 数字N 给定值v1 解释(删除N个值等于v1的元素)

ltrim key 开始index 结束index,截取指定范围的值后再赋值给key

rpoplpush 源列表 目的列表

lset key index value

linsert key before/after 已有值 插入的新值

3.redis哈希(Hash)

hset/hget/hmset/hmget/hgetall/hdel

hlen 获取某个key内的全部数量

hexists key 在key里面的某个值的key

hkeys/hvals

hincrby/hincrbyfloat

hsetnx

4.redis集合(Set)

SADD key member [member ...] 添加元素

SMEMBERS key 遍历集合中的所有元素

SISMEMBER key member 判断元素是否在集合中

SREM key member [member ...] 删除元素

scard,获取集合里面的元素个数

SRANDMEMBER key [数字] 从集合中随机展现设置的数字个数元素,元素不删除

SPOP key [数字] 从集合中随机弹出一个元素,出一个删一个

smove key1 key2 在key1里已存在的某个值 将key1里已存在的某个值赋给key2

5.redis有序集合Zset(sorted set)

ZADD key score member [score member ...] 添加元素

ZRANGE key start stop [WITHSCORES] 按照元素分数从小到大的顺序 返回索引从start到stop之间的所有元素

zrevrange 反转

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 获取指定分数范围的元素 ( 不包含

ZSCORE key member 获取元素的分数

ZCARD key 获取集合中元素的数量

zrem key 某score下对应的value值,作用是删除元素

ZINCRBY key increment member 增加某个元素的分数

ZCOUNT key min max 获得指定分数范围内的元素个数

ZMPOP 从键名列表中的第一个非空排序集中弹出一个或多个元素,它们是成员分数对

zrank key values值,作用是获得下标值

zrevrank key values值,作用是逆序获得下标值

6.redis位图(bitmap)

setbit key offset value setbit 键 偏移位 只能零或者1 Bitmap的偏移量是从零开始算的

getbit key offset 获取

strlen 统计字节数占用多少

bitcount 全部键里面含有1的有多少个?

bitop

7.redis基数统计(HyperLogLog)

pfadd key element [element ...] 添加指定元素到 HyperLogLog 中。

PFCOUNT key[key_...] 返回给定 HyperLogLog 的基数估算值。

PFMERGE destkey sourcekey [sourcekey ...] 将多个HyperLogLog 合并为一个HyperLogLog

8.redis地理空间(GEO)

如何获得某个地址的经纬度: 拾取坐标系统

GEOADD添加经纬度坐标 GEOADD city 116.403963 39.915119 "天安门" 116.403414 39.924091 "故宫" 116.024067 40.362639 "长城"

GEOPOS返回经纬度 GEOPOS city 天安门 故宫 长城

GEOHASH返回坐标的geohash表示

GEODIST .两个位置之间距离 GEODIST city 天安门 故宫 km

GEORADIUS GEORADIUS city 116.418017 39.914402 10 km withdist withcoord count 10 withhash desc GEORADIUS city 116.418017 39.914402 10 km withdist withcoord withhash count 10 desc

GEORADIUSBYMEMBER

9.redis流(Steam)

是什么:Redis版的MQ消息中间件+阻塞队列

2种方案:1.List 实现消息队列 ---------List 实现方式其实就是点对点的模式

2.(Pub/Sub)

队列相关命令:

XADD 添加消息到队列末尾 消息ID必须要比上个 ID 大,默认用星号表示自动生成规矩

XRANGE 用于获取消息列表(可以指定范围),忽略删除的消息,start 表示开始值,-代表最小值,end 表示结束值,+代表最大值,count 表示最多获取多少个值

XREVRANGE 与XRANGE 的区别在于,获取消息列表元素的方向是相反的,end在前,start在后

XDEL 删除队列中的元素

XLEN 用于获取Stream 队列的消息的长度

XTRIM 用于对Stream的长度进行截取,如超长会进行截取,允许的最大长度,对流进行修剪限制长度,允许的最小id,从某个id值开始比该id值小的将会被抛弃

XREAD 用于获取消息(阻塞/非阻塞),只会返回大于指定ID的消息,COUNT 最多读取多少条消息, BLOCK 是否已阻塞的方式读取消息,默认不阻塞,如果milliseconds设置为0,表示永远阻塞

消息组相关命令:

XGROUP CREATE 用于创建消费者组

XREADGROUP GROUP “>”,表示从第一条尚未被消费的消息开始读取,消费组groupA内的消费者consumer1从mystream消息队列中读取所有消息,但是,不同消费组的消费者可以消费同一条消息

XPENDING 查询每个消费组内所有消费者「已读取、但尚未确认」的消息,查看某个消费者具体读取了哪些数据

XACK 向消息队列确认消息处理已完成

XINFO 用于打印Stream\Consumer\Group的详细信息

10.redis位域(bitfield)

没有卵用,了解就行

是什么:将一个Redis字符串看作是一个由二进制位组成的数组并能对变长位宽和任意没有字节对齐的指定整型位域进行寻址和修改

命令:

GET 返回指定的位域 SET 设置指定位域的值并返回它的原值 INCRBY 自增或自减(如果increment为负数) 指定位域的值并返回它的新值 还有一个命令通过设置溢出行为来改变调用INCRBY指令的后序操作: OVERFLOW [WRAPSAT FAIL] 当需要一个整型时,有符号整型需在位数前加i,无符号在位数前加u。例如,u8是一个8位的无符号整型,i16是一个16位的有符号整型

溢出控制OVERFLOW [WRAP|SAT|FAIL] WRAP: 使用回绕(wrap around)方法处理有符号整数和无符号整数的溢出情况,SAT: 使用饱和计算(saturation arithmetic)方法处理溢出,下溢计算的结果为最小的整数值,而上溢计算的结果为最大的整数值,FAIL: 命令将拒绝执行那些会导致上溢或者下溢情况出现的计算,并向用户返回空值表示计算未被执行

3.Redis持久化

持久化:就是将redis内存中的数据保存到磁盘中,可以通过RDB或者AOP或者RDB+AOP三种方式

1.RDB

RDB(Redis DataBase): RDB 持久性以指定的时间间隔执行数据集的时间点快照

快照规则:(去redis.config中去找)

redis7前:

save 900 1: 每隔 900s(15min),如果有超过 个 1key 发生了变化,就写一份新的 RDB 文件save

300 10: 每隔 300s(5min),如果有超过 10 个 key 发生了变化,就写一份新的 RDB 文件

save 60 10000:每隔 60s(lmin),如果有超过 10000 个 key 发生了变化,就写一份新的 RDB 文件

reddis7:

save 900 1: 每隔 3600s(一小时),如果有超过 个 1key 发生了变化,就写一份新的 RDB 文件save

300 10: 每隔 300s(5min),如果有超过 100 个 key 发生了变化,就写一份新的 RDB 文件

save 60 10000:每隔 60s(lmin),如果有超过 10000 个 key 发生了变化,就写一份新的 RDB 文件

1.使用RDB备份数据案例

RDB备份数据有两种方式:自动触发和手动触发

自动触发

需求:本次案例5秒2次修改

1.更改配置(5秒2次)(5秒或者两次更改都没有触发)

[atguigu@hadoop105 myredis]$ pwd
/opt/module/redis-7.0.0/myredis
[atguigu@hadoop105 myredis]$ vim redis.conf 

添加:

save  5  2

2.修改dump文件保存路径

[atguigu@hadoop105 myredis]$ vim redis.conf 

修改:(这个路径一定要存在)

dir /myredis/dumpfiles

3.修改dump文件名称

[atguigu@hadoop105 myredis]$ vim redis.conf 

修改:

dbfilename  dump6379.rdb

4.重启redis

[atguigu@hadoop105 myredis]$ redis-server /opt/module/redis-7.0.0/myredis/redis.conf 

5.如何恢复

将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可

注意:执行flushall/flushdb命令也会产生dump.rdb文件,但里面是空的,无意义

6.生产注意

物理恢复,一定服务和备份分机隔离

手动触发

手动触发有两种命令方式:save和bgsave

注意:在生产环境中使用bgsave,不用save

save

在主程序中执⾏会阻塞当前redis服务器,直到持久化工作完成 执行save命令期间,Redis不能处理其他命令,线上禁止使用

案例:

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> save
OK

bgsave

Redis会在后台异步进行快照操作,不阻塞快照同时还可以响应客户端请求,该触发方式会fork一个子进程由子进程复制持久化过程

案例:

127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> bgsave
Background saving started
2.RDB优势

1.适合大规模的数据恢复

2.按照业务定时备份

3.对数据完整性和一致性要求不高

4.RDB 文件在内存中的加载速度要比 AOF 快得多

3.RDB劣势

1.在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失从当前至最近一次快照期间的数据,快照之间的数据会丢失(说白了:就是会丢失数据)

2.内存数据的全量同步,如果数据量太大会导致I/0严重影响服务器性能

3.RDB依赖于主进程的fork,在更大的数据集中,这可能会导致服务请求的瞬间延迟。fork的时候内存中的数据被克隆了一份,大致2倍的膨胀性,需要考虑

4.如何检查修复dump.rdb文件

redis-check-rdb 后面跟rdb的文件

redis-check-rdb  /myredis/dumpfiles/dump6379.rdb
5.哪些情况会触发RDB快照

1.配置文件中默认的快照配置

2.手动save/bgsave命令

3.执行flushall/flushdb命令也会产生dump.rdb文件,但里面是空的,无意义

4.执行shutdown且没有设置开启AOF持久化

5.主从复制时,主节点自动触发

6.如何禁用RDB

方法一:(一次性的)

动态所有停止RDB保存规则的方法:redis-cli config set save ""

方法二:(永久)

7.RDB优化配置项详解

save 保存

dbfilename 文件名字

dir 文件地址

stop-writes-on-bgsave-error 默认yes如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制这种不一致,那么在快照写入失败时,也能确保redis继续接受新的写请求

rdbcompression 默认yes对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能

rdbchecksum 默认yes在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能

rdb-del-sync-files rdb-del-sync-files:在没有持久性的情况下删除复制中使用的RDB文件启用。默认情况下no,此选项是禁用的。

2.AOF

以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

默认情况下,redis是没有开启AOF(append only file)的。 开启AOF功能需要设置配置:appendonly yes

1.AOF持久化工作流程

2.AOF缓冲区三种写回策略

Always:同步写回,每个写命令执行完立刻同步地将日志写回磁盘

everysec:每秒写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔1秒把缓冲区中的内容写入磁盘

no:操作系统控制的写回,每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统 决定何时将缓冲区内容写回磁盘

总结:

3.配置文件说明

1.打开AOF

[atguigu@hadoop105 myredis]$ vim redis.conf

修改:

appendonly  yes

2.默认写回策略(默认就行,不用改)

[atguigu@hadoop105 myredis]$ vim redis.conf

不用改(默认就行):

appendfsync  eveerysec

3.文件保存路径

redis6

AOF保存文件的位置和RDB保存文件的位置一样,都是通过redis.conf配置文件的 dir 配置

redis7

dir + appenddirname

说白了:就是在dir目录下还会加一个目录appenddirname

4.保存名称

redis6

有且只有一个(appendonly.aof)

redis7

Multi Part AOF的设计,有三个文件:base基本文件,incr增量文件,manifest清单文件

5.异常修复AOF文件

redis-check-aof --fix 进行修复

例:

redis-check-aof --fix appendonly.aof.1.incr.aof
4.AOF优点

更好的保护数据不丢失 、性能高、可做紧急恢复

5.AOF劣势

相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb

aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同

6.AOF重写机制

是什么

启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。

触发机制

自动触发:

满足配置文件中的选项后,Redis会记录上次重写时的AOF大小, 默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时

官网默认:(redis.config)

auto-aof-rewrite-percentage  100
auto-aof-rewrite-min-size  64mb

注意:同时满足,且的关系才会触发

手动触发:

bgrewriteaof命令

127.0.0.1:6379> BGREWRITEAOF

案例说明:

1.前期准备

开启aof

[atguigu@hadoop105 myredis]$ vim redis.conf

修改

appendonly  yes

2.修改自动触发规则(生产环境要改大一点)

auto-aof-rewrite-percentage  100
auto-aof-rewrite-min-size   1K  #生产环境改大一点

3.关闭混合

aof-use-rdb-preamble  no

4.手动触发

127.0.0.1:6379> BGREWRITEAOF
7.常用配置文件参数

一般不用修改,用默认的足够了

3.RDB-AOF混合持久化

1.怎么选

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储

AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些,命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.

2.同时开启两种持久化方式

在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.

RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),留着rdb作为一个万一的手段。

3.RDB+AOF混合方式

结合了RDB和AOF的优点,既能快速加载又能避免丢失过多的数据。

1 开启混合方式设置 设置aof-use-rdb-preamble的值为 yes yes表示开启,设置为no表示禁用 2 RDB+AOF的混合方式---------> 结论:RDB镜像做全量持久化,AOF做增量持久化 先使用RDB进行快照存储,然后使用AOF持久化记录所有的写操作,当重写策略满足或手动触发重写的时候,将最新的数据存储为新的RDB记录。这样的话,重启服务的时候会从RDB和AOF两部分恢复数据,既保证了数据完整性,又提高了恢复数据的性能。简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式。----》AOF包括了RDB头部+AOF混写。

4.纯缓存模式

说白了:同时关闭RDB+AOF,就是让redis是做缓存,不存储到磁盘中

1.关闭RDB

save "" 禁用rdb,禁用rdb持久化模式下,我们仍然可以使用命令save、bgsave生成rdb文件

2.关闭AOF

appendonly no 禁用aof ,禁用aof持久化模式下,我们仍然可以使用命令bgrewriteaof生成aof文件

4.Redis事务

1.是什么

可以一次执行多个命令,本质是一组命令的集合。一个事务中的 所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞

2.能干嘛

一个队列中,一次性、顺序性、排他性的执行一系列命令

3.与传统数据库对比

1 单独的隔离操作Redis的事务仅仅是保证事务里的操作会被连续独占的执行,redis命令执行是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的
2 没有隔离级别的概念因为事务提交前任何指令都不会被实际执行,也就不存在"事务内的查询要看到事务里的更新,在事务外查询不能看到"这种问题了
3不保证原子性Redis的事务不保证原子性,也就是不保证所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力
4 排它性Redis会保证一个事务内的命令依次执行,而不会被其它命令插入

4.事务常用命令

命令描述
DISCARD取消事务,放弃执行事务块内的所有命令
EXEC执行所有事务块内的命令
MULTI标记一个事务块的开始。
UNWATCH取消 WATCH 命令对所有 key 的监视
NATCH kev [key ...]监视一个(或多个)key,如果在事务执行之前这个(或这些)key 被其他命令所改动,那么事务将被打断.

5.案例

正常执行

MULTI

EXEC

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 vs
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
127.0.0.1:6379> get k1
"v1"
放弃事务

MULTI

DISCARD

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k11 v11
QUEUED
127.0.0.1:6379(TX)> set k22 v22
QUEUED
127.0.0.1:6379(TX)> set k33 v33
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> get k11
(nil)
全体连坐

意思是:一个命令失败(编译的时候失败)全体失败

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k111 v111
QUEUED
127.0.0.1:6379(TX)> set k222 v333
QUEUED
127.0.0.1:6379(TX)> set k333
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> set k111
(error) ERR wrong number of arguments for 'set' command
冤头债主

意思是:一个命令失败(编译的时候要通过),不会回滚,不影响其它命令执行

127.0.0.1:6379> get k3
"vs"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k11 v11
QUEUED
127.0.0.1:6379(TX)> set k22 v22
QUEUED
127.0.0.1:6379(TX)> set k33 v33
QUEUED
127.0.0.1:6379(TX)> INCR k3
QUEUED
127.0.0.1:6379(TX)> set k44 v44
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
3) OK
4) (error) ERR value is not an integer or out of range
5) OK
127.0.0.1:6379> get k11
"v11"
watch监控

Redis使用Watch来提供乐观锁定,类似于CAS(Check-and-Set)

watch

watch命令是一种乐观锁的实现,Redis在修改的时候会检测数据是否被更改,如果更改了,则执行失败

unwatch

取消监控

总结:

一旦执行了exec之前加的监控锁都会被取消掉了

当客户端连接丢失的时候(比如退出链接),所有东西都会被取消监视

6.总结

开启:以MULTI开始一个事务

入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面

执行:由EXEC命令触发事务

5.Redis管道

管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完完毕后,通过一条响应一次性将结果返回,通过减少客户端与redis的通 信次数来实现降低往返延时时间。pipeline实现的原理是队列,先进先出特性就保证数据的顺序性。

案例

[atguigu@hadoop105 myredis]$ cat cmd.txt 
set k100 v100
set k200 v200
hset k300 name yjh
hset k300 age 20
hset k300 gender male
lpush list 1 2 3 4 5
[atguigu@hadoop105 myredis]$ cat cmd.txt | redis-cli -a 123456 --pipe
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 6

总结

Pipeline与原生批量命令对比

原生批量命令是原子性(例如:mset, mget),pipeline是非原子性

原生批量命令一次只能执行一种命令,pipeline支持批量执行不同命令

原生批命令是服务端实现,而pipeline需要服务端与客户端共同完成

Pipeline与事务对比

事务具有原子性,管道不具有原子性

管道一次性将多条命令发送到服务器,事务是一条一条的发,事务只有在接收到exec命令后才会执行,管道不会

执行事务时会阻塞其他命令的执行,而执行管道中的命令时不会

使用Pipeline注意事项

pipeline缓冲的指令只是会依次执行,不保证原子性,如果执行中指令发生异常,将会继续执行后续的指令

使用pipeline组装的命令个数不能太多,不然数据量过大客户端阻塞的时间可能过久,同时服务端此时也被迫回复一个队列答复,占用很多内存

6.Redis发布订阅(没有卵用)(了解)

是一种消息通信模式:发送者(PUBLISH)发送消息,订阅者(SUBSCRIBE)接收消息,可以实现进程间的消息传递

Redis可以实现消息中间件MQ的功能,通过发布订阅实现消息的引导和分流。 仅代表我个人,不推荐使用该功能,专业的事情交给专业的中间件处理,redis就做好分布式缓存功能

7.Redis复制(replica)

1.是什么

就是主从复制,master以写为主,Slave以读为主

当master数据变化的时候,自动将新的数据异步同步到其它slave数据库

2.能干嘛

读写分离

容灾恢复

数据备份

水平扩容支撑高并发

3.基本操作命令

配从(库)不配主(库)

权限细节,重要:

master如果配置了requirepass参数,需要密码登陆

那么slave就要配置masterauth来设置校验密码,否则的话master会拒绝slave的访问请求

基本操作命令(命令是临时的,redis重新启动后就没有作用了)

info replication 可以查看复制节点的主从关系和配置信息

replicaof 主库IP 主库端口 一般写入进redis.conf配置文件内

slaveof 主库IP 主库端口 每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件,在运行期间修改slave节点的信息,如果该数据库已经是某个主数据库的从数据库,那么会停止和原主数据库的同步关系转而和新的主数据库同步,重新拜码头

slaveof no one 使当前数据库停止与其他数据库的同步,转成主数据库,自立为王

4.主从复制

一主两仆(一主两从)

架构如下:

意思是一台主机,两台重机,主机负责写数据(也可以读数据),从机复制读数据(不能写,写就会报错)

vim redis.conf

主机配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6379

指定当前工作目录,dir: dir /opt/module/redis-7.0.0/myredis

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6379.log

进程id pid文件名字,pidfile:pidfile /var/run/redis_6379.pid

密码:requirepass 123456

dump.rdb名字:dbfilename dump6379.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

从机一配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6380

指定当前工作目录,dir: /opt/module/redis-7.0.0/myredis

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6380.log

进程id pid文件名字,pidfile:pidfile /var/run/redis_6380.pid

密码:requirepass 123456

dump.rdb名字:dbfilename dump6380.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

主机的地址和端口号:replicaof 39.100.65.111 6379

主机的密码: masterauth 123456

从机二配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6381

指定当前工作目录,dir: /opt/module/redis-7.0.0/myredis

进程id pid文件名字,pidfile:pidfile /var/run/redis_6381.pid

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6381.log

密码:requirepass 123456

dump.rdb名字:dbfilename dump6381.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

主机的地址和端口号:replicaof 39.100.65.111 6379

主机的密码: masterauth 123456

启动

redis-server /opt/module/redis-7.0.0/myredis/redis.conf

进入客户端(-p 是端口号,从机的端口号记得修改)

redis-cli -a 123456 -p 6379

测试

在每台机器上执行

127.0.0.1:6380> info replication

在主机上添加数据

[atguigu@hadoop103 myredis]$ redis-cli -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"

在从机上读取数据(两台哟)

[atguigu@hadoop104 myredis]$ redis-cli -a 123456 -p 6380
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6380> ping
PONG
127.0.0.1:6380> get k1
"v1"

注意

如果是买的云服务器,记得开端口,设置安全组。

薪火相传

意思是:比如:第一台是主机,第二台是第一台的从机同时也是第三台的从机,第三台是第二台的从机,这样跟一主两从比起来的话,主机连接的机器少,效率也高,当然第二台机器还是不写操作

架构:

主机配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6379

指定当前工作目录,dir: dir /opt/module/redis-7.0.0/myredis

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6379.log

进程id pid文件名字,pidfile:pidfile /var/run/redis_6379.pid

密码:requirepass 123456

dump.rdb名字:dbfilename dump6379.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

从机一配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6380

指定当前工作目录,dir: /opt/module/redis-7.0.0/myredis

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6380.log

进程id pid文件名字,pidfile:pidfile /var/run/redis_6380.pid

密码:requirepass 123456

dump.rdb名字:dbfilename dump6380.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

主机的地址和端口号:replicaof 39.100.65.111 6379

主机的密码: masterauth 123456

从机二配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6381

指定当前工作目录,dir: /opt/module/redis-7.0.0/myredis

进程id pid文件名字,pidfile:pidfile /var/run/redis_6381.pid

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6381.log

密码:requirepass 123456

dump.rdb名字:dbfilename dump6381.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

主机的地址和端口号:replicaof 39.100.71.195 6380

主机的密码: masterauth 123456

启动

redis-server /opt/module/redis-7.0.0/myredis/redis.conf

进入客户端(-p 是端口号,从机的端口号记得修改)

redis-cli -a 123456 -p 6379

测试

在每台机器上执行

127.0.0.1:6380> info replication

在主机上添加数据

[atguigu@hadoop103 myredis]$ redis-cli -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"

在从机上读取数据(两台哟)

[atguigu@hadoop104 myredis]$ redis-cli -a 123456 -p 6380
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6380> ping
PONG
127.0.0.1:6380> get k1
"v1"

注意

如果是买的云服务器,记得开端口,设置安全组。

反客为主

命令:slaveof no one

实验:

准备:

127.0.0.1:6381> info replication
# Replication
role:slave
master_host:39.100.71.195
master_port:6380
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_read_repl_offset:3766
slave_repl_offset:3766
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:c889b8470b98e018485f8327076290742a80cea7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3766
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:631
repl_backlog_histlen:3136

反客为主:

127.0.0.1:6381> slaveof no one
OK

结果

127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:e49e03745614b9996d689071c26526c8a0168dff
master_replid2:c889b8470b98e018485f8327076290742a80cea7
master_repl_offset:4364
second_repl_offset:4365
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:631
repl_backlog_histlen:3734

4.复制原理和工作流程

slave启动,同步初请

slave启动成功连接到master后会发送一个sync命令

slave首次全新连接master,一次完全同步(全量复制)将被自动执行,slave自身原有数据会被master数据覆盖清除

首次连接,全量复制

master节点收到sync命令后会开始在后台保存快照(即RDB持久化,主从复制时会触发RDB), 同时收集所有接收到的用于修改数据集命令缓存起来,master节点执行RDB持久化完后, master将rdb快照文件和所有缓存的命令发送到所有slave,以完成一次完全同步

而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中,从而完成复制初始化

心跳持续,保持通信

10秒一下心跳,repl-ping-replica-period 10

进入平稳,增量复制

Master继续将新的所有收集到的修改命令自动依次传给slave,完成同步

从机下线,重连续传

master会检查backlog里面的offset,master和slave都会保存一个复制的offset还有一个masterId, offset是保存在backlog中的。Master只会把已经复制的offset后面的数据复制给Slave,类似断点续传

5复制的缺点

复制延时,信号衰减

master挂了如何办?

默认情况下,不会在slave节点中自动重选一个master

那每次都要人工干预?无人值守安装变成刚需

8.Redis哨兵(sentinel)

1.是什么

吹哨人巡查监控后台master主机是否故障,如果故障了根据投票数自动 将某一个从库转换为新主库,继续对外服务

官网

https://redis.io/docs/management/sentinel/

2.作用

主从监控:监控主从redis库运行是否正常

消息通知:哨兵可以将故障转移的结果发送给客户端

故障转移:如果Master异常,则会进行主从切换,将其中一个Slave作为新Master

配置中心:客户端通过连接哨兵来获得当前Redis服务的主节点地址

3.案例

架构

一共六台服务器,三台哨兵,一台master,两台slave

案例架构:

47.92.198.6 哨兵一

47.92.210.90 哨兵二

47.92.141.20 哨兵三

39.100.77.232 master

47.92.66.95 slave

39.100.74.89 slave

1.6台服务器安装redis

请看第一节中的安装

2.配置一主两从
[atguigu@iZ8vb1a9girxulrfpocsdwZ myredis]$ pwd
/opt/module/redis-7.0.0/myredis
[atguigu@iZ8vb1a9girxulrfpocsdwZ myredis]$ vim redis.conf 

主机配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6379

指定当前工作目录,dir: dir /opt/module/redis-7.0.0/myredis

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6379.log

进程id pid文件名字,pidfile:pidfile /var/run/redis_6379.pid

密码:requirepass 123456

dump.rdb名字:dbfilename dump6379.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

主机的密码: masterauth 123456

从机一配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6380

指定当前工作目录,dir: /opt/module/redis-7.0.0/myredis

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6380.log

进程id pid文件名字,pidfile:pidfile /var/run/redis_6380.pid

密码:requirepass 123456

dump.rdb名字:dbfilename dump6380.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

主机的地址和端口号:replicaof 39.100.65.111 6379

主机的密码: masterauth 123456

从机二配置:

开启:daemonize yes

注释掉:bind 127.0.0.1

关闭:protected-mode no

指定端口:port 6381

指定当前工作目录,dir: /opt/module/redis-7.0.0/myredis

进程id pid文件名字,pidfile:pidfile /var/run/redis_6381.pid

log文件名字,logfile:logfile /opt/module/redis-7.0.0/myredis/6381.log

密码:requirepass 123456

dump.rdb名字:dbfilename dump6381.rdb

aof文件,appendfilename: appendonly no (这一步可以开启,也可以关闭)

主机的地址和端口号:replicaof 39.100.65.111 6379

主机的密码: masterauth 123456

3.哨兵配置

哨兵一配置

[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ pwd
/opt/module/redis-7.0.0/myredis
[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ vim sentinel26379.log 
bind 0.0.0.0
daemonize yes
protected-mode no
port 26379
logfile "/opt/module/redis-7.0.0/myredis/sentinel26379.log"
pidfile /var/run/redis-sentinel26379.pid
dir /opt/module/redis-7.0.0/myredis
#ip地址写自己的,端口写对, 2代表投票数
sentinel monitor mymaster 39.100.77.232 6379 2
sentinel auth-pass mymaster 123456

哨兵二配置

[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ pwd
/opt/module/redis-7.0.0/myredis
[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ vim sentinel26379.log 
bind 0.0.0.0
daemonize yes
protected-mode no
port 26380
logfile "/opt/module/redis-7.0.0/myredis/sentinel26380.log"
pidfile /var/run/redis-sentinel26380.pid
dir "/opt/module/redis-7.0.0/myredis"
#ip地址写自己的,端口写对, 2代表投票数
sentinel monitor mymaster 39.100.77.232 6379 2
sentinel auth-pass mymaster 123456

哨兵三配置

[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ pwd
/opt/module/redis-7.0.0/myredis
[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ vim sentinel26379.log 
bind 0.0.0.0
daemonize yes
protected-mode no
port 26381
logfile "/opt/module/redis-7.0.0/myredis/sentinel26381.log"
pidfile /var/run/redis-sentinel26381.pid
dir "/opt/module/redis-7.0.0/myredis"
#ip地址写自己的,端口写对, 2代表投票数
sentinel monitor mymaster 39.100.77.232 6379 2
sentinel auth-pass mymaster 123456
4.启动

1.先启动redis

先在master上启动

[atguigu@iZ8vb1a9girxulrfpocsdwZ myredis]$ redis-server /opt/module/redis-7.0.0/myredis/redis.conf
[atguigu@iZ8vb1a9girxulrfpocsdwZ myredis]$ redis-cli -a 123456 -p 6379

启动slave

启动方式与上面一样,需要改一下端口

2.启动哨兵(三台都启动)

[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ redis-sentinel sentinel26379.conf --sentinel
5.测试

在哨兵执行

[atguigu@iZ8vbe5bdplcubd9xhsumjZ myredis]$ ps -ef | grep redis
atguigu   6192     1  0 16:56 ?        00:00:02 redis-sentinel 0.0.0.0:26379 [sentinel]
atguigu  10571  1619  0 17:20 pts/1    00:00:00 grep --color=auto redis

在redis集群中执行

[atguigu@iZ8vb1a9girxulrfpocsdwZ myredis]$ ps -ef | grep redis
atguigu  10003     1  0 17:04 ?        00:00:01 redis-server *:6379
atguigu  10031  1160  0 17:21 pts/0    00:00:00 grep --color=auto redis

在master上执行

[atguigu@iZ8vb1a9girxulrfpocsdwZ myredis]$ redis-cli -a 123456 -p 6379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"

在slave上执行(报错就对了,因为从机不能写,只能多)

[atguigu@iZ8vb2f4j6zd7ve3rmlhi6Z myredis]$ redis-cli -a 123456 -p 6381
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6381> get k1
"v1"
127.0.0.1:6381> set k10 v10
(error) READONLY You can't write against a read only replica.

哨兵测试

就是将master中的redis进程杀死,如果slave变为新的master就算成功

4.哨兵运行流程和选举原理

当一个主从配置中的master失效之后,sentinel可以选举出一个新的master 用于自动接替原master的工作,主从配置中的其他redis服务器自动指向新的master同步数据。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换

运行流程,故障切换

SDown主观下线(Subjectively Down)

SDOWN(主观不可用)是单个sentinel自己主观上检测到的关于master的状态,从sentinel的角度来看, 如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。

sentinel配置文件中的down-after-milliseconds设置了判断主观下线的时间长度

ODown客观下线(Objectively Down)

ODOWN需要一定数量的sentinel,多个哨兵达成一致意见才能认为一个master客观上已经宕掉

哨兵领导者,兵王如何选出来的?

当主节点被判断客观下线以后,各个哨兵节点会进行协商,先选举出一个领导者哨兵节点(兵王)并由该领导者节点,也即被选举出的兵王进行failover(故障迁移)

哨兵领导者,兵王如何选出来的?-

raft算法

由兵王开始推动故障切换流程并选出一个新master

1.redis.conf文件中,优先级slave-priority或者replica-priority最高的从节点(数字越小优先级越高 )

2.复制偏移位置offset最大的从节点

3.最小Run ID的从节点,字典顺序,ASCII码

5.哨兵使用建议

哨兵节点的数量应为多个,哨兵本身应该集群,保证高可用

哨兵节点的数量应该是奇数

各个哨兵节点的配置应一致

如果哨兵节点部署在Docker等容器里面,尤其要注意端口的正确映射

哨兵集群+主从复制,并不能保证数据零丢失

9.Redis集群

1.是什么

由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集。

官网:Redis cluster specification | Redis

2.能干嘛

Redis集群支持多个Master,每个Master又可以挂载多个Slave,读写分离,支持数据的高可用,支持海量数据的读写存储操作

由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能

客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可

槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系

3.集群算法-分片-槽位slot

1.redis集群的槽位slot

集群的密钥空间被分成 16384 个槽,有效地设置了 16384 个主节点的集群大小上限 (但是,建议的最大节点大小约为 1000 个节点) 集群中的每个主节点处理 16384个哈希槽的一个子集。当没有集群重新配置正在进行时(即哈希槽从一个节点移动到另一个节点),集群是稳定的。当集群稳定时,单个哈希槽将由单个节点提供服务(但是,服务节点可以有一个或多个副本,在网络分裂或故障的情况下替换它,并且可以用于扩展 读取陈旧数据是可接受的操作)。

2.redis集群的分片

分片是什么使用Redis集群时我们会将存储的数据分散到多台redis机器上,这称为分片。简言之,集群中的每个Redis实例都被认为是整个数据的一个分片。如何找到给定key的分片为了找到给定key的分片,我们对key进行CRC16(key)算法处理并通过对总分片数量取模。然后,使用确定性哈希函数,这意味着给定的key将多次始终映射到同一个分片,我们可以推断将来读取特定key的位置。

3.优势

最大优势,方便扩缩容和数据分派查找

4.slot槽位映射(面试重点)

哈希取余分区(说白了,就是key的hash%机器台数)

2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。优点: 简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。缺点: 原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

一致性哈希算法分区

一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决 分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不OK了。

提出一致性Hash解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系

步骤:

1.算法构建一致性哈希环

一致性哈希环 一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。

它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对2^32取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1, 0和2^32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。

2.redis服务器IP节点映射

将集群中各个IP节点映射到环上的某一个位置。 将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:

3.key落到服务器的落键规则

当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。 如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上

优点

一致性哈希算法的容错性:

假设Node C宕机,可以看到此时对象A、B、D不会受到影响。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据且这些数据会转移到D进行存储。

一致性哈希算法的扩展性:

数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。

缺点

一致性哈希算法的数据倾斜问题

Hash环的数据倾斜问题,一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题, 例如系统中只有两台服务器:

总结:

为了在节点数目发生改变时尽可能少的迁移数据 将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash后会顺时针找到临近的存储节点存放。而当有节点加入或退出时仅影响该节点在Hash环上顺时针相邻的后续节点 优点 加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。 缺点 数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。

哈希槽分区

1 为什么出现

哈希槽实质就是一个数组,数组[0,2^14 -1]形成hash slot空间。

2 能干什么 解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配 3 多少个hash槽 一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。 集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取模,余数是几key就落入对应的槽里。HASH_SLOT = CRC16(key) mod 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

4.hash槽计算

Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis先对key使用crc16算法算出一个结果然后用结果对16384求余数[ CRC16(key) % 16384],这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上

5.为什么redis集群的最大槽数是16384个?(面试重点)

(1)如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。 在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb 在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。 当槽位为16384时,这块的大小是: 16384÷8÷1024=2kb 因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。

(2)redis的集群主节点数量基本不可能超过1000个。 集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。

(3)槽位越小,节点少的情况下,压缩比高,容易传输 Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。

Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群 可能会丢掉一些被系统收到的写入请求命令

4.三主三从集群搭建(生产重点)

1.架构

需要六台服务器

2.前提准备

需要在六台服务器中安装好redis,请看第一节

3.配置

第一台服务器

[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ pwd
/opt/module/redis-7.0.0/myredis/cluster
[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ vim redisCluster6381.conf 
bind 0.0.0.0
daemonize yes
protected-mode no
port 6381
logfile "/opt/module/redis-7.0.0/myredis/cluster/cluster6381.log"
pidfile /opt/module/redis-7.0.0/myredis/cluster6381.pid
dir /opt/module/redis-7.0.0/myredis/cluster
dbfilename dump6381.rdb
appendonly yes
appendfilename "appendonly6381.aof"
requirepass 111111
masterauth 111111 
cluster-enabled yes
cluster-config-file nodes-6381.conf
cluster-node-timeout 5000

第二台服务器

[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ pwd
/opt/module/redis-7.0.0/myredis/cluster
[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ vim redisCluster6382.conf 
bind 0.0.0.0
daemonize yes
protected-mode no
port 6382
logfile "/opt/module/redis-7.0.0/myredis/cluster/cluster6382.log"
pidfile /opt/module/redis-7.0.0/myredis/cluster6382.pid
dir /opt/module/redis-7.0.0/myredis/cluster
dbfilename dump6382.rdb
appendonly yes
appendfilename "appendonly6382.aof"
requirepass 111111
masterauth 111111 
cluster-enabled yes
cluster-config-file nodes-6382.conf
cluster-node-timeout 5000

第三台服务器

[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ pwd
/opt/module/redis-7.0.0/myredis/cluster
[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ vim redisCluster6383.conf 
bind 0.0.0.0
daemonize yes
protected-mode no
port 6383
logfile "/opt/module/redis-7.0.0/myredis/cluster/cluster6383.log"
pidfile /opt/module/redis-7.0.0/myredis/cluster6383.pid
dir /opt/module/redis-7.0.0/myredis/cluster
dbfilename dump6383.rdb
appendonly yes
appendfilename "appendonly6383.aof"
requirepass 111111
masterauth 111111
cluster-enabled yes
cluster-config-file nodes-6383.conf
cluster-node-timeout 5000

第四台服务器

[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ pwd
/opt/module/redis-7.0.0/myredis/cluster
[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ vim redisCluster6384.conf 
bind 0.0.0.0
daemonize yes
protected-mode no
port 6384
logfile "/opt/module/redis-7.0.0/myredis/cluster/cluster6384.log"
pidfile /opt/module/redis-7.0.0/myredis/cluster6384.pid
dir /opt/module/redis-7.0.0/myredis/cluster
dbfilename dump6384.rdb
appendonly yes
appendfilename "appendonly6384.aof"
requirepass 111111
masterauth 111111
cluster-enabled yes
cluster-config-file nodes-6384.conf
cluster-node-timeout 5000

第五台服务器

[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ pwd
/opt/module/redis-7.0.0/myredis/cluster
[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ vim redisCluster6385.conf 
bind 0.0.0.0
daemonize yes
protected-mode no
port 6385
logfile "/opt/module/redis-7.0.0/myredis/cluster/cluster6385.log"
pidfile /opt/module/redis-7.0.0/myredis/cluster6385.pid
dir /opt/module/redis-7.0.0/myredis/cluster
dbfilename dump6385.rdb
appendonly yes
appendfilename "appendonly6385.aof"
requirepass 111111
masterauth 111111
cluster-enabled yes
cluster-config-file nodes-6385.conf
cluster-node-timeout 5000

第六台服务器

[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ pwd
/opt/module/redis-7.0.0/myredis/cluster
[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ vim redisCluster6385.conf 
bind 0.0.0.0
daemonize yes
protected-mode no
port 6386
logfile "/opt/module/redis-7.0.0/myredis/cluster/cluster6386.log"
pidfile /opt/module/redis-7.0.0/myredis/cluster6386.pid
dir /opt/module/redis-7.0.0/myredis/cluster
dbfilename dump6386.rdb
appendonly yes
appendfilename "appendonly6386.aof"
requirepass 111111
masterauth 111111

cluster-enabled yes
cluster-config-file nodes-6386.conf
cluster-node-timeout 5000
4.启动

每一台都需要启动哟(一定要启动)

redis-server redisCluster6381.conf

查看启动情况

[atguigu@iZ8vb5kc8g5l77gfw9rxb3Z cluster]$ ps -ef | grep redis
atguigu   6017     1  0 15:44 ?        00:00:01 redis-server 0.0.0.0:6383 [cluster]
atguigu   6057  1475  0 16:03 pts/1    00:00:00 grep --color=auto redis
5.分配

命令说明:-a 是密码

--cluster create 意思是创建集群

--cluster-replicas 1 意思是有一个副本

后面的ip是六台服务器的ip+端口号(一定要写对)

执行命令后,直接写入 yes 就OK了

redis-cli -a 111111 --cluster create --cluster-replicas 1 47.92.175.223:6381 47.92.175.224:6382 47.92.132.66:6383 47.92.132.67:6384 47.92.106.147:6385 47.92.106.148:6386
6.访问
redis-cli -a 111111 -p 6381 -c

一定要加 -c

7.查看集群情况

登录到redis中 使用以下命令就OK了(三个命令都可以用)

info replication

cluster info

cluster nodes

10.SpringBoot集成Redis

如果想用普通java程序访问redis,请看:asxyyjh_安装部署-CSDN博客

集成Jedis

1.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>com.wsjj.yjh</groupId>
    <artifactId>redis7_study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>

    <dependencies>
    <!--SpringBoot通用依赖模块-->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--jedis-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>4.3.1</version>
    </dependency>
    <!--通用基础配置-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>
</dependencies>
    <build>
        <plugins>
        <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        </plugins>
    </build>
</project>

2.yaml

spring:
  application:
    name: redis7_study

3.创建类

package com.wsjj.yjh.demo;

import redis.clients.jedis.Jedis;

import java.util.List;

public class JedisDemo {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("hadoop102", 6379);
//        jedis.auth("密码");
        System.out.println(jedis.ping());
        System.out.println(jedis.get("k1"));
        jedis.set("k2","v2");
        System.out.println(jedis.get("k2"));
        System.out.println(jedis.keys("*"));
        jedis.lpush("list","aaa","bbbb","ccc");
        System.out.println(jedis.lindex("list",3L));
        System.out.println(jedis.lrange("list", 0L, 2L));
        List<String> list = jedis.lrange("list", 0L, 2L);
        for (String s : list) {
            System.out.println(s);
        }

    }
}

集成lettuce

1.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>com.wsjj.yjh</groupId>
    <artifactId>redis7_study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>

    <dependencies>
    <!--SpringBoot通用依赖模块-->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--jedis-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>4.3.1</version>
    </dependency>
     <!--lettuce-->
    <dependency>
       <groupId>io.lettuce</groupId>
       <artifactId>lettuce-core</artifactId>
       <version>6.2.1.RELEASE</version>
    </dependency>
    <!--通用基础配置-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>
</dependencies>

    <build>
        <plugins>
        <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        </plugins>
    </build>

</project>

2.yaml

spring:
  application:
    name: redis7_study

3.创建一个类

package com.wsjj.yjh.demo;

import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

public class LettuceDemo {
    public static void main(String[] args) {
//        1.使用构造者方式创建URI
        RedisURI redis = RedisURI.builder().redis("hadoop102").withPort(6379).build();
//        2.创建连接客户端
        RedisClient redisClient = RedisClient.create(redis);
        StatefulRedisConnection conn = redisClient.connect();
//        3.获取操作对象
        RedisCommands sync = conn.sync();
//        4.写具体逻辑
        System.out.println(sync.ping());
        System.out.println(sync.keys("*"));
        System.out.println(sync.get("k1"));

//        5.关闭连接
        conn.close();
        redisClient.shutdown();
    }
}

集成RedisTemplate

1.单机版本

1.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>com.wsjj.yjh</groupId>
    <artifactId>redis7_study</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.10</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
    </properties>

    <dependencies>
    <!--SpringBoot通用依赖模块-->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--jedis-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>4.3.1</version>
    </dependency>
<!--     &lt;!&ndash;lettuce&ndash;&gt;-->
<!--    <dependency>-->
<!--       <groupId>io.lettuce</groupId>-->
<!--       <artifactId>lettuce-core</artifactId>-->
<!--       <version>6.2.1.RELEASE</version>-->
<!--    </dependency>-->
        <!--SpringBoot与Redis整合依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
        <!--swagger2-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.9.2</version>
    </dependency>
        
    <!--通用基础配置-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>${log4j.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>${lombok.version}</version>
        <optional>true</optional>
    </dependency>
</dependencies>

    <build>
        <plugins>
        <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
        </plugins>
    </build>

</project>

2.yaml

# ========================logging=====================
logging:
  level:
    root: info
    com:
      wsjj:
        yjh:
          redis7: info
#  pattern:
#    console: %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
#    file: %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
  file:
    name: E:/item/redis7_study/redis7_study.log

# ========================swagger=====================
spring:
  application:
    name: redis7_study
  swagger2:
    enabled: true
  #在springboot2.6.X结合swagger2.9.X会提示documentationPluginsBootstrapper空指针异常,
  #原因是在springboot2.6.X中将SpringMVC默认路径匹配策略从AntPathMatcher更改为PathPatternParser,
  #导致出错,解决办法是matching-strategy切换回之前ant_path_matcher

  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher

# ========================redis单机=====================
  redis:
    database: 0
    port: 6379
#修改为自己真实IP
    host: hadoop102
    password:
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

3.启动类

package com.wsjj.yjh;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Redis7Study {
    public static void main(String[] args) {
        SpringApplication.run(Redis7Study.class,args);
    }
}

4.配置类

SwaggerConfig类

package com.wsjj.yjh.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
      @Value("${spring.swagger2.enabled}")
        private  Boolean  enabled;

        @Bean
        public  

    Docket createRestApi() {
                return new  Docket(DocumentationType.SWAGGER_2)
                               .apiInfo(apiInfo())
                               .enable(enabled)
                               .select()
                               .apis(RequestHandlerSelectors.basePackage("com.wsjj.yjh"))  //你自己的package
                                .paths(PathSelectors.any())
                               .build();
       }
        public  

    ApiInfo apiInfo() {
                return new ApiInfoBuilder()
                               .title("springboot利用swagger2构建api接口文档  "+"\t"+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now()))
                               .description("springboot+redis整合,有问题给管理员阳哥邮件:zzyybs@126.com")
                               .version("1.0")
                               .termsOfServiceUrl("https://www.atguigu.com/")
                               .build();
       }
}

RedisConfig类(主要解决的是序列化的问题)

package com.wsjj.yjh.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
     /**
          * redis序列化的工具配置类,下面这个请一定开启配置
          * 127.0.0.1:6379> keys *
          * 1) "ord:102"  序列化过
          * 2) "\xac\xed\x00\x05t\x00\aord:102"   野生,没有序列化过
          * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
          * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
          * this.redisTemplate.opsForSet(); //提供了操作set的所有方法
          * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
          * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
          * @param lettuceConnectionFactory
          * @return
          */
     @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
                    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

                    redisTemplate.setConnectionFactory(lettuceConnectionFactory);
                    //设置key序列化方式string
                    redisTemplate.setKeySerializer(new StringRedisSerializer());
                    //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
                    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

                    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
                    redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

                    redisTemplate.afterPropertiesSet();

                    return redisTemplate;
                }
    
}

5.service

package com.wsjj.yjh.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;


@Service
@Slf4j
public class OrderService {

    @Resource
    private RedisTemplate redisTemplate;

    public static final String ORDER_KEY = "ord:";

    public void addOrder(){
        int keyId = ThreadLocalRandom.current().nextInt(1000) + 1;
        System.out.println(keyId);
        String serialNo = UUID.randomUUID().toString();

        String key = ORDER_KEY+keyId;
        String value = "京东订单" + serialNo;

        redisTemplate.opsForValue().set(key,value);

    }

    public String getOrder(String key){
        String keys = ORDER_KEY+key;
        System.out.println("查询出来的结果  "+(String) redisTemplate.opsForValue().get(keys));
        return (String) redisTemplate.opsForValue().get(keys);
    }

}

6.Controller

package com.wsjj.yjh.controller;

import com.wsjj.yjh.service.OrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
@Api(tags = "订单接口")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @ApiOperation("新增订单")
    @PostMapping("/order/add")
    public void addOrder(){
        orderService.addOrder();
    }

    @ApiOperation("查询订单")
    @GetMapping("/order/get/{key}")
    public String getOrder(@PathVariable String key){
        System.out.println("key的值"+key);
       return orderService.getOrder(key);
    }
}

7.测试

访问如下地址:

http://localhost:8080/swagger-ui.html

8.注意

RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。KEY被序列化成这样,线上通过KEY去查询对应的VALUE非常不方中

所以:解决方案:就是加上配置类中的RedisConfig

如果redis中涉及到中文,访问redis的时候,用以下命令访问(--raw)

[atguigu@hadoop102 redis-6.2.1]$ redis-cli --raw
127.0.0.1:6379> keys *
list
redis:743
redis35
k1
k2
127.0.0.1:6379> get redis:763

127.0.0.1:6379> get redis:743
"京东订单56643eb5-3580-437f-932e-5e6893a4b158"
127.0.0.1:6379> 
2.集群版本

在单机版本的基础上,修改yaml文件就好了

# ========================logging=====================
logging:
  level:
    root: info
    com:
      wsjj:
        yjh:
          redis7: info
#  pattern:
#    console: %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
#    file: %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
  file:
    name: E:/item/redis7_study/redis7_study.log

# ========================swagger=====================
spring:
  application:
    name: redis7_study
  swagger2:
    enabled: true
  #在springboot2.6.X结合swagger2.9.X会提示documentationPluginsBootstrapper空指针异常,
  #原因是在springboot2.6.X中将SpringMVC默认路径匹配策略从AntPathMatcher更改为PathPatternParser,
  #导致出错,解决办法是matching-strategy切换回之前ant_path_matcher

  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
# ========================redis集群=====================
  redis:
    cluster: 
      max-redirects: 3   # (集群的时候打开)
      nodes: 192.168.111.175:6381,192.168.111.175:6382,192.168.111.172:6383,192.168.111.172:6384,192.168.111.174:6385,192.168.111.174:6386  # (集群的时候打开)
    database: 0
    port: 6379
#    #修改为自己真实IP
#    host: hadoop102
    password: 
      lettuce: 
        cluster:
          refresh:
            period: 2000
            adaptive: true   (集群的时候,一定打开,这里是动态监听到redis集群的动态变化)
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

以上代码请访问地址redis7版本的学习代码: redis7版本的学习代码,使用三种方式连接redis

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值