Redis复习笔记

文章目录

Redis

一、redis入门

1.1、NoSQL的引言

NoSQL(Not Only SQL),意即不仅仅是SQL, 泛指非关系型的数据库。Nosql这个技术门类,早期就有人提出,发展至2009年趋势越发高涨。

泛指非关系型数据库

1.2、为什么是NoSQL

​ 随着互联网网站的兴起,传统的关系数据库在应付动态网站,特别是超大规模和高并发的纯动态网站已经显得力不从心,暴露了很多难以克服的问题。如商城网站中对商品数据频繁查询、对热搜商品的排行统计、订单超时问题、以及微信朋友圈(音频,视频)存储等相关使用传统的关系型数据库实现就显得非常复杂,虽然能实现相应功能但是在性能上却不是那么乐观。nosql这个技术门类的出现,更好的解决了这些问题,它告诉了世界不仅仅是sql。

1.3、NoSQL的四大分类

1.3.1 键值(Key-Value)存储数据库
# 1.说明: 
- 这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。

# 2.特点
- Key/value模型对于IT系统来说的优势在于简单、易部署。  
- 但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。

# 3.相关产品
- Tokyo Cabinet/Tyrant,
- Redis  内存
- SSDB   硬盘
- Voldemort 
- Oracle BDB
1.3.2 列存储数据库
# 1.说明
- 这部分数据库通常是用来应对分布式存储的海量数据。

# 2.特点
- 键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。

# 3.相关产品
- Cassandra、HBase、Riak.
1.3.3 文档型数据库
# 1.说明
- 文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高

# 2.特点
- 以文档形式存储

# 3.相关产品
- MongoDB、CouchDB、 MongoDb(4.x). 国内也有文档型数据库SequoiaDB,已经开源。
1.3.4 图形(Graph)数据库
# 1.说明
- 图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。

# 2. 特点
- NoSQL数据库没有标准的查询语言(SQL),因此进行数据库查询需要制定数据模型。许多NoSQL数据库都有REST式的数据接口或者查询API。

# 3.相关产品
- Neo4J、InfoGrid、 Infinite Graph、

1.4 NoSQL应用场景

  • 数据模型比较简单
  • 需要灵活性更强的IT系统
  • 对数据库性能要求较高
  • 不需要高度的数据一致性(Nosql产品对于事务支持都不是特别良好)

1.5 什么是Redis

img

Redis is an open source (BSD licensed),in-memory data structure store, used as a database, cache and message broker.

Redis开源 遵循BSD 基于内存数据存储,被用于作为数据库、缓存、消息中间件

总结: redis是一个内存型的数据库

1.6 Redis特点

  • Redis是一个高性能key/value内存数据库
  • Redis支持丰富的数据类型
  • Reids支持持久化
  • Reids单线程,单进程

1.7 Redis 安装

# 0.准备环境
- vmware15.x+
- centos7.x+

# 1.下载redis源码包
 https://redis.io/

在这里插入图片描述

# 2.下载完整源码包
redis-6.0.9.tar.gz

# 3.将下载redis资料包上传到Linux中

# 4.解压缩文件
[root@localhost opt]# tar -zxvf redis-6.0.9.tar.gz
[root@localhost opt]# ls
containerd  redis  redis-6.0.9.tar.gz  rh

# 5.安装gcc  
yum install -y gcc

# 6.进入解压缩目录执行如下命令
[root@localhost redis] make MALLOC=libc
 
# 7.编译完成后执行如下命令
make install PREFIX=/usr/redis  # 将编译好的文件放到指定目录下

# 8.进入/usr/local/redis/bin目录启动redis服务 
./redis-server

在这里插入图片描述

# 9.Redis服务端口默认是 6379

# 10.进入bin目录执行客户端连接操作
./redis-cli -h localhost –p 6379

redis细节

1. redis启动服务的细节
 注意:直接使用./redis-server方式启动使用的是redis-server这个shell脚本中默认配置
 
2. 如何在启动redis时指定配置文件启动
 注意:默认在redis安装完成之后再安装目录没有任何配置文件,需要在源码目录中复制redis.conf配置文件到安装目录
 a.进入源码目录
 	cp redis.conf  /usr/redis
 b.进入/usr/redis安装目录查看复制的配置文件
 	cd /usr/redis
 	ls
 c.进入bin目录加载配置启动
 	./redis-server ../redis.conf
3. 修改redis默认端口号
	vim redis.conf  修改里面  port 7000 保存退出
	
4. redis中库的概念
	库:database 用来存放数据的一个基本单元   一个库可以存放key-value键值对   redis中每一个库都有一个唯一名称|编号  从0开始
	默认库的个数:16个库    库的编号:0-15   默认使用0号库
	切换库:  select dbid(库标号)
5. redis中清除库的指令
	清空当前库:FLUSHDB
	清空所有库:FLUSHALL

二、redis数据库相关指令

2.1 数库操作指令

# 1.Redis中库说明
- 使用redis的默认配置器动redis服务后,默认会存在16个库,编号从0-15
- 可以使用select 库的编号 来选择一个redis的库

# 2.Redis中操作库的指令
- 清空当前的库  FLUSHDB
- 清空全部的库  FLUSHALL

# 3.redis客户端显示中文
-	./redis-cli  -p 7000 --raw 

2.2 操作key相关指令

# 1.DEL指令
- 语法 :  DEL key [key ...] 
- 作用 :  删除给定的一个或多个key 。不存在的key 会被忽略。
- 可用版本: >= 1.0.0
- 返回值: 被删除key 的数量。 

# 2.EXISTS指令
- 语法:  EXISTS key
- 作用:  检查给定key 是否存在。
- 可用版本: >= 1.0.0
- 返回值: 若key 存在,返回1 ,否则返回0。

# 3.EXPIRE
- 语法:  EXPIRE key seconds
- 作用:  为给定key 设置生存时间,当key 过期时(生存时间为0 ),它会被自动删除。
- 可用版本: >= 1.0.0
- 时间复杂度: O(1)
- 返回值:设置成功返回1 。

# 4.KEYS
- 语法 :  KEYS pattern
- 作用 :  查找所有符合给定模式pattern 的key 。
- 语法:
	KEYS * 匹配数据库中所有key 。
	KEYS h?llo 匹配hello ,hallo 和hxllo 等。
	KEYS h*llo 匹配hllo 和heeeeello 等。
	KEYS h[ae]llo 匹配hello 和hallo ,但不匹配hillo 。特殊符号用 "\" 隔开
- 可用版本: >= 1.0.0
- 返回值: 符合给定模式的key 列表。

# 5.MOVE
- 语法 :  MOVE key db
- 作用 :  将当前数据库的key 移动到给定的数据库db 当中。
- 可用版本: >= 1.0.0
- 返回值: 移动成功返回1 ,失败则返回0 。

# 6.PEXPIRE
- 语法 :  PEXPIRE key milliseconds
- 作用 :  这个命令和EXPIRE 命令的作用类似,但是它以毫秒为单位设置key 的生存时间,而不像EXPIRE 命令那样,以秒为单位。
- 可用版本: >= 2.6.0
- 时间复杂度: O(1)
- 返回值:设置成功,返回1  key 不存在或设置失败,返回0

# 7.PEXPIREAT
- 语法 :  PEXPIREAT key milliseconds-timestamp
- 作用 :  这个命令和EXPIREAT 命令类似,但它以毫秒为单位设置key 的过期unix 时间戳,而不是像EXPIREAT那样,以秒为单位。
- 可用版本: >= 2.6.0
- 返回值:如果生存时间设置成功,返回1 。当key 不存在或没办法设置生存时间时,返回0 。(查看EXPIRE 命令获取更多信息)

# 8.TTL
- 语法 :   TTL key
- 作用 :   以秒为单位,返回给定key 的剩余生存时间(TTL, time to live)。
- 可用版本: >= 1.0.0
- 返回值:
	当key 不存在时,返回-2 。
	当key 存在但没有设置剩余生存时间时,返回-1 。
	否则,以秒为单位,返回key 的剩余生存时间。
- Note : 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。

# 9.PTTL
- 语法 :  PTTL key
- 作用 :  这个命令类似于TTL 命令,但它以毫秒为单位返回key 的剩余生存时间,而不是像TTL 命令那样,以秒为单位。
- 可用版本: >= 2.6.0
- 返回值: 
	当key 不存在时,返回-2 。
	当key 存在但没有设置剩余生存时间时,返回-1 。
	否则,以毫秒为单位,返回key 的剩余生存时间。
- 注意 : 在Redis 2.8 以前,当key 不存在,或者key 没有设置剩余生存时间时,命令都返回-1 。

# 10.RANDOMKEY
- 语法 :  RANDOMKEY
- 作用 :  从当前数据库中随机返回(不删除) 一个key 。
- 可用版本: >= 1.0.0
- 返回值:当数据库不为空时,返回一个key 。当数据库为空时,返回nil 。

# 11.RENAME
- 语法 :  RENAME key newkey
- 作用 :  将key 改名为newkey 。当key 和newkey 相同,或者key 不存在时,返回一个错误。当newkey 已经存在时,RENAME 命令将覆盖旧值。
- 可用版本: >= 1.0.0
- 返回值: 改名成功时提示OK ,失败时候返回一个错误。

# 12.TYPE
- 语法 :  TYPE key
- 作用 :  返回key 所储存的值的类型。
- 可用版本: >= 1.0.0
- 返回值:
	none (key 不存在)
	string (字符串)
	list (列表)
	set (集合)
	zset (有序集)
	hash (哈希表)

2.3 String类型

1. 内存存储模型

在这里插入图片描述

4. 常用操作命令
命令说明
set设置一个key/value
get根据key获得对应的value
mset一次设置多个key value
mget一次获得多个key的value
getset获得原始key的值,同时设置新值
strlen获得对应key存储value的长度
append为对应key的value追加内容
getrange索引0开始 截取value的内容
setex设置一个key存活的有效期(秒)
psetex设置一个key存活的有效期(毫秒)
setnx存在不做任何操作,不存在添加
msetnx原子操作(只要有一个存在不做任何操作)
可以同时设置多个key,只要有一个存在都不保存
decr进行数值类型的-1操作
decrby根据提供的数据进行减法操作
Incr进行数值类型的+1操作
incrby根据提供的数据进行加法操作
Incrbyfloat根据提供的数据加入浮点数

2.4 List类型

list 列表 相当于java中list 集合 特点 元素有序 且 可以重复

1. 内存存储模型

在这里插入图片描述

2. 常用操作指令
命令说明
lpush将某个值加入到一个key列表头部
lpushx同lpush,但是必须要保证这个key存在
rpush将某个值加入到一个key列表末尾
rpushx同rpush,但是必须要保证这个key存在
lpop返回和移除列表左边的第一个元素
rpop返回和移除列表右边的第一个元素
lrange获取某一个下标区间内的元素
llen获取列表元素个数
lset设置某一个指定索引的值(索引必须存在)
lindex获取某一个指定索引位置的元素
lrem listname count value删除元素 (正数从左边开始删,负数从右边开始删除)
ltrim保留列表中特定区间内的元素
linsert listname before/after pivot value在某一个元素之前,之后插入新元素

2.5 Set类型

特点: Set类型 Set集合 元素无序 不可以重复

1.内存存储模型

在这里插入图片描述

2.常用命令
命令说明
sadd为集合添加元素
smembers显示集合中所有元素 无序
scard返回集合中元素的个数
spop随机返回一个元素 并将元素在集合中删除
smove (formset toset value)从一个集合中向另一个集合移动元素 必须是同一种类型
srem从集合中删除一个元素
sismember判断一个集合中是否含有这个元素
srandmember随机返回元素
sdiff去掉第一个集合中其它集合含有的相同元素 (第一个集合的补集)
sinter求交集
sunion求和集

2.6 ZSet类型

特点: 可排序的set集合 排序 不可重复

ZSET 官方 可排序SET sortSet

1. 内存模型

在这里插入图片描述

2. 常用命令
命令说明
zadd添加一个有序集合元素
zcard返回集合的元素个数
zrange 升序返回一个范围内的元素
zrevrange 降序 ( reversal反转 )返回一个范围内的元素
zrangebyscore按照分数查找一个范围内的元素
zrank返回排名
zrevrank倒序排名
zscore显示某一个元素的分数
zrem移除某一个元素
zincrby给某个特定元素加分

2.7 hash类型

特点: value 是一个map结构 存在key value, key 无序的

1.内存模型

在这里插入图片描述

2.常用命令
命令说明
hset设置一个key/value对
hget获得一个key对应的value
hgetall获得所有的key/value对
hdel删除某一个key/value对
hexists判断一个key是否存在
hkeys获得所有的key
hvals获得所有的value
hmset设置多个key/value
hmget获得多个key的value
hsetnx设置一个不存在的key的值
hincrby为value进行加法运算
hincrbyfloat为value加入浮点值

可视化工具

redis-desktop-manager

开启redis远程连接

注意:默认redis服务器是没有开启远程连接,也就是默认拒绝所有远程客户端连接

1. 修改配置开启远程连接
 vim redis.conf   修改如下配置
 bind 0.0.0.0   # 允许一切客户端连接
 
2. 修改配置只有重启redis服务
 ./redis.server  ../redis.conf  # 注意一定要加载配置文件启动

三、持久化机制

client redis[内存] -----> 内存数据- 数据持久化–>磁盘

Redis官方提供了两种不同的持久化方法来将数据存储到硬盘里面分别是:

  • 快照(Snapshot)RDB
  • AOF (Append Only File) 只追加日志文件

3.1 快照(Snapshot)

3.1.1 特点

这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是 redis的默认开启持久化方式,保存的文件是以.rdb形式结尾的文件,因此这种方式也称之为RDB方式。

在这里插入图片描述

3.1.2 快照生成方式

客户端方式:BGSAVE 和 SAVE指令

服务器配置自动触发

# 1.客户端方式之BGSAVE
	客户端可以使用BGSAVE命令来创建一个快照,当接收到客户端的BGSAVE命令时,redis会调用fork¹来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求。
	
	名词解释: fork当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务

在这里插入图片描述

# 2.客户端方式之SAVE
	客户端还可以使用SAVE命令来创建一个快照,接收到SAVE命令的redis服务器在快照创建完毕之前将不再响应任何其他的命令

img

注意: SAVE命令并不常用,使用SAVE命令在快照创建完毕之前,redis处于阻塞状态,无法对外服务

# 3 服务器配置方式之满足配置自动触发
	如果用户在redis.conf中设置了save配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令

在这里插入图片描述

# 4.服务器接收客户端shutdown指令
	当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器
3.1.3 配置生成快照名称和位置
# 1.修改生成快照名称
dbfilename dump.rdb

# 2.修改生成位置
dir ./   # 这个表示redis-cli、redis-server这些命令的同级目录

在这里插入图片描述

3.1.4 RDB优点、缺点

RDB优点

  • RDB是一个紧凑压缩的二进制文件,存储效率较高
  • RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
  • RDB恢复数据的速度要比AOF快很多

Rdb缺点

  • RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
  • bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能
  • Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象

3.2 AOF 只追加日志文件

3.2.1 特点

​ 这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发
生的变化,因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集.

在这里插入图片描述

AOF持久化文件保存路径,与RDB持久化文件保持一致即可

3.2.2 开启AOF持久化

在redis的默认配置中AOF持久化机制是没有开启的,需要在配置中开启

# 1.开启AOF持久化
- a.修改 appendonly yes 开启持久化
- b.修改 appendfilename "appendonly.aof" 指定生成文件名称

img

3.2.3 日志追加频率
# 1.always 【谨慎使用】
	说明: 每个redis写命令都要同步写入硬盘,严重降低redis速度
	解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
	注意: 转盘式硬盘在这种频率下200左右个命令/s ; 固态硬盘(SSD) 几百万个命令/s;
	警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月。

# 2.everysec 【推荐】
	说明:每秒执行一次同步显式的将多个写命令同步到磁盘
	解释:为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。

# 3.no	【不推荐】
	说明: 由操作系统决定何时同步 
	解释:最后使用no选项,将完全有操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。
3.2.4 修改同步频率
# 1.修改日志同步频率
	修改appendfsync everysec|always|no 指定

img

3.3 AOF文件的重写

3.3.1 AOF带来的问题

AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写(ReWriter)机制。

3.3.2 AOF重写

用来在一定程度上减小AOF文件的体积

3.3.3 触发重写方式
# 1.客户端方式触发重写
	执行BGREWRITEAOF命令  不会阻塞redis的服务

# 2.服务器配置方式自动触发
	配置redis.conf中的auto-aof-rewrite-percentage选项 参加下图↓↓↓
	如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用的AOF持久化时,那么当AOF文件体积大于64M,并且AOF文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大

img

4.3.4 重写原理

注意:重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换原有的文件这点和快照有点类似。

# 重写流程
1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
3. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

在这里插入图片描述

3.4 持久化总结

两种持久化方案既可以同时使用(aof),又可以单独使用,在某种情况下也可以都不使用,具体使用那种持久化方案取决于用户的数据和应用决定。

无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份(最好备份在多个不同地方)。

  • AOF 的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。
  • RDB与AOF二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。
  • Redis允许同时开启AOF和RDB,既保证了数据安全又使得进行备份等操作十分容易。此时重新启动Redis后Redis会使用AOF文件来恢复数据,因为AOF方式的持久化可能丢失的数据更少
  • 若只打算用Redis 做缓存,可以关闭持久化。

四. java操作Redis

4.1 环境准备

1. 引入依赖
<!--引入jedis连接依赖-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.9.0</version>
</dependency>
2.创建jedis对象
public static void main(String[] args) {
    //1.创建jedis对象
    Jedis jedis = new Jedis("192.168.40.4", 6379); //1.redis服务必须关闭防火墙  2.redis服务必须开启远程连接
    jedis.select(0);//选择操作的库默认0号库
    //2.执行相关操作
    //....
    //3.释放资源
    jedis.close();
}

4.2 操作key相关API

private Jedis jedis;

@Before
public void before(){
    this.jedis = new Jedis("192.168.202.205", 7000);
}

@After
public void after(){
    jedis.close();
}

//测试key相关
@Test
public void testKeys(){
    //删除一个key
    jedis.del("name");
    //删除多个key
    jedis.del("name","age");

    //判断一个key是否存在exits
    Boolean name = jedis.exists("name");
    System.out.println(name);

    //设置一个key超时时间 expire pexpire
    Long age = jedis.expire("age", 100);
    System.out.println(age);

    //获取一个key超时时间 ttl
    Long age1 = jedis.ttl("newage");
    System.out.println(age1);

    //随机获取一个key
    String s = jedis.randomKey();

    //修改key名称
    jedis.rename("age","newage");

    //查看可以对应值的类型
    String name1 = jedis.type("name");
    System.out.println(name1);
    String maps = jedis.type("maps");
    System.out.println(maps);
}

4.3操作String相关API

//测试String相关
@Test
public void testString(){
    //set
    jedis.set("name","小陈");
    //get
    String s = jedis.get("name");
    System.out.println(s);
    //mset
    jedis.mset("content","好人","address","海淀区");
    //mget
    List<String> mget = jedis.mget("name", "content", "address");
    mget.forEach(v-> System.out.println("v = " + v));
    //getset
    String set = jedis.getSet("name", "小明");
    System.out.println(set);

    //............
}

4.5操作Set的相关API

//测试SET相关
@Test
public void testSet(){

    //sadd
    jedis.sadd("names","zhangsan","lisi");

    //smembers
    jedis.smembers("names");

    //sismember
    jedis.sismember("names","xiaochen");

    //...
}

4.6 操作ZSet相关API

//测试ZSET相关
@Test
public void testZset(){

  //zadd
  jedis.zadd("names",10,"张三");

  //zrange
  jedis.zrange("names",0,-1);

  //zcard
  jedis.zcard("names");

  //zrangeByScore
  jedis.zrangeByScore("names","0","100",0,5);

  //..

}

4.7 操作Hash相关API

//测试HASH相关
@Test
public void testHash(){
    //hset
    jedis.hset("maps","name","zhangsan");
    //hget
    jedis.hget("maps","name");
    //hgetall
    jedis.hgetAll("mps");
    //hkeys
    jedis.hkeys("maps");
    //hvals
    jedis.hvals("maps");
    //....
}

五、SpringBoot整合Redis

Spring Boot Data(数据) Redis 中提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象,而StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。

环境准备

引入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置applicaiton.properties

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0

使用Template

5种数据结构的操作

(String)redisTemplate.opsForValue();  //操做字符串
(String)redisTemplate.opsForHash();   //操做hash
(String)redisTemplate.opsForList();   //操做list
(String)redisTemplate.opsForSet();     //操做set
(String)redisTemplate.opsForZSet();   //操做有序set
@Autowired
private StringRedisTemplate stringRedisTemplate;  //对字符串支持比较友好,不能存储对象

@Autowired
private RedisTemplate redisTemplate;  //存储对象
//key相关的操作
@Test
public void test(){
    Set<String> keys = stringRedisTemplate.keys("*");	//查看所有key
    Boolean name = stringRedisTemplate.hasKey("name");	//判断某个key是否存在
    stringRedisTemplate.delete("age");	//根据指定key删除
    stringRedisTemplate.rename("","");	//修改key的名称
    stringRedisTemplate.expire("key",10, TimeUnit.HOURS);
    //设置key超时时间 参数1:设置key名 参数2:时间 参数3:时间的单位
    stringRedisTemplate.move("",1);//移动key
}
//String相关的操作 opsForValue
@Test
public void testString(){
    stringRedisTemplate.opsForValue().set("name","小陈");
    String s = stringRedisTemplate.opsForValue().get("name");
    System.out.println(s);
    
    stringRedisTemplate.opsForValue().set("code","235952", 60, TimeUnit.SECOUNDS);
    stringRedisTemplate.opsForValue().append("name", "他是个好人");
    
    Long size = stringRedisTemplate.opsForValue().size("name");
    System.out.println(size);
}
//list相关的操作opsForList
@Test
public void testList(){
    stringRedisTemplate.opsForList().leftPush("lists","张三");
    stringRedisTemplate.opsForList().leftPushAll("lists","小陈","李四","王五");
    List<String> lists = new ArrayList<>();
    lists.add("小1");
    lists.add("小2");
    stringRedisTemplate.opsForList().leftPushAll("lists", lists);
    
    List<String> lists = stringRedisTemplate.opsForList().range("lists", 0, -1);
    lists.forEach(key -> System.out.println(key));
}
//set相关操作 opsForSet
@Test
public void testSet(){
    stringRedisTemplate.opsForSet().add("sets","xiaosan","xiaosi","xiaowu");
    Set<String> sets = stringRedisTemplate.opsForSet().members("sets");
    sets.forEach(value-> System.out.println(value));
    
    Long size = stringRedisTemplate.opsForSet().size("sets");
    System.out.println("size="+size);
}
//zset相关操作 opsForZSet
@Test
public void testZSet(){
    stringRedisTemplate.opsForZSet().add("zsets","小黑",10);
    
    Set<String> zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1);
    zsets.forEach(value-> System.out.println(value));
    
    Set<ZSetOperations.TypedTyple<String>> zsets1 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zsets", 0, 1000); //获取指定元素以及分数
    
    zsets1.forEach(typeTuple ->{
        System.out.println(typedTuple.getValue());
        System.out.println(typedTuple.getScore());
    })
}
//hash相关操作 opsForHash
@Test
public void testHash(){
    stringRedisTemplate.opsForHash().put("maps","name","小黑");
    Object o = stringRedisTemplate.opsForHash().get("maps", "name");
    System.out.println(o);
    Map<String, String> map = new HashMap<String, String>();
    map.put("age", "12");
    map.put("bir", "2012-12-12");
    stringRedisTemplate.opsForHash().putAll("maps", map);
    
    List<Object> values = stringRedisTemplate.opsForHash().multiGet("maps", Arrays.asList("name", "age"));
    values.forEach(value -> System.out.println(value));
    
    String value = (String) stringRedisTemplate.opsForHash().get("maps", "name");
    
    List<Object> vals = stringRedisTemplate.opsForHash().values("maps");
    
    Set<Object> keys = stringRedisTemplate.opsForHash().keys("maps");
}
@Test
public void testRedisTemplate(){
    // 修改key序列化方案
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    // 修改hash key序列化方案
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    
    User user = new User();
    user.setId(UUID.randomUUID().toString().setName("xiaochen").setAge(23).setBir(new Date()));
    redisTemplate.opsForValue().set("user",user);
    
    User user1 = (User)redisTemplate.opsForValue().get("user");
    System.out.println(user1);
    
    redisTemplate.opsForList().leftPush("list", user);
    redisTemplate.opsForSet().leftPush("set", user);
    redisTemplate.opsForZSet().leftPush("zset", user, 10);
}
public class TestBoudAPI {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void testBound() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        // 对字符串类型key进行绑定,后续所有操作都是基于这个key进行的
        BoundValueOperations<String, String> nameValueOperations = redisTemplate.boundValueOps("name");
        nameValueOperations.set("zhagnsan");
        nameValueOperations.append("是一个好人");
        String name = nameValueOperations.get();
        System.out.println(name);
        
        // 对list set Zset hash等
        BoundListOperations<String, String> listOperations = stringRedisTemplate.boundListOps("lists");
        listOperations.leftPushAll("嗓散", "saw ", "we fww e");
        List<String> lists = listOperations.range(0, -1);
        lists.forEach(list -> {
            System.out.println(list);
        });
    }
}

小结

1. 针对于日后处理key value都是string 使用 stringRedisTemplate
2. 针对于日后处理的key value存在对象,使用 RedisTemplate
3. 针对于同一个key多次操作可以使用boundxxxOps()   Value List Set ZSet Hadh的api

六、Redis的应用场景

# redis的应用场景
1. 利用redis中字符串类型完成项目中手机验证码存储的实现
2. 利用redis中字符串类型完成具有失效性业务功能12306淘宝︰订单还有:40分钟
3. 利用redis分布式集群系统中session共享memcache内存数据存储上限数据类型比较简单redis 内存数据上限数据类型丰富
4. 利用redis zset类型可排序set类型 元素 分数   排行榜之类功能  dangdang 销量排行   sales (zset)[商品id ,商品销量]......
5. 利用redis分布式缓存实现
6. 利用redis存储认证之后token信息  微信小程序  微信公众号  |用户openid   --->令牌(token) redis超时
7. 利用redis 解决分布式集群系统中分布式锁问题       redis 单进程 单线程   n 20 定义
    redis单进程单线程n 20定义
    jvm  1进程开启多个线程 synchronize int n=20
    jvm  1进程开启多个线程 synchronize int n=20
    .....
    LRA脚本

7 分布式缓存

# 1.什么是缓存
	定义:存储在计算机内存的数据

# 2. 内存中数据特点
	1. 读写快, 
	2.断电立即丢失

# 3.缓存解决了什么问题?
	1. 提高网站吞吐量,提高网站的响应速度。
	2. 核心解决问题:通过缓存可以缓解对数据库的访问压力

# 4.既然缓存能提高效率,那项目中所有数据加入缓存岂不是更好?
	注意:使用缓存时一定是数据库中数据极少发生修改,更多用于查询这种情况

# 5.本地和分布式(缓存)
- 本地缓存: 存在应用服务器内存中的数据为本地缓存(local cache)
- 分布式缓存:存储在当前应用服务器内存之外的数据为分布式缓存(distribute cache)

- 集群:将同一种服务的多个节点放在一起共同对系统提供服务过程称之为集群。
- 分布式:有多个不同服务集群共同对系统系统提供服务这个系统称之为分布式系统(distribute system),即分布式是建立在集群之上的。

# 6.利用MyBatis缓存结合redis实现分布式缓存
	a. mybatis中应用级缓存   SqlSessionFactory级别缓存  所有会话共享
	b. 如何开启二级缓存
		mapper.xml
		<cacehe/>

mybatis缓存

<!--引入测试-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!--引入依赖 spring data redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.19</version>
</dependency>
server.port=8989

redis 单节点(这个是单节点的配置,即主从复制或者没有主从配置)
spring.redis.host=192.168.42.137
spring.redis.port=6377
spring.redis.database=0

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/2001?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root

mybatis.mapper-locations=classpath:com/cqm/mapper/*.xml
mybatis.type-aliases-package=com.cqm.entity

logging.level.com.cqm.dao=debug

测试

public class testUserService(){
    
    @Autowired
    private UserService userService;
    
    @Test
    public void test(){
        userService().findAll().forEach(u -> System.out.println("u= "+ u));
        System.out.println("========================");
        
        userService().findAll().forEach(u -> System.out.println("u= "+ u));
    }
}

MyBatis缓存

MyBatis中的缓存相关类都在cache包下面,而且定义了一个顶级接口Cache,默认只有一个实现类PerpetualCache,PerpetualCache中是内部维护了一个HashMap来实现缓存。

在这里插入图片描述

//自定义Redis缓存实现
public class RedisCache implements Cache {

    //当前放入缓存的mapper的namespace
    private final String id;

    //必须存在构造方法
    public RedisCache(String id) {
        System.out.println("id:=====================> " + id);
        this.id = id;
    }

    //返回cache唯一标识
    @Override
    public String getId() {
        return this.id;
    }

    //缓存中放入值  redis RedisTemplate   StringRedisTemplate
    @Override
    public void putObject(Object key, Object value) {
        System.out.println("key:" + key.toString());
        System.out.println("value:" + value);
//        //通过application工具类获取redisTemplate
//        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
//        redisTemplate.setKeySerializer(new StringRedisSerializer());
//        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        //使用redishash类型作为缓存存储模型  key   hashkey  value
        getRedisTemplate().opsForHash().put(id.toString(),getKeyToMD5(key.toString()),value);

        if(id.equals("com.cqm.dao.UserDAO")){
            //缓存超时  client  用户   client  员工
            getRedisTemplate().expire(id.toString(),1, TimeUnit.HOURS);
        }

        if(id.equals("com.cqm.dao.CityDAO")){
            //缓存超时  client  用户   client  员工
            getRedisTemplate().expire(id.toString(),30, TimeUnit.MINUTES);
        }

        //.....指定不同业务模块设置不同缓存超时时间
    }

    //缓存中获取数据
    @Override
    public Object getObject(Object key) {
        System.out.println("key:" + key.toString());
//        //通过application工具类获取redisTemplate
//        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
//        redisTemplate.setKeySerializer(new StringRedisSerializer());
//        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        //根据key 从redis的hash类型中获取数据
        return getRedisTemplate().opsForHash().get(id.toString(), getKeyToMD5(key.toString()));
    }

    //注意:这个方法为mybatis保留方法 默认没有实现 后续版本可能会实现
    @Override
    public Object removeObject(Object key) {
        System.out.println("根据指定key删除缓存");
        return null;
    }

    @Override
    public void clear() {
        System.out.println("清空缓存~~~");
        //清空namespace
        getRedisTemplate().delete(id.toString());//清空缓存
    }

    //用来计算缓存数量
    @Override
    public int getSize() {
        //获取hash中key value数量
        return getRedisTemplate().opsForHash().size(id.toString()).intValue();
    }

    //封装redisTemplate
    private RedisTemplate getRedisTemplate(){
        //通过application工具类获取redisTemplate
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
    
    //封装一个对key进行md5处理方法
    private String getKeyToMD5(String key){
        return DigestUtils.md5DigestAsHex(key.getBytes());
    }
}

// 用来获取springboot创建好的工厂
@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    // 保留下来的工厂
    private static ApplicationContext applicationContext;

    // 将创建好的工厂以参数形式传递给这个类
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    // 提供在工厂中获取对象的方法
    public static Object getBean(String beanName) {
        return applicationContext.getBean(beanName);
    }
}
# 在mybtis的缓存中如何要解决关联关系时更新缓存信息的问题?
    
/**
* <cache-ref namespace="xxx.xxx.xxx"</><cache-ref/>
*/

# 缓存优化策略
- 对放入redis中key进行优化:key的长度不能太长

	MD5加密
1. 一切文件字符串等经过md5处理之后,都会生成32位16进制字符串
2. 不同内容文件经过md5进行加密,加密结果一定不一致
3. 相同内容文件多次经过md5加密,生成结果始终一致

# 面试相关概念

1. 什么是缓存穿透 
	- 定义:是指客户端查询一个数据库中不存在,缓存也不存在的数据。
	- mybatis中cache解决了缓存穿透:将数据库中没有查询到的结果也进行缓存,设置key,value值为null
	- 布隆过滤器
2. 什么是缓存击穿
	- 缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,并且回写缓存,会导使数据库瞬间压力过大。
	- 解决:
		设置热点数据永不过期
		加互斥锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

3. 什么是缓存雪崩
	- 定义:在高并发下,大量缓存key在同一时间失效,大量请求直接落在数据库上,导致数据库阻塞和挂起
	- 解决方案:
		根据对不同的业务的数据,根据吞吐量来设置随机因子来生成缓存时间,高并发的业务随机因子较大。
		缓存永久存储(不推荐)。
		若是集群部署,可将热点数据均匀分布在不同的Redis库中也能够避免key全部失效问题。

Redis主从复制

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。

在这里插入图片描述

主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据

搭建主从复制

# 1.准备3台机器并修改配置
- master
	port 6379
	bind 0.0.0.0
	
- slave1
	port 6380
	bind 0.0.0.0
	replicaof 192.168.202.206 6379
	
- slave2
	port 6381
	bind 0.0.0.0
	replicaof 192.168.202.206 6379

在这里插入图片描述

# 2.启动3台机器进行测试
- cd /usr/redis/bin
- ./redis-server /root/master/redis.conf
- ./redis-server /root/slave1/redis.conf
- ./redis-server /root/slave2/redis.conf

无法解决:

  1. master节点出现故障的自动故障转移问题

Redis哨兵机制

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

在这里插入图片描述

搭建主从复制

# 1.在主节点上创建哨兵配置
- 在Master对应redis.conf同目录下新建sentinel.conf文件,名字绝对不能错;

# 2.配置哨兵,在sentinel.conf文件中填入内容:
 - sentinel monitor 被监控数据库名字  ip port 1
	说明:这个后面的数字,是指当有两个及以上的sentinel服务检测到master宕机,才会去执行主从切换的功能。

# 3.启动哨兵模式进行测试	
- redis-sentinel  /root/sentinel/sentinel.conf

哨兵模式的全部配置!

# Example sentinel.conf

# 哨兵sentinel实例运行的端口 默认26379
port 26379

# 哨兵sentinel的工作目录
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。

#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# shell编程
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!

无法解决:

  1. 单节点并发压力问题
  2. 单节点内存和磁盘物理上限

springboot操作redis哨兵

# redis sentinel 配置(这个是启动哨兵机制时的配置)
# master书写是使用哨兵监听的那个名称,比如sentinel monitor mymaster 192.168.42.137 6377 2  
spring.redis.sentinel.master=mymaster
# 连接的不再是一个具体redis主机,书写的是多个哨兵节点
spring.redis.sentinel.nodes=192.168.42.137:26377,192.168.42.137:26378,192.168.42.137:26379
# 注意:如果连接过程中出现如下错误:
RedisConnectionException: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command ‘CONFIG SET protected-mode no’ from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2)

# 解决方案:
在哨兵的配置文件中加入bind 0.0.0.0 开启远程连接权限

在这里插入图片描述

Redis集群

集群架构

Redis在3.0后开始支持Cluster模式,目前redis的集群支持节点的自动发现,支持slave-master选举和容错,支持在线分片(sharding shard )等特性。

在这里插入图片描述

集群细节

- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
- 节点的fail是通过集群中超过半数的节点检测失效时才生效. 
- 客户端与redis节点直连,不需要中间proxy层。客户端不需要连接集群所有节点,连接集群任何一个可用节点即可
- redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node<->slot<->value
    - 集群的节点需要对0-16383的节点进行完全覆盖
    - 分片时,增加集群节点,对应就要重新分配slot ,slot的值也要迁移到新的节点上

在这里插入图片描述

集群搭建

判断一个是集群中的节点是否可用,是集群中的所用主节点选举过程,如果半数以上的节点认为当前节点挂掉,那么当前节点就是挂掉了,所以搭建redis集群时建议节点数最好为奇数,搭建集群至少需要三个主节点,三个从节点,至少需要6个节点

# 1.Redis5.0以下版本 准备环境安装ruby以及redis集群依赖
- yum install -y ruby rubygems
- gem install redis-xxx.gem

在这里插入图片描述

在这里插入图片描述

# 2.在一台机器创建7个目录

在这里插入图片描述

# 3.每个目录复制一份配置文件
[root@localhost ~]# cp redis-4.0.10/redis.conf 7000/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7001/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7002/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7003/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7004/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7005/
[root@localhost ~]# cp redis-4.0.10/redis.conf 7006/

在这里插入图片描述

# 4.修改不同目录配置文件
- port 	6379 .....                		 //修改端口
- bind  0.0.0.0                   		 //开启远程连接
- daemonize yes								// 开启守护进程
- pidfile /var/run/redis_7000.pid
- cluster-enabled  yes 	        			 //开启集群模式
- cluster-config-file  nodes-port.conf //集群节点配置文件
- cluster-node-timeout  5000      	   //集群节点超时时间
- appendonly  yes   		               //开启AOF持久化


# 5.指定不同目录配置文件启动七个节点
- [root@localhost bin]# ./redis-server  /root/7000/redis.conf
- [root@localhost bin]# ./redis-server  /root/7001/redis.conf
- [root@localhost bin]# ./redis-server  /root/7002/redis.conf
- [root@localhost bin]# ./redis-server  /root/7003/redis.conf
- [root@localhost bin]# ./redis-server  /root/7004/redis.conf
- [root@localhost bin]# ./redis-server  /root/7005/redis.conf
- [root@localhost bin]# ./redis-server  /root/7006/redis.conf

在这里插入图片描述

# 6.查看进程
- [root@localhost bin]# ps aux|grep redis

在这里插入图片描述

1.创建集群
# 方式一 Redis5.0 以下版本
## 1.复制集群操作脚本到bin目录中
- [root@localhost bin]# cp /root/redis-4.0.10/src/redis-trib.rb .

## 2.创建集群
- Redis5.0 以下版本
- ./redis-trib.rb create --replicas 1 192.168.202.205:7000 192.168.202.205:7001 192.168.202.205:7002 192.168.202.205:7003 192.168.202.205:7004 192.168.202.205:7005

# 方式二 Redis5.0 以上版本
- redis-cli --cluster create  节点:端口 --cluster-replicas 1 -a foobared
-
- redis-cli --cluster create  --cluster-replicas 1  192.168.17.130:7000 192.168.17.130:7001 192.168.17.130:7002 192.168.17.130:7003 192.168.17.130:7004 192.168.17.130:7005 -a 123

在这里插入图片描述

# 3.集群创建成功出现如下提示

在这里插入图片描述

2.查看集群状态
# 1.查看集群状态 check [原始集群中任意节点]
./redis-trib.rb check 192.168.202.205:7000

# 2.集群节点状态说明
- 主节点 
	主节点存在hash slots,且主节点的hash slots 没有交叉
	主节点不能删除
	一个主节点可以有多个从节点
	主节点宕机时多个副本之间自动选举主节点

- 从节点
	从节点没有hash slots
	从节点可以删除
	从节点不负责数据的写,只负责数据的同步
3 添加主节点
# 1.添加主节点 add-node [新加入节点] [原始集群中任意节点]
 ./redis-trib.rb  add-node 192.168.1.158:7006  192.168.1.158:7005
注意:
	1.该节点必须以集群模式启动
	2.默认情况下该节点就是以master节点形式添加
4 添加从节点
# 1.添加从节点 add-node --slave [新加入节点] [集群中任意节点]
- ./redis-trib.rb  add-node --slave 192.168.1.158:7006 192.168.1.158:7000
- 注意:
	当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点
	
# 2.为确定的master节点添加主节点 add-node --slave --master-id master节点id [新加入节点] [集群任意节点]
./redis-trib.rb  add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006  127.0.0.1:7000
5 删除副本节点
# 1.删除节点 del-node [集群中任意节点] [删除节点id]
./redis-trib.rb  del-node 127.0.0.1:7002 0ca3f102ecf0c888fc7a7ce43a13e9be9f6d3dd1

- 注意:
	1.被删除的节点必须是从节点或没有被分配hash slots的节点
6 集群在线分片
# 1.在线分片 reshard [集群中任意节点] 
./redis-trib.rb  reshard  192.168.1.158:7000
7 新版本创建集群的相关命令
[root@k8smaster bin]# ./redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN
                 --cluster-replicas <arg>
  check          host:port
                 --cluster-search-multiple-owners
  info           host:port
  fix            host:port
                 --cluster-search-multiple-owners
                 --cluster-fix-with-unreachable-masters
  reshard        host:port
                 --cluster-from <arg>
                 --cluster-to <arg>
                 --cluster-slots <arg>
                 --cluster-yes
                 --cluster-timeout <arg>
                 --cluster-pipeline <arg>
                 --cluster-replace
  rebalance      host:port
                 --cluster-weight <node1=w1...nodeN=wN>
                 --cluster-use-empty-masters
                 --cluster-timeout <arg>
                 --cluster-simulate
                 --cluster-pipeline <arg>
                 --cluster-threshold <arg>
                 --cluster-replace
  add-node       new_host:new_port existing_host:existing_port
                 --cluster-slave
                 --cluster-master-id <arg>
  del-node       host:port node_id
  call           host:port command arg arg .. arg
                 --cluster-only-masters
                 --cluster-only-replicas
  set-timeout    host:port milliseconds
  import         host:port
                 --cluster-from <arg>
                 --cluster-copy
                 --cluster-replace
  backup         host:port backup_directory
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
8 springboot操作集群
# redis cluster 操作 书写集群中所有节点
spring.redis.cluster.nodes=192.168.202.206:7000,192.168.202.206:7001,192.168.202.206:7002,192.168.202.206:7003,192.168.202.206:7004,192.168.202.206:7005,192.168.202.206:7006

Redis实现分布式Session管理

管理机制

​ redis的session管理是利用spring提供的session管理解决方案,将一个应用session交给Redis存储,整个应用中所有session的请求都会去redis中获取对应的session数据。

在这里插入图片描述

开发Session管理

引入依赖

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
server.port=8989
server.servlet.context-path=/redissession

# redis cluster
spring.redis.cluster.nodes=192.168.202.206:7000,192.168.202.206:7001,192.168.202.206:7002,192.168.202.206:7003,192.168.202.206:7004,192.168.202.206:7005,192.168.202.206:7006
@Configuration
@EnableRedisHttpSession    //将整个应用中使用session的数据全部交给redis处理
public class RedisSessionManager {
   
}

测试

@Controller
@RequestMapping("test")
public class TestController {
    // redis  session   list    1

    // jvm  list 地址    list.add   list.size  2

    //使用redis 的session管理  注意:当session中数据发生变化时必须将session中变化的数据同步到redis中
    @RequestMapping("test")
    public void test(HttpServletRequest request, HttpServletResponse response) throws IOException {
        
        List<String> list = (List<String>) request.getSession().getAttribute("list");
        if(list==null){
            list = new ArrayList<>();
        }
        list.add("xxxx");
        request.getSession().setAttribute("list",list);//每次session变化都要同步session

        response.getWriter().println("size: "+list.size());
        response.getWriter().println("sessionid: "+request.getSession().getId());
    }

    @RequestMapping("logout")
    public void logout(HttpServletRequest request){
        //退出登录
        request.getSession().invalidate();//失效
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值