redis

2021/7/17 19:07:35

本文主要是介绍redis,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1、NOSQL&Redis概述

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库

NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。

基本都是内存数据库,查询速度极快

特点

  • 不遵循SQL标准。
  • 不支持ACID
  • 远超于SQL的性能。

Redis概述

  • Redis是一个开源的**key-value**存储系统。
  • 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)
  • 这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,且这些操作都是原子性的。
  • 在此基础上,Redis支持各种不同方式的排序
  • 与memcached一样,为了保证效率,数据都是缓存在内存中。
  • 但是Redis可以将数据持久化
  • 并且在此基础上实现了master-slave(主从)同步

使用场景

  • 分布式Session存储
  • 验证码
  • 秒杀 计数器
  • 排行榜

。。。

Redis的安装

参考word文档笔记

相关知识即小总结

  • Redis默认端口 6379

  • 同一个库中不能有同名的key,会进行覆盖,不同库之间可以存在同名key

  • Redis也是内存数据库 但是可以持久化

  • 默认有16个数据库,初始默认使用0号库,所有库密码相同

    select n 切换数据库
    
  • Redis是单线程+多路IO复用

  • 常用命令

    1、Docker进入容器中使用客户端连接redis服务器
     redis-cli 
    
    2、Linux下启动redis服务
        redis-server /xx/redis.conf  以指定目录下的配置文件启动redis
    3、客户端连接
        redis-cli -p xxx 指定连接哪个端口的redis服务
        如果有密码 使用 auth 密码 
    4、启动哨兵服务
    	redis-sentienl  /xx/sentinel.conf 启动哨兵服务以指定的配置文件
    
    
    dbsize   查看当前数据库的key数量
    
    flushdb  清空当前库
    
    flushall 清空所有库
    
    keys * 查看当前库的所有key
    
    del keyName 删除key
    
    unlink keyName 根据value选择非阻塞删除 仅将key从keyspace中元数据中删除,真正的删除会在后续异步操作
    
    type keyName 查看key对应的value类型
    
    exists keyName 判断指定key是否存在
    
    expire keyName n 为指定的key设置一个过期时间 单位:秒 (不设置默认永不过期)
    
    ttl keyName 查看这个key还有多少秒过期 -1表示永不过期 -2表示已经过期 
    

2、数据类型

可以将Redis看做就是一个Map,key是String,value就是下面的5中数据类型

2.1字符串String

2.1.1概述

  • String类型是Redis中最基本使用最多的数据类型 value类似Java中的String
  • 所以说这个Map就可以看做是Map<String,String>
  • String类型是二进制安全的,即Redis的String可以包含任何数据,比如jpg图片或者是序列化的对象
  • 一个Redis字符串value最多可以是512M
  • 同一个库中不存在相同的key,会进行覆盖 不同库中可以存在相同key

2.1.2常用命令

1、添加或者修改
    set key value    添加一个值
    mset k1 v1 k2 v2 ... 添加多个k-v

2、添加时直接设置过期时间
    setex key timen value  

3、以新换旧
    设置新值的同时返回原始值
    getset key newvalue

4、查询
	get key          获取指定key的value值
	mget k1 k2 ...   获取多个key的value值

5、范围查询
	getrange key start end  查询key的value指定长度值

6、范围覆盖(覆盖指定索引后面的value) 索引从0开始 左闭右闭 [ ]
    setrange key startn value

7、追加
	append key abc   在指定key的value值后面添加abc字符串

8、获取value长度
	strlen key       获取key的value长度

9、尝试添加
    setnx key value  添加一个值 如果不存在添加 存在就不添加 (用于分布式锁机制) 
	返回0代表设置失败 返回1代表设置成功

    msetnx k13 v1 k2 v2 添加多个值,如果不存在则添加 存在就不添加 只有所有的都添加成功才     成功(原子性 有一个失败就全部失败)
    返回0代表添加失败 返回1代表添加成功

10、原子增减
-----  下面这些操作都是原子性的 (单线程操作)
incr key         将指定key的value值加1 前提:value必须是数字
decr key         将指定key的value值减1 前提:value必须是数字
incrby key n     将指定key的value值加指定的值 前提:value必须是数字
dncrby key n     将指定key的value值减指定的值 前提:value必须是数字

2.1.3数据结构

  • 底层类似于StringBuffer/StringBuilder,动态字符串,可以动态的扩容,
  • 如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M
    在这里插入图片描述

2.2列表List

2.2.1概述

  • 单键多值
  • 是一个简单的字符串列表,可以在头部或者尾部插入取出数据
  • 底层实际上是一个双向链表
  • 通俗可以看做是一个Map<String,List<String>>
    在这里插入图片描述

2.2.2常用命令

1、从左边或者右边插入数据,先进来的数据在最前面
    lpush key v1 v2 v3 ...
    rpush key v1 v2 v3 ...  
 
2、从列表的左边或者右边取出一个值,如果此列表中值全被取出了,那么这个列表就不存在了
	lpop key
	rpop key

3、按照索引下标范围遍历元素从左到右 (跟lpop不同 这个只是查询功能,不会将数据取出) 
   如果写 lrange key 0 -1 则会取出所有的数据
    lrange key start end

4、获取列表长度
    llen key

5、获取指定索引位置n上的元素(索引从0开始)
    lindex key n 

6、从k1列表取出一个元素插入到k2列表的左边
     rpoplpush k1 k2 

7、在指定的元素oldvalue左边插入一个元素newvalue
    linsert key before oldvalue newvalue

8、从左边开始删除n个指定value的元素
     lrem key n value

9、替换列表中索引为n的值
	 lset key n newvalue

2.3集合Set

2.3.1概述

  • 跟Java的HashSet类似,元素无序且不可重复
  • 是String类型的无序 不重复集合,底层就是一个Hash表,所以添加删除查找复杂度都是O(1)
  • Map<String,Set<String>>

2.3.2常用命令

1、添加元素(已存在的元素不会添加 不重复型) 返回值代表插入成功的数据的个数
    sadd key v1 v2 v3 ...

2、查询出指定key的集合中的所有元素
     smembers key

3、判断指定集合中是否包含某一个元素(v1)
	 sismember key v1

4、返回集合中的元素个数
 	scard key

5、删除集合中的指定元素
	srem key v1 v2 ...

6、随机从集合中取出n个元素并返回 (相当于从集合中随机删除并返回删除的值)
	spop key n
	
7、随机从集合中获取n个元素 (不会将元素从集合中删除)
	srandmember key n

8、将k1集合中的一个元素v1移动到k2集合中
	smove k1 k2 v1	
	
9、返回两个集合的交集
   	sinter k1  k2

10、返回两个集合的并集元素。 
	sunion k1 k2

11、返回两个集合的差集元素  (即找出key1中有的k2没有的元素)
	sdiff k1 k2

2.4哈希Hash

2.4.1概述

  • Hash数据类型本质就是一个Map<String,Object>
  • 本质就是一个哈希表
  • 整个数据可以用Map<String,Map<String,Object>>

2.4.2常用命令

整个数据可以看做是- Map<String,Map<String,Object>>
将最外层的key取名叫key 内部的map的key取名叫innerkey

1、添加数据(可以添加多个数据)
    hset key innerkey1 v1 innerkey2 v2 ....

2、取出一个数据
	hget key innerkey

3、判断Hash中是否存在某个字段
	hexists key innerkey

4、获取指定key的Hash中所有的key
	 hkeys key

5、获取指定key的Hash中的所有value
	hvals key

6、将指定key的Hash中的指定key的value值加n (前提:此value必须是数字类型)
	 hincrby key innerkey n

7、为指定key的innerkey的value赋值 (innerkey不存在时赋值 存在不赋值) 成功返回1 失败返回0
	hsetnx key innerkey value

2.5有序集合Zset

2.5.1概述

  • 跟Hash类似,但是每一个innerkey都有一个评分,可以根据这个评分进行排序
  • Map<String,Map<String,Double>>

2.5.2常用命令

1、添加数据
	zadd key score1 innerkey1 score2 innerkey2 ...

2、返回根据socre排序完成之后的指定索引范围的元素
 > 写0 -1返回所有数据
 > 带withscores score也会一起显示
   	  zrange key index_start index_end withscores

3、返回score在指定范围的元素 (从小到大)
	zrangebyscore key score_min score_max [withscores] [limit]分页

4、返回score在指定范围的元素 (从大到小)
	zrevrangebyscore key score_max score_min [withscores] [limit]分页

5、为指定的innerkey加上n
  zincrby key n innerkey
 
6、 删除一个Key中的innerkey
   zrem key innerkey

7、统计一个Key中指定score范围的innerkey的个数
   zcount key score_min score_max

8、返回一个innerkey在一个key中的排名(从0开始 从小到大)
	zrank key innerkey		

2.5.3跳跃表

参考word文档

https://www.yuque.com/u21639638/kv9qg4/19986153

2.6-新数据类型1_Bitmaps

https://www.yuque.com/u21639638/kv9qg4/19986153

2.7-新数据类型2_HyperLoglog

https://www.yuque.com/u21639638/kv9qg4/19986153

2.8-新数据类型3_Geospatial

https://www.yuque.com/u21639638/kv9qg4/19986153

2.9总结

各个数据类型应用场景:

类型简介特性场景Java表示
String(字符串)二进制安全可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512MMap<String,Object>
Hash(字典)键值对集合,即编程语言中的Map类型适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去)存储、读取、修改用户属性Map<String,Map<String,Object>>
List(列表)链表(双向链表)增删快,提供了操作某一段元素的API1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列Map<String,List<Object>>
Set(集合)哈希表实现,元素不重复1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作**1、**共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐Map<String,Set<Object>>
Sorted ZSet(有序集合)将Set中的元素增加一个权重参数score,元素按score有序排列数据插入集合时,已经进行天然排序1、排行榜 2、带权重的消息队列Map<String,Map<Object,Double>>

3、配置文件redis.conf详解

参考Word文档

https://www.yuque.com/u21639638/kv9qg4/19986153#ST3hw

  • 当配置了密码时,使用redis-cli启动客户端时,要使用auth 密码进行验证

4、Redis发布和订阅

类似MQ

参考Word文档

https://www.yuque.com/u21639638/kv9qg4/19986153#ST3hw

5、Jedis(Java操作Redis)

5.1创建连接

1.导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.1</version>
</dependency>

2.创建Jedis对象

@Test
public void test(){
    //Redis有密码的话使用带密码的构造器
    Jedis jedis = new Jedis("ip",6379);
    //测试是否连接成功 返回pong代表连接成功
    String isConn = jedis.ping();
    System.out.println(isConn);
}

5.1常用方法

//删除key
jedis.del("username");

//切换数据库
jedis.select(9);

Long size = jedis.dbSize();

Set<String> keys = jedis.keys("*");

//查看value类型
String type = jedis.type("username");

Boolean username = jedis.exists("username");

//设置过期时间
Long username1 = jedis.expire("username", 2000);

//查看过期时间
Long ttl = jedis.ttl("username");

//关闭
jedis.close();

5.2操作String类型的数据

方法和redis的命令基本一致

jedis.set("username", "张三");

String username = jedis.get("username");

//添加并设置过期时间
jedis.setex("password", 100, "123456");

jedis.mset("age","2","gender","男");

List<String> info = jedis.mget("username", "password", "age", "gender");

//获取原值并设置新值
String username1 = jedis.getSet("username", "李四");
System.out.println(username1);

//查看value的指定长度
String password = jedis.getrange("password", 0L, 1L);
System.out.println(password);

//key的value长度
Long strlen = jedis.strlen("age");
System.out.println(strlen);

//有不添加 没有才添加
Long setnx = jedis.setnx("username", "王五");
System.out.println(setnx);

//年龄+1
Long age = jedis.incr("age");
System.out.println(jedis.get("age"));

5.3操作List类型的数据

//从左边放数据
Long lpush = jedis.lpush("username", "zhangsna", "李四", "jack");
String username = jedis.lpop("username");

//取出数据 
List<String> names = jedis.lrange("username", 0L, -1L);
for (String name : names) {
    System.out.println(name);
}

//获取长度
Long llen = jedis.llen("username");
System.out.println(llen);

//获取指定位置上的值
String lindex = jedis.lindex("username", 2);
System.out.println(lindex);

//取出数据
 String name = jedis.lpop("key");

6、事务&锁&LUA脚本

6.1事务概述

  • Redis也是支持事务的,但是Redis的事务不支持ACID
  • Redis的事务分为两个阶段,首先使用multi命令开启一个事务,此时是组队阶段,即所有的命令不会执行,可以使用exec命令进入执行阶段,此时所有的操作按照顺序依次执行
  • 可以使用discard命令将组队阶段的所有操作全部撤销,即放弃事务,进入执行阶段的事务不能被撤销

在这里插入图片描述

事务中某些操作出现错误

开启一个事务,难免有些操作会出现错误,有两种情况

  • 如果说在组队阶段有操作报错,那么使用exec进入执行阶段时所有的命令都不会执行
  • 如果说在组队阶段没有操作报错,但是在执行阶段有操作报错,比如使用incr对一个key的value进行加操作,但是这个value是一个文本字符串那么其他的操作仍然会执行,只有这个错误的操作不会执行成功(即不保证原子性)

6.2事务的特性

单独的隔离操作

事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

没有隔离级别的概念

队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

不保证原子性

事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

6.3Redis乐观锁及watch key

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

watch key

在开启一个事务之前,可以使用watch [key1] [key2] [key3],可以监视一个或多个key,如果在事务执行之前这些key被改变了,value或者key的名字被改变,那么这个事务将被打断(被取消)

unwatch key

取消 WATCH 命令对所有 key 的监视。

如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

官方文档说明

6.4Redis实现分布式锁

setnx

博客一

博客二

6.5LUA脚本

word文档

7、持久化

>>7.1概述

因为Redis中的数据都在内存中,我们想要将这些内存中的数据保存到硬盘中,就得使用持久化技术

Redis的持久化方式

  • RDB(快照) 默认使用保存某一时刻的数据状态
  • AOF(Append Only File) 只追加日志文件记录所有的写(增删改)命令到日志文件中

>>7.2持久化之RDB

7.2.1特点

  • 类似于虚拟机的快照机制,给当前的Redis的状态拍摄一个快照,主要是保存当前的所有数据,这种方式可以将某一时刻的所有数据都写入硬盘,保存的文件默认的文件名是dump.rdb
  • 然后Redis服务器在下一次启动时就会读取这个dump.rdb文件,将数据还原

在这里插入图片描述

7.2.2快照生成方式

7.2.2.1.客户端手动生成

BGSAVE

  • 运行流程: 客户端可以使用BGSAVE命令来创建一个快照==(dump.rdb)==,当接收到客户端的BGSAVE命令时,redis会调用fork来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求
  • 名词解释: fork 当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化: 在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务
  • 好处:创建快照进程(子进程)不影响主进程执行操作,且子进程在没有父进程没有写操作时效率高

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9kXkvSZ7-1626509252120)(image/BGSAVE.png)]

SAVE

SAVE命令会阻塞主进程的操作,在SAVE执行完毕之前主进程无法执行操作,一般不使用
在这里插入图片描述

7.2.2.2.服务端满足条件自动触发

  • **1、**当服务器接收到客户端的shutdown指令即关闭redis服务器时,会执行SAVE命令,阻塞所有的客户端,不在执行客户端发送的任何命令,并且在save命令执行完毕之后关闭服务器
  • **2、**如果用户在redis.conf中配置了save配置选项,那么redis会在save选项条件满足后自动触发一次BGSAVE命令,如果设置多个save配置选项,只要任意一个save配置选项满足,redis也会触发一次BGSAVE命令。可以自定义配置

默认的触发条件

save 900 1    ---->900秒有一个key发生变化就生成一次dump.rdb文件
save 300 10   ---->300秒有10个key发生变化就生成一次dump.rdb文件
save 60 1000  ---->60秒内有1000个key发生变化就生成一次dump.rdb文件

在这里插入图片描述

生成快照文件的名称和位置

修改生成快照名称
    dbfilename dump.rdb
修改生成位置
    dir ./

在这里插入图片描述

7.2.3RDB的缺陷

可能会丢失数据,当某一时刻满足生成rdb文件的条件时会生成一次,然后这时又执行了一些写操作,但是不满足生成rdb文件的条件,这时突然Redis宕机(非正常的shutdown),那么这些数据就不会被生成快照,就造成了数据的丢失。

>>7.3持久化之AOF

7.3.1概述

将Redis执行的写操作都记录到日志文件AOF中,以此来记录数据发生的变化,所以在Redis服务器启动时只需执行一遍AOF文件即可将数据恢复
在这里插入图片描述

7.3.2开启AOF持久化

Redis默认没有开启AOF持久化,需要在Redis.conf中手动开启

# 1.开启AOF持久化
- a.修改 appendonly yes 开启持久化
- b.修改 appendfilename "appendonly.aof" 指定生成文件名称(AOF文件)
默认的位置跟RDB配置的dir位置相同

在这里插入图片描述

7.3.3日志追加频率

默认是everysec

  • always: 每执行一个写命令Redis都会记录到AOF文件中,大量并发来会出现问题。
  • everysec: 一秒执行一次写入到日志文件的操作,记录多条命令,相当于批量写入
  • no: 完全由操作系统决定何时写入到日志文件
# 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的处理命令请求的速度变慢。

7.3.4修改追加频率

Redis.conf中修改

# 1.修改日志同步频率
- 修改appendfsync everysec|always|no 指定

在这里插入图片描述

7.3.5AOF文件的重写

作用:用来在一定程度上减少AOF文件的体积

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

7.3.6重写方式

1、客户端重写

- 执行BGREWRITEAOF命令 不会阻塞redis的服务

2、服务器配置自动触发

可以设置两个参数

auto-aof-rewrite-percentage
   代表触发重写时当前文件大小是否比上一次文件大小的大了百分之xx

auto-aof-rewrite-min-size
   代表触发重写的当前文件的大小
   
比如默认的两个参数的值分别是100和64,也就是说当AOF文件大小到大64MB时,会触发一次重写,当比如重写后可能变成了30MB,然后运行一段时间后到了60MB,这时满足了第一个条件,即60比30大了百分之百,但是不满足重写的最小文件大小64M,则不会重写,当AOF文件到达了64MB时才会重写,以次类推	(重写使用的也是BGREWRITEAOF操作)

在Redis.conf中找

在这里插入图片描述

7.3.7重写原理

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

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

在这里插入图片描述

>>7.4总结

  • 如果RDB和AOF同时开启,Redis启动时默认会使用AOF,AOF保存数据更加完整安全

    来源Redis.conf官方的解释

  • 官方推荐两个都启用。如果对数据不敏感,可以选单独用RDB。不建议单独用 AOF,因为可能会出现Bug。如果只是做纯内存缓存,可以都不用。

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

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

8、主从复制

8.1修改配置文件

1、主节点配置

主节点无需额外进行配置

# 开启对外连接
bind 0.0.0.0

# 指定端口
port 6379

2、从节点配置

REPLICATION块中配置,可以配置多个从节点

# redis 5之后是replicaof 主节点ip 主节点Redis服务器的端口
replicaof 192.168.80.37 6379

# 注意:如果主节点的Redis服务器配置了密码 那么从节点这里一定要配置 如果主节点没有配置密码,那么这里注释即可
masterauth 主节点密码

在这里插入图片描述

经过测试,当主节点添加数据时,从节点自动的也有数据

8.2注意事项及总结

  • 默认从节点只能读,不能进行写操作,可以通过修改从节点配置文件中的replica-read-only修改,不过基本不会修改,从节点只是用来当做同步主节点的数据,从节点不进行写操作
 replica-read-only yes/no 
  • 当主节点挂掉之后,从节点不能进行自动的当做主节点使用(需要进行哨兵机制的操作)

9、哨兵机制

9.1概述

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

9.2哨兵机制运行

哨兵可以设置多个,哨兵刚开始监控主节点,向主节点发送心跳检测,检测主节点是否还在运行,一旦半数以上的哨兵判断主节点已经挂掉了,那么就会从从节点中选举出一个节点作为主节点对外服务

在这里插入图片描述

9.3配置哨兵运行

哨兵的运行是基于主从复制的架构,所以要先搭建好主从复制的架构,参考上一节的配置

哨兵的配置

创建一个sentinel.conf(必须以此命名)配置文件,首次就编写下面的内容即可,当整个架构运行起来后,后续的内容会自动填充

# 编写下面一行配置 前两个是固定写法,第三个是给此架构起一个名字,一般起mymaster
# ip是主节点的ip 端口是主节点的端口 1是至少有多少个哨兵同意进行选举的个数
sentinel monitor mymaster 192.168.80.37 7002 1

# 如果主节点有密码 必须配置密码
sentinel auth-pass mymaster redis

启动主从节点以及哨兵

注意:默认哨兵服务的端口号是26379

# 启动主从节点
进入bin目录下 运行redis-server命令
redis-server 主节点的配置文件目录/redis.conf
redis-server 从节点的配置文件目录/redis.conf
redis-server 从节点的配置文件目录/redis.conf

# 运行哨兵
默认bin目录下没有redis.sentinel脚本,在redis目录下的src下有,拷贝一份到bin目录下,然后启动
redis-sentinel /哨兵的配置文件目录/sentinel.conf

运行起来后,再看sentinel.conf,自动配置了一些关键的配置,比如端口号,从节点的ip端口等

sentinel myid 7e298931454708c4a4a6b3ac5977c279f6c684c0
# Generated by CONFIG REWRITE
port 26379
dir "/usr/local/redis/bin"
protected-mode no
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 192.168.80.37 7002 1
sentinel auth-pass mymaster redis
sentinel config-epoch mymaster 4
daemonize no
sentinel leader-epoch mymaster 9
sentinel known-replica mymaster 192.168.80.37 7001
sentinel known-replica mymaster 192.168.80.37 7000
sentinel current-epoch 9

测试

当所有的服务都启动完成后,将主节点手动shutdown掉,看到哨兵服务的日志打印,切换了一个从节点作为了主节点,测试了新的主节点是可以进行写操作的,将挂掉的主节点启动,发现哨兵的日志打印原主节点已经成为了新主节点的一个从节点(说明哨兵+主从复制架构搭建成功)

9.4选举新节点规则

  • 哪个从机会被选举为主机呢?根据优先级别:slave-priority
  • 原主机重启后会变为从机。
    在这里插入图片描述

按照下面的三种顺序判断选举哪个从节点

  1. 优先级在redis.conf中默认:slave-priority 100,值越小优先级越高,
  2. 偏移量是指获得原主机数据最全的 选数据最全的
  3. 每个redis实例启动后都会随机生成一个40位的runid ,选runid小的

9.5SpringBoot操作哨兵

不在配置Redis的服务器ip和端口,只需配置哨兵的ip和端口即可

spring:
  redis:
    sentinel:
     # sentinel.conf中配置的自定义集群名称
      master: mymaster
      # 是一个集合,可配置多个哨兵
      nodes: [192.168.80.37:8123]
      # Redis密码
      password: redis
      #Redis的密码
    password: redis

9.6总结及存在的问题

总结

  • 当主节点挂掉后,只有当半数以上的哨兵判断此节点挂掉那么才会从从节点中选举出一个新的主节点,否则不会进行选举
  • 当选举出一个新的主节点后,如果原主节点恢复使用后,那么原主节点不会成为主节点,只会成为新主节点的从节点使用

存在的问题

虽然哨兵解决了主从复制架构中的主节点挂掉后从节点无法对外服务的缺陷,但是仍然存在着整个结构只有一台服务进行工作的问题,在高并发情况下显然是会崩掉的,而且由于做持久化,久而久之AOF文件会越来越大,服务器迟早会崩掉,所有我们要做成集群的模式,多台Redis对外进行服务,实现高可用。

下一节,我们就来实现Redis的集群模式

10、集群

10.1概述

为了解决及哨兵模式的不足,即无法进行数据的压力分担,即只有一台服务器对外服务,Redis自3.0开始引入了集群(Cluster)模式,支持主从+哨兵,支持数据分片,将数据分片在多台服务器上,实现了压力分担。

但是,redis的集群模式,跟普通的集群模式是不同的

**1、**redis的集群通过客户端可以直接连接redis服务器,不用使用中间代理,比如我们在做Tomcat集群时必须使用Nginx进行负载均衡,将请求分发到Tomcat集群中,

**2、**redis的集群可以直接每一台服务器(主节点)存储的数据是不同的,有分片的概念,将不同的数据根据规则存储到不同的redis服务器上,

**3、**集群中的各个节点之间会进行ping-pong机制(内部使用了二进制协议优化了传输速度)的心跳检测,如果超过一半的节点认为一个节点挂了,那么这个节点就是挂了, 所以建议搭建集群时,节点的个数最好是奇数个

10.2工作流程&槽&CRC16算法

上面也说了Redis的集群中的节点存储的数据时不同的,就是数据的分片,如何分片呢?,如何进行数据的寻找呢?

  • Redis对集群数据的存储使用的是槽slot,从0-16383号,一共是16384个槽位,然后根据集群中的节点个数,(这里说的节点个数不包括从节点,只包括对外提供服务的主节点),对这个槽位进行平均分配,比如下面如果我们有设置了三个节点,那么node1可能的槽位就是0-5461,node2可能是5642-10922,node3可能是10923-16383,然后客户端进行操作数据时先会对key进行CRC16的计算然后%16383,计算出这个key所在槽位的节点,然后重定向到指定节点进行操作
  • 当然,不同的key通过CRC16计算肯定是不同的,但是对16383求余之后可能得到相同的值,即不同的key占用的槽位是相同的,但是不影响,直接存储即可。
    在这里插入图片描述

10.3集群的搭建

前提: 安装ruby环境,并使用redis-xxx.gem

yum源安装的ruby版本太老,建议使用安装包的形式,进行编译安装,建议使用ruby2.3.x

安装ruby出现问题

上传一个redis-xx.gem.tar.gz,解压并使用gem命令

gem install redis-xxx.gem

1、复制redis.conf分别到/root/redis_cluster/700n下,然后分别修改配置

这里是模拟集群,所以都在一台机器上创建,这里我们创建6个redis服务,三主三从

# 4.修改不同目录配置文件 这里为了简单不设置密码 注意修改一些文件输出文件的名字
- port 	7000 ...               		  //修改端口
- bind  0.0.0.0                   		  //开启远程连接
- cluster-enabled  yes 	        			 //开启集群模式
- cluster-config-file  nodes-port.conf //集群节点配置文件
- cluster-node-timeout  5000      	   //集群节点超时时间
- appendonly  yes   		               //开启AOF持久化

2、启动所有的redis服务

进入redis的bin目录
	redis-server /xxx/redis.conf
	.....

使用 ps -aux | grep redis 查看进程
	发现每个redis后面都是cluster,即以集群模式启动

3、拷贝redis目录下的src目录下的redis-trib.rb脚本到bin目录(4.x使用,5.x直接使用redis-cli即可)

cp /usr/local/redis/srcredis-trib.rb ../bin

4、执行脚本,将启动的所有redis服务都放到一个集群里

命令详解

https://www.cnblogs.com/ivictor/p/9768010.html

因为这里我用的是redis5.x ,官方推荐使用redis-cli命令

1、创建集群命令
redis-cli --cluster create
192.168.80.101:7000 192.168.80.101:7001 192.168.80.101:7002 192.168.80.101:7003 192.168.80.101:7004 192.168.80.101:7005 
--cluster-replicas 1
# 1代表每个主节点配备几个从节点 然后会进行自动计算主节点及从节点

2、查看集群状态 (输入任意一个节点ip端口即可)
 redis-cli --cluster check 192.168.80.101:7001

执行完之后,集群会创建完毕,主从节点都会自动标识,如果不满意,可以输入no,然后重新执行命令再次生成,输入yes即可使用集群
在这里插入图片描述

10.4集群操作数据

集群不支持操作批量的key,
在这里插入图片描述

10.5主节点及从节点的规则

- 主节点 
	主节点存在hash slots,且主节点的hash slots 没有交叉,即都是顺序分配(xx-xxx)
	主节点不能删除
	一个主节点可以有多个从节点
	主节点宕机时多个副本之间自动选举主节点,如主节点恢复则变为从节点

- 从节点
	从节点没有hash slots
	可以删除一个从节点
	从节点不负责数据的写,只负责数据的同步

10.6添加主节点

						    	[新加节点]         [原集群中的任意一个节点]
redis-cli --cluster add-node 192.168.80.101:7006 192.168.80.101:7005
 
注意 
    1.该节点必须以集群模式启动
	2.默认情况下该节点就是以master节点形式添加
	3.添加完主节点后此节点是没有slot的,需要重新的进行分片	

10.7添加从节点

# 1.添加从节点                    新节点             集群任意节点
- ./redis-cli --cluster add-node 192.168.80.101:7006 192.168.80.101:7000 --cluster-slave
- 注意:
	当添加副本节点时没有指定主节点,redis会随机给副本节点较少的主节点添加当前副本节点
	
# 2.给指定的master节点添加主节点   新节点         集群中的任意节点
- ./redis-cli --cluster add-node 192.168.80.101:7006 192.168.80.101:7000 --cluster-slave --cluster-master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e (指定的节点id)

10.8从新分片

当添加了一个新的主节点时,此时这个新的主节点是没有slot的,需要进行重新分片

可以指定某些节点进行给指定的节点进行槽位移动,以及槽位的个数

							[集群中的任意一个节点]			
redis-cli --cluster reshard 192.168.80.101:7000

在这里插入图片描述

10.9删除节点

# 1.删除节点 
			                   [集群中任意节点] 
- redis-cli --cluster del-node 192.168.80.101:7002 0ca3f102ecf0c888fc7a7ce43a13e9be9f6d3dd1 ([删除节点id])

- 注意:
 1.被删除的节点必须是从节点或没有被分配hash slots的主节点

10.10故障恢复

如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?

  • 如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes(默认),那么 ,整个集群都挂掉`
  • 如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储,但是集群不会挂。
  • redis.conf中的参数 cluster-require-full-coverage

10.11SpringBoot操作集群

直接在配置文件中配置即可

spring:
  redis:
    cluster:
      nodes:  
      #写上所有节点的ip端口
     - 192.168.80.101:7000 
     - 192.168.80.101:7001
     ....

10.12总结及缺陷

集群自带了主从+哨兵功能,十分的强大

好处

  • 实现扩容
  • 分摊压力
  • 无中心配置相对简单

不足

  • 多键操作是不被支持的
  • 多键的Redis事务是不被支持的。lua脚本不被支持
  • 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

11、三大缓存问题

11.1缓存穿透

指查询一个不存在的key,但是redis有没有将null值写入缓存,导致所有的请求全去查询数据库,大量请求进来DB有可能崩掉
在这里插入图片描述

解决办法

1、对空值缓存(最简单)
	如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟

2、设置可访问的名单(白名单)
	使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。

3、采用布隆过滤器
	(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

4、进行实时监控:
	当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

11.2缓存雪崩

在某一时刻大量的key过期,但是有大量的请求进来,因为这些key过期了,大量的请求全都去查询数据库,可能导致数据库崩掉
在这里插入图片描述

解决方案

1、构建多级缓存架构
	nginx缓存 + redis缓存 +其他缓存(ehcache等)
2、使用锁或队列:
	用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况

3、设置过期标志更新缓存:
	记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。

4、过期时间给随机值(常用)
	比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

11.3缓存击穿

有一个热点key突然过期,这时大量请求进来全部去查询数据库,可能导致数据库崩掉
在这里插入图片描述

解决方案

1、预先设置热门数据:
	在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
2、实时调整:
	现场监控哪些数据热门,实时调整key的过期时长
3、使用锁:(一般都是分布式锁 redisson等)
	大量请求进来如果缓存失效,那么加锁只有一个线程去查询数据库,查到数据后将结果放入缓存,后面的线程直接从缓存中获取即可

12、Redis6.0新特性

word文档笔记



这篇关于redis的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程