文章目录

  • 1.redis简介
  • Redis是什么?
  • NoSQL
  • Redis能干嘛?
  • Redis技术
  • redis的原子性
  • 2.启动
  • 前台启动(不推荐)
  • 后台启动
  • 3.五大数据类型
  • Redis键(Key)
  • 3.1 字符串(Srting)
  • 3.1.1简介
  • 3.1.2 常用命令
  • 3.1.3 数据结构
  • 预分配扩容机制
  • 3.2 列表(List)
  • 3.2.1 简介
  • 3.2.2 常用命令
  • 3.2.3 数据结构
  • 3.3 集合(Set)
  • 3.3.1 简介
  • 3.3.2 常用命令
  • 3.3.3 数据结构
  • 3.4 哈希(Hash)
  • 3.4.1 简介
  • 3.4.2 常用命令
  • 3.4.3 数据结构
  • 3.5 Redis有序集合Zset(sorted set)
  • 3.5.1 简介
  • 3.5.2 常用命令
  • 3.5.3 数据结构
  • 3.5.4 跳跃表(跳表)
  • 4.配置文件介绍
  • 5.发布和订阅
  • 概念
  • 步骤
  • 发布订阅命令行实现
  • 6.新数据类型
  • 6.1 Bitmaps
  • 简介
  • 命令
  • Bitmaps与set对比
  • 6.2 HyperLogLog
  • 简介
  • 命令
  • 6.3.Geospatial
  • 简介
  • 命令
  • 7.Jedis(Java程序操作Redis的工具)
  • 7.1导入Jedis依赖坐标
  • 7.2.连接Redis注意事项
  • 7.3 Jedis常用操作
  • 7.3.1.创建动态的工程
  • 7.3.2.创建测试程序
  • 7.4 测试相关数据类型
  • 7.4.1.Jedis-API: Key
  • 7.4.2.Jedis-API: String
  • 7.4.3.Jedis-API: List
  • 7.4.4.Jedis-API: set
  • 7.4.5.Jedis-API: hash
  • 7.4.6.Jedis-API: zset
  • 8.Redis_Jedis_实例
  • 8.1.完成一个手机验证码功能
  • 9.Redis与Spring Boot整合
  • 9.1.整合步骤
  • 10.事务和锁机制
  • 10.1.Redis的事务定义
  • 10.2.Multi、Exec、discard
  • 10.3.事务的错误处理
  • 10.4.为什么要做成事务
  • 10.5.事务冲突的问题
  • 例子
  • 悲观锁解决
  • 乐观锁解决
  • 10.6.Redis事务三特性
  • 10.7.命令
  • 10.8秒杀案例
  • 秒杀并发模拟
  • 手动联网安装
  • 超卖问题
  • 解决
  • 继续增加并发-连接超时问题
  • 数据库连接池解决连接超时问题
  • 库存遗留问题
  • LUA脚本
  • LUA脚本在Redis中的优势
  • 11.持久化之RDB
  • RDB是什么
  • 备份是如何执行的
  • Fork
  • RDB持久化流程
  • dump.rdb文件
  • 配置位置
  • 如何触发RDB快照;保持策略
  • 命令
  • save VS bgsave
  • flushall命令
  • Save
  • stop-writes-on-bgsave-error
  • rdbcompression 压缩文件
  • rdbchecksum 检查完整性
  • rdb的备份
  • RDB优势
  • 劣势
  • 停用RDB
  • 总结
  • 12.Redis持久化之AOF
  • AOF是什么
  • AOF持久化流程
  • 开启AOF
  • AOF和RDB同时开启,谁的优先级更高
  • AOF启动/修复/恢复
  • 优势
  • 劣势
  • 总结
  • AOF和RDB的选用
  • 主从复制
  • 什么是主从复制
  • Redis的主从复制原理
  • 如何搭建redis主从复制?
  • 主从模式不足
  • 常用3招
  • 一主二仆
  • 薪火相传
  • 反客为主
  • 哨兵模式
  • 简介
  • 原理
  • 多哨兵模式
  • 1) 主观下线
  • 2) 客观下线
  • 3) 投票选举
  • 13.集群
  • 13.1问题
  • 13.2什么是集群
  • 13.3删除持久化数据
  • 13.4制作6个实例,6379,6380,6381,6389,6390,6391
  • 13.4.1配置基本信息
  • 13.4.2redis cluster配置修改
  • 13.4.3.修改好redis6379.conf文件,拷贝多个redis.conf文件
  • 13.4.4.使用查找替换修改另外5个文件
  • 13.4.5.启动6个redis服务
  • 13.5将六个节点合成一个集群
  • 13.6.-c 采用集群策略连接,设置数据会自动切换到相应的写主机
  • 13.7.通过 cluster nodes 命令查看集群信息
  • 13.8.redis cluster 如何分配这六个节点?
  • 13.9什么是slots
  • 在集群中录入值
  • 查询集群中的值
  • 故障恢复
  • 集群的Jedis开发
  • Redis 集群好处
  • Redis 集群不足
  • 在集群中录入值
  • 查询集群中的值
  • 故障恢复
  • 集群的Jedis开发
  • Redis 集群好处
  • Redis 集群不足


1.redis简介

Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存可持久化日志型Key-Value数据库,并提供多种语言的API。

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费和开源!是当下最热门的 NoSQL 技术之一,也被人们称之为结构化数据库。

  • Redis是一个开源的key-value存储系统
  • 支持存储的value类型相对更多,包括String,list,set,zset,hash
  • 数据类型支持push/pop、add/remove 以及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的
  • 支持不同方式的排序
  • 类似memcached,数据缓存内存中
  • 不同:Redis会周期性的把数据写入硬盘或者把修改操作写入追加的记录文件
  • 实现了master-slave(主从)同步

NoSQL

泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题

  • 不遵循SQL标准
  • 不支持ACID(原子性,一致性,隔离性,持久性),但并不是不支持事务
  • 远超于SQL的性能
    1.2.2 NoSQL使用场景
  • 数据高并发读写 (电商秒杀功能)
  • 海量数据读写
  • 对数据高可扩展性的

Redis能干嘛?

1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof) 2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)
6、…

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

Redis技术

Redis使用单线程+多路IO复用技术

多路复用是指用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select(select * 切换到 星号库)和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直至超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行

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

  • 串行:一个一个操作完成
  • 多线程+锁
  • 单线程+多路IO复用

redis的原子性

所谓原子操作是指不会被线程调度机制打断的操作。这种操作一旦开始,就一直运行到结束,中间不会有任何的context switch(切换到另一个线程)。

  • 单线程和多线程的原子性

(1)在单线程中,能够在单条指令中完成的操作都可以认为是“原子操作”,因为中断只能发生在指令之间。
(2)在多线程中,不能被其他进程(线程)打断的操作就叫原子操作。
Redis单命令的原子性只要得益于 Redis是单线程的。

案例:

java的多线程是否满足原子性?

在两个线程中分别进行i=0;和100次i++,最后的值为多少?

是2~200,要满足原子性,最后的值确定为200。

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

2.启动

前台启动(不推荐)

1.进入redis安装目录

默认安装目录为 /usr/local/bin

cd /usr/local/bin

2.输入redis-server前台启动

redis-server

但是窗口不能关闭,关闭了redis也一起关闭,且不能在该窗口中输入redis命令了,所以不推荐使用。

按ctrl+c退出

后台启动

1.进入解压 redis-x.x.x.tar.gz 后的文件夹中

2.复制/拷贝 redis.conf 到etc目录

cp redis.conf /etc/redis.conf

3.进入ect目录,输入ll或ls查看是否复制成功

4.vi编辑redis.conf

查找到daemonize no 改为 daemonize yes

vi redis.conf

相关vi命令

/查找的内容    找到相关行
i  	     	 进入编辑模式
shift + :	 退出读写
wq	     	 保存退出

5.通过配置文件后台启动redis

redis-server /etc/redis.conf

6.关闭redis

shutdown

或者

kill <进程号>

3.五大数据类型

Redis键(Key)

常用命令

keys *  	  #查看当前库所有key(匹配:keys *1)
exists key    #判断某个key是否存在
type key      #查看你的key是什么类型
del key  	  #删除指定的key的数据
unlink key    #根据value选择非阻塞删除
  	          #仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作
expire key 10 #10秒钟:为给定的key设置过期时间
ttl key		  #查看还有多少秒过期,-1表示永不过期,-2表示已过期

select 		  #切换数据库
dbsize	      #查看当前数据库的key的数量
flushdb 	  #清空当前数据库
flushall 	  #通杀所有库

3.1 字符串(Srting)

3.1.1简介

String是redis最基本的类型,可以理解成与Memcached一摸一样的类型,一个key对应一个value。
String类型是二进制安全的。意味着Redis的String可以包含任何数据。比如jpg图片或者序列化对象。
String类型是Redis最基本的数据类型,一个字符串value最多可以是512M。

3.1.2 常用命令
1.set <key><value> #添加键值对
*NX:当数据库中key不存在时,可以将key-value添加数据库
*XX:当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥
*EX:key的超时秒数
*PX:key的超时毫秒数,与EX互斥
2.get <key> #查询对应键值

3.append <key><value> #将给定的value追加到原值的末尾

4.strlen <key> #获得键值的长度

5.setnx <key><value> #只有在key不存在时 设置key的值

6.incr <key> #将key中存储的数字值增1
			 #只能对数字值操作,如果为空,新增值为1
7.decr <key> #减1

8.incrby/decrby <key> <步长> #增减数字值
mset <key1><value1><key2><value2>...
#同时设置一个或者多个key-value对
mget
#同时获得多个value
msetnx
#同时设置一个或多个key-value对,当且仅当所有给定key都不在

原子性,有一个失败则都失败

getrange <key><start><end>
#获得值的范围,类似java中的substring,前包,后包
setrange <key><起始位置><value>
#用<value>覆写<key>所储存的字符串值,从<起始位置>开始(索引从0开始)

setex <key><过期时间><value>
#设置键值的同时,设置过期时间,单位秒
getset<key><value>
#以新换旧,设置了新值的同时获得旧值
3.1.3 数据结构

String的数据结构为简单动态字符串(Simple Dynamic String)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。

预分配扩容机制

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

3.2 列表(List)

3.2.1 简介

特点:单键多值

Redis列表是简单的字符串列表,按照插入顺序排序,可以添加一个元素到表的头部或者尾部。
它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的结点性能会较差。

3.2.2 常用命令
lpush/rpush <key><value1><value2><value3>...
#从左边/右边插入值
lpop/rpop <key>
#从左边/右边吐出一个值。值在键在,值光键亡。

rpoplpush <key1><key2>
#从1列表右边吐出一个值,插到2列表左边

lrange <key><start><stop>
#按照索引下标获得元素(从左到右)

lrange mylist 0 -1
#0左边第一个,-1右边第一个,(0 -1表示获取所有)
lindex <key><index>
#按照索引下标获得元素(从左到右)
llen <key>
#获得列表长度

linsert <key> before <value><newvalue>
#在value的前面插入new值
lrem <key><n><value>
#从左边删除n个value(从左到右)
lset<key><index><value>
#将列表下标为index的值替换成value
3.2.3 数据结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1q5A8vXs-1667294299192)(redis.assets/image-20221031160035519.png)]

3.3 集合(Set)

3.3.1 简介

Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择。并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
Redis的Set是string类型的无序集合。它的底层是一个value为null的hash表,所以添加、删除、查找的复杂度都是O(1)。
一个算法,随着数据的增加,执行时间的长短,如果是1,数据增加,查找数据的时间不变。

特点:不会重复,可快速查找、添加、删除

3.3.2 常用命令

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

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

3.3.3 数据结构

Set数据结构是dict字典,字典是用哈希表实现的。
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

3.4 哈希(Hash)

3.4.1 简介

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

类似于Java里面的Map<String,Object>

Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

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

3.4.2 常用命令
hset <key><field><value>
#给<key>集合中的 <field>键赋值<value>
hget <key1><field>
#从<key1>集合<field>取出 value
hmset <key1><field1><value1><field2><value2>... 
#批量设置hash的值
hexists<key1><field>
#查看哈希表 key 中,给定域 field 是否存在。
hkeys <key>
#列出该hash集合的所有field
hvals <key>
#列出该hash集合的所有value
hincrby <key><field><increment>
#为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx <key><field><value>
#将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在
3.4.3 数据结构

Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

3.5 Redis有序集合Zset(sorted set)

3.5.1 简介

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。
因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

3.5.2 常用命令
zadd <key><score1><value1><score2><value2>…
#将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
zrange <key><start><stop> [WITHSCORES] 
#返回有序集 key 中,下标在<start><stop>之间的元素带WITHSCORES,可以让分数一
#起和值返回到结果集。
zrangebyscore <key> minmax [withscores] [limit offset count]
#返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max
# )的成员。有序集成员按 score 值递增(从小到大)次序排列。
zrevrangebyscore <key> maxmin [withscores] [limit offset count] 
#同上,改为从大到小排列。
zincrby <key><increment><value> 
#为元素的score加上增量
zrem <key><value>
#删除该集合下,指定值的元素
zcount <key><min><max>
#统计该集合,分数区间内的元素个数
zrank <key><value>
#返回该值在集合中的排名,从0开始。
3.5.3 数据结构

SortedSet(zset)是Redis提供的一个非常特别的数据结构。

一方面它等价于Java的数据结构Map<String,Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

zset底层使用了两个数据结构。

(1) hash , hash 的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。

(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

3.5.4 跳跃表(跳表)

1、简介
有序集合在生活中比较常见,例如根据成绩对学生排名,根据得分对玩家排名等。对于有序集合的底层实现,可以用数组、平衡树、链表等。数组不便元素的插入、删除;平衡树或红黑树虽然效率高但结构复杂;链表查询需要遍历所有效率低。Redis采用的是跳跃表。跳跃表效率堪比红黑树,实现远比红黑树简单。
2、实例
对比有序链表和跳跃表,从链表中查询出51
(1) 有序链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1JgjAiJT-1667294299194)(redis.assets/image-20221031161202967.png)]

(2)跳跃表

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

可以发现使用跳跃表的遍历效率远远高于普通的有序链表

4.配置文件介绍

5.发布和订阅

概念

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

Redis 客户端可以订阅任意数量的频道。

步骤

1.客户端可以订阅频道

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

2.当这个频道发送消息后,消息就会发送给订阅该频道的客户端

发布订阅命令行实现

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

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

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

6.新数据类型

6.1 Bitmaps

简介

现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Up6ObBs-1667294299195)(redis.assets/image-20221031202926531.png)]

合理地使用操作位能够有效地提高内存使用率和开发效率。
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
(1) Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
(2) Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。

命令

1、setbit
(1)格式

setbit key offset value

setbit设置Bitmaps中某个偏移量的值(0或1)

*offset:偏移量从0开始

2.getbit

getbit key offset

获取键的第offset位的值(从0开始算)

3、bitcount

bitcount key [start end]

统计字符串从start字节到end字节比特值为1的数量

4、bitop

bitop  and(or/not/xor) <destkey> [key…]

bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。

Bitmaps与set对比

假设网站有1亿用户, 每天独立访问的用户有5千万, 如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表

set和Bitmaps存储一天活跃用户对比

数据类型

每个用户id占用空间

需要存储的用户量

全部内存量

集合类型

64位

50000000

400MB

Bitmaps

1位

100000000

12.5MB

因此, 这种情况下使用Bitmaps能节省很多的内存空间, 尤其是随着时间推移节省的内存还是非常可观的

set和Bitmaps存储独立用户空间对比

数据类型

一天

一个月

一年

集合类型

400MB

12GB

144GB

Bitmaps

12.5MB

375MB

4.5GB

但Bitmaps并不是万金油, 假如该网站每天的独立访问用户很少, 例如只有10万(大量的僵尸用户) , 那么两者的对比如下表所示, 很显然, 这时候使用Bitmaps就不太合适了, 因为基本上大部分位都是0。
set和Bitmaps存储一天活跃用户对比(独立用户比较少)
数据类型 每个userid占用空间 需要存储的用户量 全部内存量
集合类型 64位 100000 64位100000 = 800KB
Bitmaps 1位 100000000 1位100000000 = 12.5MB

6.2 HyperLogLog

简介

在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。
但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
(1)数据存储在MySQL表中,使用distinct count计算不重复个数
(2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
能否能够降低一定的精度来平衡存储空间?

  • Redis推出了HyperLogLog

Redis HyperLogLog 是用来做基数统计的算法
优点是:

  • 在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

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

命令

1、pfadd

pfadd <key>< element> [element ...]   #添加指定元素到HyperLogLog 中

2、pfcount

pfcount [key …]

3、pfmerge

pfmerge <destkey> <sourcekey> [sourcekey ...]  
#将一个或多个HLL合并后的结果存储在另一个HLL中,比如每月活跃用户可以使用每天的活跃用户来合并计算可得

6.3.Geospatial

简介

Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

命令

1、geoadd

geoadd <key> <longitude> <latitude> <member> [longitude latitude member...]   #添加地理位置(经度,纬度,名称)

2、geopos

geopos  <key><member> [member...] 
# 获得指定地区的坐标值

3、geodist

geodist<key><member1><member2>  [m|km|ft|mi ]  
#获取两个位置之间的直线距离

4、georadius

georadius<key>< longitude><latitude>radius  m|km|ft|mi   
#以给定的经纬度为中心,找出某一半径内的元素

7.Jedis(Java程序操作Redis的工具)

7.1导入Jedis依赖坐标

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

7.2.连接Redis注意事项

1.禁用Linux的防火墙:Linux(CentOS7)里执行命令

systemctl stop/disable firewalld.service

2.redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no

3.需要先开启服务端

redis-server /etc/redis.conf

不开服务端也会超时

7.3 Jedis常用操作

7.3.1.创建动态的工程
7.3.2.创建测试程序
package com.qxy;
import redis.clients.jedis.Jedis;
public class Demo01 {
	public static void main(String[] args) {
		Jedis jedis = new Jedis("192.168.137.3",6379);
		String pong = jedis.ping();
		System.out.println("连接成功:"+pong);
		jedis.close();
	}
}
7.4 测试相关数据类型
7.4.1.Jedis-API: Key
jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");
Set<String> keys = jedis.keys("*");
System.out.println(keys.size());
for (String key : keys) {
System.out.println(key);
}
System.out.println(jedis.exists("k1"));
System.out.println(jedis.ttl("k1"));                
System.out.println(jedis.get("k1"));
7.4.2.Jedis-API: String
jedis.mset("str1","v1","str2","v2","str3","v3");
System.out.println(jedis.mget("str1","str2","str3"));
7.4.3.Jedis-API: List
List<String> list = jedis.lrange("mylist",0,-1);
for (String element : list) {
System.out.println(element);
}
7.4.4.Jedis-API: set
jedis.sadd("orders", "order01");
jedis.sadd("orders", "order02");
jedis.sadd("orders", "order03");
jedis.sadd("orders", "order04");
Set<String> smembers = jedis.smembers("orders");
for (String order : smembers) {
System.out.println(order);
}
jedis.srem("orders", "order02");
7.4.5.Jedis-API: hash
jedis.hset("hash1","userName","lisi");
System.out.println(jedis.hget("hash1","userName"));
Map<String,String> map = new HashMap<String,String>();
map.put("telphone","13810169999");
map.put("address","atguigu");
map.put("email","abc@163.com");
jedis.hmset("hash2",map);
List<String> result = jedis.hmget("hash2", "telphone","email");
for (String element : result) {
System.out.println(element);
}
7.4.6.Jedis-API: zset
jedis.zadd("zset01", 100d, "z3");
jedis.zadd("zset01", 90d, "l4");
jedis.zadd("zset01", 80d, "w5");
jedis.zadd("zset01", 70d, "z6");
 
Set<String> zrange = jedis.zrange("zset01", 0, -1);
for (String e : zrange) {
System.out.println(e);
}

8.Redis_Jedis_实例

8.1.完成一个手机验证码功能

要求:
1、输入手机号,点击发送后随机生成6位数字码,2分钟有效
Random

2、输入验证码,点击验证,返回成功或失败
把验证码放到redis里面,设置过期时间120s
从redis获取验证码和输入的验证码进行比较

3、每个手机号每天只能输入3次
incr 每次发送之后+1
大于2时候,提交不能发送

代码示例:

package com.qxy;

import redis.clients.jedis.Jedis;

import java.util.Random;

public class PhoneCode {
    public static void main(String[] args) {

    getRedisCode("19816303615","637938");


    }

    //校验
    public  static void getRedisCode(String phone,String code){
        Jedis jedis=new Jedis("192.168.92.128",6379);

        String codeKey="VerifyCode"+phone+":code";
        String redisCode = jedis.get(codeKey);
        if(redisCode.equals(code))
        {
            System.out.println("成功");
        }else{
            System.out.println("失败");
        }
        jedis.close();
    }
    //让每个手机每天只能发送三次,验证码放到redis中设置过期时间
    public  static void verifyCode(String phone){
        Jedis jedis=new Jedis("192.168.92.128",6379);

        //拼接key
        //phone
        String countKey = "VerifyCode"+phone+":count";
        //code
        String codeKey="VerifyCode"+phone+":code";
        //times
        String count = jedis.get(countKey);
        if(count==null) {
            //没有发送过
            jedis.setex(countKey, 24 * 60 * 60, "1");
        }else if(Integer.parseInt(count)<=2){
            jedis.incr(countKey);

        }else if(Integer.parseInt(count)>2){
            System.out.println("今天发送次数已经超过三次");
            jedis.close();
        }
        //验证码放到redis中
        String vcode = getCode();
        jedis.setex(codeKey,120,vcode);
        jedis.close();


    }


    //六位验证码
    public static String getCode(){
        Random random = new Random();
        String code = "";
        for (int i = 0; i < 6; i++) {
            int rand = random.nextInt(10);
            code +=rand;
        }

        return code;
    }
}

9.Redis与Spring Boot整合

9.1.整合步骤

1、在pom.xml文件中引入redis相关依赖

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

<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>

2、application.properties配置redis配置

#Redis服务器地址
spring.redis.host=192.168.140.136
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

3、添加redis配置类

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

   @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
//key序列化方式
        template.setKeySerializer(redisSerializer);
//value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

4、测试一下
RedisTestController中添加测试方法
@RestController
@RequestMapping(“/redisTest”)
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;

@GetMapping
public String testRedis() {
    //设置值到redis
    redisTemplate.opsForValue().set("name","lucy");
    //从redis获取值
    String name = (String)redisTemplate.opsForValue().get("name");
    return name;
}

10.事务和锁机制

10.1.Redis的事务定义

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。

10.2.Multi、Exec、discard

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。

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

案例:

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

组队成功,提交成功

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

组队阶段报错,提交失败

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

组队成功,提交有成功有失败情况

10.3.事务的错误处理

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

10.4.为什么要做成事务

场景:有很多人有你的账户,同时去参加双十一抢购

10.5.事务冲突的问题
例子

一个请求想给金额减8000
一个请求想给金额减5000
一个请求想给金额减1000

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

悲观锁解决

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

每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到他拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁解决

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

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

10.6.Redis事务三特性
  • 单独的隔离操作

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

  • 没有隔离级别的概念

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

  • 不保证原子性

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

10.7.命令

1.开始事务

multi

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行

2.执行

exec

输入Exec后,Redis会将之前的命令队列中的命令依次执行。

3.放弃组队

组队的过程中可以通过discard来放弃组队。

4.监视

WATCH key [key …]

5.取消监视

取消watch命令对所有key的监视

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

unwatch

10.8秒杀案例

秒杀并发模拟

使用工具ab模拟测试
CentOS6 默认安装
CentOS7需要手动安装

手动联网安装
yum install httpd-tools
超卖问题

使用ab工具模拟高并发,出现超卖问题。

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1H3olM2l-1667294299197)(redis.assets/image-20221101111123960.png)]

解决

利用乐观锁淘汰用户,解决超卖问题。

//增加乐观锁
jedis.watch(qtkey);
 
//3.判断库存
String qtkeystr = jedis.get(qtkey);
if(qtkeystr==null || "".equals(qtkeystr.trim())) {
System.out.println("未初始化库存");
jedis.close();
return false ;
}
 
int qt = Integer.parseInt(qtkeystr);
if(qt<=0) {
System.err.println("已经秒光");
jedis.close();
return false;
}
 
//增加事务
Transaction multi = jedis.multi();
 
//4.减少库存
//jedis.decr(qtkey);
multi.decr(qtkey);
 
//5.加人
//jedis.sadd(usrkey, uid);
multi.sadd(usrkey, uid);
 
//执行事务
List<Object> list = multi.exec();
 
//判断事务提交是否失败
if(list==null || list.size()==0) {
System.out.println("秒杀失败");
jedis.close();
return false;
}
System.err.println("秒杀成功");
jedis.close();
继续增加并发-连接超时问题

使用乐观锁解决超卖问题后,在高并发下可能出现连接超时问题

ab -n 2000 -c 200 -k -p postfile -T ‘application/x-www-form-urlencoded’ http://192.168.140.1:8080/seckill/doseckill

增加-r参数,-r Don’t exit on socket receive errors.
ab -n 2000 -c 100 -r -p postfile -T ‘application/x-www-form-urlencoded’ http://192.168.140.1:8080/seckill/doseckill
11.5.2.已经秒光,可是还有库存
ab -n 2000 -c 100 -p postfile -T ‘application/x-www-form-urlencoded’ http://192.168.137.1:8080/seckill/doseckill
已经秒光,可是还有库存。原因,就是乐观锁导致很多请求都失败。先点的没秒到,后点的可能秒到了。

数据库连接池解决连接超时问题

节省每次连接redis服务带来的消耗,把连接好的实例反复利用。
通过参数管理连接的行为

  • 链接池参数
  • MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
  • maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
  • MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
  • testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
库存遗留问题

使用乐观锁解决超卖问题后,可能出现库存遗留问题

LUA脚本

Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。 这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件。

LUA脚本在Redis中的优势

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。
利用lua脚本淘汰用户,解决超卖问题。
redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJtvCeHd-1667294299197)(redis.assets/image-20221101111712249.png)]

11.持久化之RDB

官网介绍:http://www.redis.io

Redis RDB持久化详解(原理+配置策略) (biancheng.net)

Redis 提供了2个不同形式的持久化方式。

  • RDB(Redis DataBase)
  • AOF(Append Of File)

RDB是什么

Redis DataBase 在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里

备份是如何执行的

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。

Fork

  • Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
  • 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”
  • 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

RDB持久化流程

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

dump.rdb文件

在redis.conf中配置文件名称,默认为dump.rdb

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

配置位置

rdb文件的保存路径,也可以修改。默认为Redis启动时命令行所在的目录下 dir “/myredis/”

如何触发RDB快照;保持策略
命令
save VS bgsave

save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。
bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。
可以通过lastsave 命令获取最后一次成功执行快照的时间

flushall命令

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

Save

格式:save 秒钟 写操作次数
RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,
默认是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。
禁用
不设置save指令,或者给save传入空字符串

stop-writes-on-bgsave-error

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

当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes.

rdbcompression 压缩文件

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

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

rdbchecksum 检查完整性

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

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

rdb的备份

先通过config get dir 查询rdb文件的目录
将*.rdb的文件拷贝到别的地方
rdb的恢复

  • 关闭Redis
  • 先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb
  • 启动Redis, 备份数据会直接加载

RDB优势

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高更适合使用
  • 节省磁盘空间
  • 恢复速度快

劣势

  • Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
  • 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。

停用RDB

动态停止RDB:redis-cli config set save “”#save后给空值,表示禁用保存策略

总结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1SNrHFwQ-1667294299198)(redis.assets/image-20221101120758819.png)]

12.Redis持久化之AOF

AOF是什么

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

AOF持久化流程

(1)客户端的请求写命令会被append追加到AOF缓冲区内;
(2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
(3)AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
(4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4u6YcP4P-1667294299198)(redis.assets/image-20221101113412239.png)]

开启AOF

AOF默认关闭

vi打开redis.conf,在redis.conf配置文件中搜索appendonly

将appendonly no 改为appendonly yes

appendonly.aof文件和dump.rdb的保存路径一致

AOF和RDB同时开启,谁的优先级更高

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

AOF启动/修复/恢复

  • AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。
    正常恢复
  • 修改默认的appendonly no,改为yes
  • 将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir)
  • 恢复:重启redis然后重新加载

异常恢复

  • 修改默认的appendonly no,改为yes
  • 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof–fix appendonly.aof进行恢复
  • 备份被写坏的AOF文件
  • 恢复:重启redis,然后重新加载

优势

  • 备份机制更稳健,丢失数据概率更低。
  • 可读的日志文本,通过操作AOF稳健,可以处理误操作。

劣势

  • 比起RDB占用更多的磁盘空间。
  • 恢复备份速度要慢。
  • 每次读写都同步的话,有一定的性能压力。
  • 存在个别Bug,造成恢复不能。

总结

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

AOF和RDB的选用

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

主从复制

什么是主从复制

主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库。在赋值过程中,一个服务器充当主服务器,而另外一台服务器充当从服务器。
当一台从服务器连接到主服务器时,从服务器会通知主服务器从服务器的日志文件中读取最后一次成功更新的位置。然后从服务器会接收从哪个时刻起发生的任何更新,然后锁住并等到主服务器通知新的更新

Redis的主从复制原理

主机数据更新后根据配置和策略, 自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7IO2NZyV-1667294299199)(redis.assets/image-20221101145911193.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UHMZd1C5-1667294299199)(redis.assets/image-20221101150827826.png)]主要的功能有:

  • 读写分离,性能扩展
  • 容灾快速恢复

具体主从的复制原理:
从机主动发送

  • Slave启动成功连接到master后,从机slave会发送一个sync命令
  • Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件到slave,以完成一次完全同步

1.全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。(刚开始从机连接主机,主机一次给)
2.增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步 (主机修改了数据会给予从机修改的数据同步,叫做增量复制)
断开之后重新连接,只要是重新连接master,一次完全同步(全量复制)将被自动执行,rdb的数据就会给从机。
主机负责写,从机负责读

如何搭建redis主从复制?

拷贝多个redis.conf文件include(写绝对路径)
开启daemonize yes
Pid文件名字pidfile
指定端口port
Log文件名字
dump.rdb名字dbfilename
Appendonly 关掉或者换名字

1.拷贝多个redis.conf文件include(写绝对路径)

Pid文件名字pidfile
指定端口port
Log文件名字
dump.rdb名字dbfilename
Appendonly 关掉或者换名字

2.新建redis6379.conf,填写以下内容

# redis的路径位置
include /myredis/redis.conf
#一下都是redis.conf配置文件中的属性,只需改变端口号即可
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb

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

3.新建redis6380.conf,填写以下内容

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

4.新建redis6381.conf,填写以下内容

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

  • slave-priority 10设置从机的优先级,值越小,优先级越高,用于选举主机时使用。默认100,在后面的哨兵模式中会有提及

5.启动三台redis服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6d60HTff-1667294299200)(redis.assets/image-20221101151446659.png)]

6.查看系统进程,看看三台服务器是否启动

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

7.查看三台主机运行情况

  • info replication打印主从复制的相关信息

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

8.配从(库)不配主(库)

slaveof <ip><port>成为某个实例的从服务器

  • 在6380和6381上执行: slaveof 127.0.0.1 6379

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

  • 在主机上写,在从机上可以读取数据
    在从机上写数据报错
  • 主机挂掉,重启就行,一切如初
  • 从机重启需重设: slaveof 127.0.0.1 6379
  • 可以将配置增加到文件中。永久生效。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7O3mkl7u-1667294299200)(redis.assets/image-20221101151926365.png)]

主从模式不足

主从模式并不完美,它也存在许多不足之处,下面做了简单地总结:

  • 1) Redis 主从模式不具备自动容错和恢复功能,如果主节点宕机,Redis 集群将无法工作,此时需要人为干预,将从节点提升为主节点。
  • 2) 如果主机宕机前有一部分数据未能及时同步到从机,即使切换主机后也会造成数据不一致的问题,从而降低了系统的可用性。
  • 3) 因为只有一个主节点,所以其写入能力和存储能力都受到一定程度地限制。
  • 4) 在进行数据全量同步时,若同步的数据量较大可能会造卡顿的现象。

常用3招

一主二仆
薪火相传
反客为主

哨兵模式

Redis集群:Sentinel哨兵模式(详细图解) (biancheng.net)

简介

在 Redis 主从复制模式中,因为系统不具备自动恢复的功能,所以当主服务器(master)宕机后,需要手动把一台从服务器(slave)切换为主服务器。在这个过程中,不仅需要人为干预,而且还会造成一段时间内服务器处于不可用状态,同时数据安全性也得不到保障,因此主从模式的可用性较低,不适用于线上生产环境。(主从模式不足1、2)

Redis 官方推荐一种高可用方案,也就是 Redis Sentinel 哨兵模式,它弥补了主从模式的不足。Sentinel 通过监控的方式获取主机的工作状态是否正常,当主机发生故障时, Sentinel 会自动进行 Failover(即故障转移),并将其监控的从机提升主服务器(master),从而保证了系统的高可用性。

原理

哨兵模式是一种特殊的模式,Redis 为其提供了专属的哨兵命令,它是一个独立的进程,能够独立运行。下面使用 Sentinel 搭建 Redis 集群,基本结构图如下所示:

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

在上图过程中,哨兵主要有两个重要作用:

  • 第一:哨兵节点会以每秒一次的频率对每个 Redis 节点发送PING命令,并通过 Redis 节点的回复来判断其运行状态。
  • 第二:当哨兵监测到主服务器发生故障时,会自动在从节点中选择一台将机器,并其提升为主服务器,然后使用 PubSub 发布订阅模式,通知其他的从节点,修改配置文件,跟随新的主服务器。
多哨兵模式

在实际生产情况中,Redis Sentinel 是集群的高可用的保障,为避免 Sentinel 发生意外,它一般是由 3~5 个节点组成,这样就算挂了个别节点,该集群仍然可以正常运转。其结构图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5md4ayAH-1667294299201)(redis.assets/image-20221101161016138.png)]

上图所示,多个哨兵之间也存在互相监控,这就形成了多哨兵模式,现在对该模式的工作过程进行讲解,介绍如下:

1) 主观下线

主观下线,适用于主服务器和从服务器。如果在规定的时间内(配置参数:down-after-milliseconds),Sentinel 节点没有收到目标服务器的有效回复,则判定该服务器为“主观下线”。比如 Sentinel1 向主服务发送了PING命令,在规定时间内没收到主服务器PONG回复,则 Sentinel1 判定主服务器为“主观下线”。

2) 客观下线

客观下线,只适用于主服务器。 Sentinel1 发现主服务器出现了故障,它会通过相应的命令,询问其它 Sentinel 节点对主服务器的状态判断。如果超过半数以上的 Sentinel 节点认为主服务器 down 掉,则 Sentinel1 节点判定主服务为“客观下线”。

3) 投票选举

投票选举,所有 Sentinel 节点会通过投票机制,按照谁发现谁去处理的原则,选举 Sentinel1 为领头节点去做 Failover(故障转移)操作。Sentinel1 节点则按照一定的规则在所有从节点中选择一个最优的作为主服务器,然后通过发布订功能通知其余的从节点(slave)更改配置文件,跟随新上任的主服务器(master)。至此就完成了主从切换的操作。

对上对述过程做简单总结:

Sentinel 负责监控主从节点的“健康”状态。当主节点挂掉时,自动选择一个最优的从节点切换为主节点。客户端来连接 Redis 集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点的地址,然后再去连接主节点进行数据交互。当主节点发生故障时,客户端会重新向 Sentinel 要地址,Sentinel 会将最新的主节点地址告诉客户端。因此应用程序无需重启即可自动完成主从节点切换。

13.集群

13.1问题

容量不够,redis如何进行扩容?
并发写操作, redis如何分摊?
另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。
之前通过代理主机来解决,但是redis3.0中提供了解决方案。就是无中心化集群配置。

13.2什么是集群

Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

13.3删除持久化数据

将rdb,aof文件都删除掉

13.4制作6个实例,6379,6380,6381,6389,6390,6391
13.4.1配置基本信息

开启daemonize yes
Pid文件名字
指定端口
Log文件名字
Dump.rdb名字
Appendonly 关掉或者换名字

13.4.2redis cluster配置修改
cluster-enabled yes    
#打开集群模式
cluster-config-file nodes-6379.conf  
#设定节点配置文件名
cluster-node-timeout 15000   
#设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。
include /home/bigdata/redis.conf
port 6379
pidfile "/var/run/redis_6379.pid"
dbfilename "dump6379.rdb"
dir "/home/bigdata/redis_cluster"
logfile "/home/bigdata/redis_cluster/redis_err_6379.log"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
13.4.3.修改好redis6379.conf文件,拷贝多个redis.conf文件

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

13.4.4.使用查找替换修改另外5个文件

例如::%s/6379/6380

13.4.5.启动6个redis服务

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

13.5将六个节点合成一个集群

组合之前,请确保所有redis实例启动后,nodes-xxxx.conf文件都生成正常。

  • 合体:
    cd /opt/redis-6.2.1/src
redis-cli --cluster create --cluster-replicas 1
 192.168.11.101:6379 192.168.11.101:6380
 192.168.11.101:6381 192.168.11.101:6389
 192.168.11.101:6390 192.168.11.101:6391

此处不要用127.0.0.1, 用真实IP地址

–replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。

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

  • 普通方式登录
    可能直接进入读主机,存储数据时,会出现MOVED重定向操作。所以,应该以集群方式登录.
13.6.-c 采用集群策略连接,设置数据会自动切换到相应的写主机

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

13.7.通过 cluster nodes 命令查看集群信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U6nQNejf-1667294299202)(redis.assets/f8cb1e66a43340dd8abc15a03ac2a2ed.png)]

13.8.redis cluster 如何分配这六个节点?

一个集群至少要有三个主节点。
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。

13.9什么是slots

[OK] All nodes agree about slots configuration.
Check for open slots…
Check slots coverage…
[OK] All 16384 slots covered.
一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。
举个例子, 如果一个集群可以有主节点,
其中:
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽。

在集群中录入值

在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。

redis-cli客户端提供了 –c 参数实现自动重定向。

如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。

不在一个slot下的键值,是不能使用mget,mset等多键操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q3BjIKQo-1667294299202)(redis.assets/074571639f19480bb2edbf0866cacc16.png)]

可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XhNHyHGP-1667294299202)(redis.assets/042eb2673e1542aebce9cc8610db8cd5.png)]

查询集群中的值
CLUSTER GETKEYSINSLOT <slot><count> 
#返回 count 个 slot 槽中的键。
故障恢复

如果主节点下线?从节点能否自动升为主节点?注意:15秒超时
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EmLVzx2L-1667294299202)(redis.assets/843045e01d1b4c8eaa1ca6c4c28586be.png)]

主节点恢复后,主从关系会如何?主节点回来变成从机。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vYjXAQfO-1667294299202)(redis.assets/90e49a495896483090f550a4cc7ac84a.png)]

如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage

集群的Jedis开发

即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。
无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。

public class JedisClusterTest {
  public static void main(String[] args) { 
     Set<HostAndPort>set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("192.168.31.211",6379));
     JedisCluster jedisCluster=new JedisCluster(set);
     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
}
Redis 集群好处

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

Redis 集群不足

多键操作是不被支持的
多键的Redis事务是不被支持的。lua脚本不被支持
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。
中一个,
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。
举个例子, 如果一个集群可以有主节点,
其中:
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽。

在集群中录入值

在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。

redis-cli客户端提供了 –c 参数实现自动重定向。

如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。

不在一个slot下的键值,是不能使用mget,mset等多键操作。

[外链图片转存中…(img-Q3BjIKQo-1667294299202)]

可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。
[外链图片转存中…(img-XhNHyHGP-1667294299202)]

查询集群中的值
CLUSTER GETKEYSINSLOT <slot><count> 
#返回 count 个 slot 槽中的键。
故障恢复

如果主节点下线?从节点能否自动升为主节点?注意:15秒超时
[外链图片转存中…(img-EmLVzx2L-1667294299202)]

主节点恢复后,主从关系会如何?主节点回来变成从机。
[外链图片转存中…(img-vYjXAQfO-1667294299202)]

如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么 ,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage

集群的Jedis开发

即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。
无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。

public class JedisClusterTest {
  public static void main(String[] args) { 
     Set<HostAndPort>set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("192.168.31.211",6379));
     JedisCluster jedisCluster=new JedisCluster(set);
     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
}
Redis 集群好处

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

Redis 集群不足

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