redis学习笔记

1、NoSQL数据库

(一)NoSQL数据库概述:

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。
NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。

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

(二)适用场景:对数据高并发的读写;海量数据的读写;对数据高可扩展性的。

(三)NoSQL不适用场景:

  • 需要事务支持
  • 基于sql的结构化查询存储,处理复杂的关系,需要即席查询。
  • (用不着sql的和用了sql也不行的情况,请考虑用NoSql)

NoSQL有哪些:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、Redis概述和安装

  • Redis是一个开源key-value存储系统。
  • 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)list(链表)set(集合)zset(sorted set --有序集合)hash(哈希类型)
  • 这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
  • 在此基础上,Redis支持各种不同方式的排序
  • 与memcached一样,为了保证效率,数据都是缓存在内存中
  • 区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。
  • 并且在此基础上实现了master-slave(主从)同步

原子性: 所谓原子操作是指不会被线程调度机制打断的操作; 这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个线程)。
(1)在单线程中, 能够在单条指令中完成的操作都可以认为是"原子操作",因为中断只能发生于指令之间。
(2)在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。 Redis单命令的原子性主要得益于Redis的单线程。

Redis安装

(Linux中)安装目录:/usr/local/bin

查看默认安装目录:
redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何
redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
redis-check-dump:修复有问题的dump.rdb文件
redis-sentinel:Redis集群使用
redis-server:Redis服务器启动命令
redis-cli:客户端,操作入口

2.1、Redis的启动和关闭

(一)前台启动(不推荐)

redis-server

在这里插入图片描述
(二)后台启动(推荐)

redis配置文件详解

  • 备份redis.conf
cp  /opt/redis-6.2.6/redis.conf	 /myredis
  • 后台启动设置daemonize no改成yes
    找到复制的redis.conf,然后用vi redis.conf编辑
    在这里插入图片描述

在这里插入图片描述

  • Redis启动
    在这里插入图片描述
  • 用客户端访问:redis-cli
    多个端口可以:redis-cli -p端口号(例:redis-cli -p6379)
    在这里插入图片描述
  • Redis关闭
    (一)在终端关闭
    在这里插入图片描述
    (二)单实例关闭:redis-cli shutdown
    在这里插入图片描述
    多实例关闭,指定端口关闭:redis-cli -p 端口号 shutdown (例:redis-cli -p 6379 shutdown)

2.2、Redis介绍相关知识

  • redis有默认16个数据库,类似数组下标从0开始,初始默认使用0号库
  • 使用命令 select <dbid>来切换数据库。如: select 8
  • 统一密码管理,所有库同样密码。

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

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

MemcacheRedis
多线程+锁单线程+多路IO复用

Redis与Memcache三点不同: 支持多数据类型,支持持久化,单线程+多路IO复用

在这里插入图片描述

2.3、Redis键(key)

redis是基于key:value存储的

命令作用
keys *查看当前库所有key
exists key判断某个key是否存在
type key查看你的key是什么类型
del key删除指定的key数据
unlink key根据value选择非阻塞删除
expire key 10为给定的key设置过期时间,这里是10秒(单位:秒)
ttl key查看key还有多少秒过期,-1表示永不过期,-2表示已过期
select 库号命令切换数据库(例:select 8 切换到8号数据库)
dbsize查看当前数据库的key的数量
flushdb清空当前库
flushall清空全部库

del keyunlink key 的区别:
del key 直接删除
unlink key仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作

3、数据类型

3.1、Redis字符串(String)

简介:

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

字符串(String)常用命令

命令作用
set <key> <value>添加键值对(当数据库中key不存在时,可以将key-value添加数据库;当数据库中key存在时,覆盖已有的value)
get <key>查询对应键值
append <key><value>将给定的<value> 追加到原值的末尾
strlen <key>获得值的长度
setnx <key><value>只有在 key 不存在时设置 key 的值,key存在则无法设置key的值(无法覆盖)
incr <key>将 key 中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
decr <key>将 key 中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
incrby/decrby <key> <步长>将 key 中储存的数字值增/减。自定义步长。
mset <key1> <value1> <key2> <value2>…同时设置一个或多个 key-value对
mget <key1> <key2> <key3>同时获取一个或多个 value
msetnx <key1> <value1> <key2> <value2>…同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在(若有一个key-value设置失败,则全部设置失败)
getrange <key> <起始位置> <结束位置>获得值的范围,类似java中的substring
setrange <key> <起始位置> <value>用 <value> 覆写<key>所储存的字符串值,从<起始位置>开始(索引从0开始)
setex <key> <过期时间> <value>设置键值的同时,设置过期时间,单位秒
getset <key> <value>设置了新值同时获得旧值。

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

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

3.2、Redis列表(List)

简介:

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

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

在这里插入图片描述
列表(List)常用命令

命令作用
lpush/rpush <key> <value1> <value2> <value3>…从左边/右边插入一个或多个值。
lpop/rpop <key>从左边/右边获取并移除一个值,类似于出栈/出队
rpoplpush <key1> <key2>从<key1>列表右边获取并移除一个值,插到<key2>列表左边。(剪切,粘贴)
lrange <key> <start> <stop>从左边遍历数据并指定开始和结束索引,0是第一个索引,-1终索引
lindex <key> <index>获取指定索引数据,没有则为null,没有索引越界
llen <key>获得列表长度
linsert <key> before <value> <newvalue>在<value>的后面插入<newvalue>值
lrem <key> <n> <value>从左边删除n个value(从左到右)
lset <key> <index> <value>将列表key下标为index的值替换成value

数据结构:
List的数据结构为快速链表quickList。
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。

当数据量比较多的时候才会改成quicklist。
因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。
在这里插入图片描述
Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

3.3、Redis集合(Set)

简介:

Redis
set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

Redis的Set是string类型的无序集合它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。

集合(Set)常用命令

命令作用
sadd <key> <value1> <value2>…将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers <key>取出该集合的所有值
sismember <key> <value>判断集合<key>是否为含有该<value>值,有1,没有0
scard <key>返回该集合的元素个数
srem <key> <value1> <value2> …删除集合中的某个或多个元素
spop <key>随机获取集合中的某个数据,并将该数据从集合中移除
srandmember <key> <n>随机从该集合中取出n个值,不会从集合中删除
smove <source> <destination> <value>将指定数据从原始集合移动到目标集合中
sinter <key1> <key2>返回两个集合的交集元素
sunion <key1> <key2>返回两个集合的并集元素
sdiff <key1> <key2>返回两个集合的差集元素(key1中的,不包含key2中的)

数据结构:

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

3.4、Redis哈希(Hash)

简介:

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

类似Java里面的Map<String,Object>
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:
在这里插入图片描述

哈希(Hash)常用命令

命令作用
hset <key> <field> <value>给<key>集合中的<field>键赋值<value>
hget <key1> <field>从<key1>集合<field>取出 value
hmset <key1> <field1> <value1> <field2> <value2>…批量设置hash的值
hexists <key1> <field>查看hash表中是否存在指定字段fieId
hkeys <key>列出该hash集合的所有field
hvals <key>列出该hash集合的所有value
hincrby <key> <field> <increment>指定字段的数值数据增加指定的值,increment为负数则减少
hsetnx <key> <field> <value>将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在

数据结构:

哈希类型的内部编码有两种:ziplist(压缩列表)、hashtable(哈希表、字典)。

当存储的数据量比较小的情况下,Redis才使用压缩列表来实现字典类型,具体需要满足两个条件:

  • 当键值对个数小于hash-max-ziplist-entries配置(默认512个)
  • 所有键值都小于hash-max-ziplist-value配置(默认64字节)

ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀,当ziplist无法满足哈希类型时,Redis会使用hashtable作为哈希内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

压缩列表
压缩列表(ziplist)是列表和哈希的底层实现之一,压缩列表用来紧凑数据存储,节省内存,有序:
在这里插入图片描述
压缩列表是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。

哈希表
Redis字典使用散列表为底层实现,一个散列表里面有多个散列表节点,每个散列表节点就保存了字典中的一个键值对,发生哈希冲突采用链表发解决,存储无序

  • 为了避免散列表性能的下降,当装载因子大于1的时候,Redis会触发扩容,将散列表扩大为原来大小的2倍左右
  • 当数据动态减少之后,为例节省内存,当装载因子小于0.1的时候,Redis就会触发缩容,缩小字典中数据个数的50%左右

3.5、Redis有序集合Zset(sorted set)

简介:

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。

不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的

因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

有序集合Zset(sorted set)常用命令

命令作用
zadd <key> <score1> <value1> <score2> <value2>…将一个或多个 member 元素及其 score 值加入到有序集 key 当中
zrange <key> <start> <stop> [WITHSCORES]获取指定范围内的数据,升序,WITHSCORES 代表显示分数
zrangebyscore key min max [withscores] [limit offset count]返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列
zrevrangebyscore key max min [withscores] [limit offset count]同上,改为从大到小排列
zincrby <key> <increment> <value>指定值的分数增加increment
zrem <key> <value>删除该集合下,指定值的元素
zcount <key> <min> <max>统计该集合分数区间内的元素个数
zrank <key> <value>返回该值在集合中的排名,从0开始

数据结构:

有序集合是由ziplist(压缩列表)或skiplist(跳跃表)组成的

当数据比较少时,有序集合使用的是ziplist存储的,使用ziplist格式存储需要满足以下两个条件:

  • 有序集合保存的元素个数要小于128个;
  • 有序集合保存的所以元素大小都小于64字节;

当元素比较多时,此时ziplist的读写效率会下降,时间复杂度是O(n),跳跃表的时间复杂度是O(logn)

跳跃表
Redis使用跳跃表作为有序集合的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现

跳跃表在链表的基础上增加了多级索引以提升查找的效率,索引是占内存的,所以是一个空间换时间的方案。原始链表中存储的有可能是很大的对象,而索引节点只需要存储关键值和几个指针,并不需要存储对象,因此当节点本身比较大或者元素数量比较多的时候,其优势可以被放大,而缺点则可以忽略

  • 基于单向链表加索引的方式实现
  • Redis的跳跃实现由zskiplist和zskiplistnode两个结构组成,其中zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistnode则用于表示跳跃表节点
  • Redis每个跳跃表节点的层高都是1至32之间的随机数(Redis5之后最大层数为64)
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的。跳跃表中的节点按照分值大小进行排序,当分值相同时节点按照成员对象的大小进行排序
    在这里插入图片描述

3.6、Bitmaps

简介:

现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位,合理地使用操作位能够有效地提高内存使用率和开发效率。

Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:

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

常用命令

命令作用
setbit <key> <offset> <value>设置指定key中偏移量上对应的bit值,value只能为0或1
getbit <key> <offset>获取指定key中偏移量上对应的bit值
bitcount <key> [start end]统计字符串从start字节到end字节比特值为1的数量
bitop and(or/not/xor) <destkey> [key…]做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中

3.7、HyperLogLog

简介:

在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。

但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
(1)数据存储在MySQL表中,使用distinct count计算不重复个数
(2)使用Redis提供的hash、set、bitmaps等数据结构来处理

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。

能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog

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

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

常用命令

命令作用
pfadd <key> <element> [element …]添加指定元素到 HyperLogLog 中
pfcount <key> [key …]计算HyperLogLog的近似基数,可以计算多个HyperLogLog
pfmerge <destkey> <sourcekey> [sourcekey …]将一个或多个HyperLogLog合并后的结果存储在另一个HyperLogLog中

3.8、Geospatial

简介:

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

常用命令

命令作用
geoadd <key> < longitude> <latitude> <member> [longitude latitude member…]加地理位置(经度,纬度,名称)
geopos <key> <member> [member…]获得指定地区的坐标值
geodist <key> <member1> <member2>获取两个位置之间的直线距离
georadius <key> <longitude> <latitude> radius以给定的经纬度为中心,找出某一半径内的元素

4、Redis的发布和订阅

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。Redis 客户端可以订阅任意数量的频道。

客户端可以订阅频道如下图:
在这里插入图片描述
当给这个频道发布消息后,消息就会发送给订阅的客户端:
在这里插入图片描述

常用命令

命令作用
subscribe channel [channel …]订阅给定的一个或多个频道
unsubscribe [channel [channel …]]退订一个或多个频道
publish channel message向指定频道发布消息
psubscribe pattern [pattern …]订阅一个或多个符合给定模式的频道
punsubscribe [pattern [pattern …]]退订一个或多个符合给定模式的频道
pubsub channels [pattern]列出当前的活跃频道
pubsub numsub [channel-1 … channel-N]返回给定频道的订阅者数量, 订阅模式的客户端不计算在内
pubsub numpat返回订阅模式的数量(注意, 这个命令返回的不是订阅模式的客户端的数量, 而是客户端订阅的所有模式的数量总和)

命令测试

---------------订阅端---------------

127.0.0.1:6379> SUBSCRIBE bg  #订阅频道bg
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "bg"
3) (integer) 1

---------------消息发布端---------------

127.0.0.1:6379> PUBLISH bg hello #向bg频道发布消息hello
(integer) 1
127.0.0.1:6379> 

这个时候再去看订阅端:
---------------订阅端---------------

127.0.0.1:6379> SUBSCRIBE bg 
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "bg"
3) (integer) 1
1) "message"  #消息
2) "bg"		#哪个频道的消息
3) "hello"	#消息内容

(注:当订阅端收到消息之后,输入命令无反应的情况,如下图)
在这里插入图片描述
这个时候按ctrl+c就可以解决了。

退订频道:

---------------订阅端---------------

127.0.0.1:6379> UNSUBSCRIBE bg  #退订bg频道
1) "unsubscribe"
2) "bg"
3) (integer) 0
127.0.0.1:6379> 

订阅一个或多个符合给定模式的频道:

127.0.0.1:6379> psubscribe news.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"		#显示订阅成功
2) "news.*"			#订阅的模式
3) (integer) 1		#目前已订阅模式的数量

退订一个或多个符合给定模式的频道:

127.0.0.1:6379> PUNSUBSCRIBE news.*
1) "punsubscribe"
2) "news.*"
3) (integer) 0

原理:

Redis是使用C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,籍此加深对Redis的理解。

Redis通过PUBLISH、SUBSCRIBE 和PSUBSCRIBE等命令实现发布和订阅功能。

通过SUBSCRIBE命令订阅某频道后, redis-server 里维护了一个字典,字典的键就是一个个 频道! , 而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定channel的订阅链表中。

通过PUBLISH命令向订阅者发送消息, redis-server 会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。

Pub/Sub从字面上理解就是发布( Publish )与订阅( Subscribe ) , 在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除。

使用场景

  1. 实时消息系统
  2. 实时聊天(频道当作聊天室,将信息回显给所有人即可!)
  3. 公众号订阅,微博关注等等(起始更多是使用消息队列来进行实现)

缺点

如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。

这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

5、Jeids

介绍

jedis 是 redis推荐的java客户端。通过Jedis我们可以很方便地使用java代码的方式,对redis进行操作。jedis使用起来比较简单,它的操作方法与redis命令相类似。

使用(这里的例子使用maven)

在使用之前我们得修改以下redis配置文件:

(一)这里的bind得注释掉,如果不注释掉,那就只能本机访问,后面的代码无法通过ip访问到。
在这里插入图片描述
(二)把这里的保护模式改为no(默认是yes),如果是yes也无法通过远程访问,也只能本机访问
在这里插入图片描述
(三)关闭防火墙(开启防火墙:systemctl start firewalld)
在这里插入图片描述
在这里插入图片描述

然后引入依赖:

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

然后创建一个类JedisDemo

public class JedisDemo {
    public static void main(String[] args) {
        //创建Jedis对象
        Jedis jedis = new Jedis("192.168.157.128",6379);
        
        //测试
        String value = jedis.ping();
        System.out.println(value);
    }

在这里插入图片描述
我们还能通过jedis用java代码对redis中的数据类型进行操作
jedis操作和使用

6、redis整合Springboot

  • 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.157.128
#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、测试
@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;
    }
}

在这里插入图片描述

7、事务和锁机制

我们早在学习MySQL的时候就知道事务(ACID)就是所谓的原子性(Atomicity)
一致性(Consistency),隔离性(Isolation),持久性(Durability)

  • 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency)
    事务前后数据的完整性必须保持一致。
  • 隔离性(Isolation)
    事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
  • 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

7.1、Redis事务

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

7.2、Redis事务操作过程

  • 开启事务(Multi
    从输入Multi命令开始,输入的命令都会依次进入命令队列中(为了方便理解,这里将其称为组队),但不会执行。

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

(注意:当我们执行完事务要重新开启事务!!!)

127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) OK
3) OK
127.0.0.1:6379> 
  • 取消事务(discard)
    组队的过程中可以通过discard来放弃组队,取消事务。
127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> set k6 v6
QUEUED
127.0.0.1:6379(TX)> DISCARD #取消事务
OK
127.0.0.1:6379> 

在这里插入图片描述

7.3、事务错误

代码语法错误(编译时异常)所有的命令都不执行。
就是组队中某个命令出现了报告错误,执行的时侯整个的所有队列都会被取消。

在这里插入图片描述

127.0.0.1:6379> MULTI #开启事务
OK
127.0.0.1:6379(TX)> set pym serein
QUEUED
127.0.0.1:6379(TX)> set bg ggz
QUEUED
127.0.0.1:6379(TX)> seete k1 v1  #没有这个语法,所以出现了编译时异常
(error) ERR unknown command `seete`, with args beginning with: `k1`, `v1`, 
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> EXEC  #执行事务
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get pym #因为出现了编译时异常,所以整个队列都不会被执行
(nil)

代码逻辑错误 (运行时异常) 其他命令可以正常执行 所以不保证事务原子性。
就是执行事务阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行

在这里插入图片描述

127.0.0.1:6379> MULTI  #开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> INCR k1 #这里因为k1的值是v1,无法加1,所以会报错
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC	#执行事务
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
127.0.0.1:6379> get k3 #能取到k3的值,说明后面两行命令也执行了
"v3"
127.0.0.1:6379> 

虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。 所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。

7.4、锁机制

(一)悲观锁
在这里插入图片描述

悲观锁(Pessimistic Lock)
顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞(block)直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

(二)乐观锁
在这里插入图片描述

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

redis实现乐观锁

使用watch key监控指定数据,相当于乐观锁加锁。
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

redis监视测试:

127.0.0.1:6379> set money 100 #设置余额:100
OK
127.0.0.1:6379> set usedMoney 0 #设置使用金额:0
OK
127.0.0.1:6379> WATCH money #监视余额(上锁)
OK
127.0.0.1:6379> MULTI	#开启事务
OK
127.0.0.1:6379(TX)> decrby money 30	#余额-30
QUEUED
127.0.0.1:6379(TX)> incrby usedMoney 30	#使用金额+30
QUEUED
127.0.0.1:6379(TX)> EXEC	#执行事务,监视值没有被中途修改,事务正常执行
1) (integer) 70
2) (integer) 30

模拟插队线程:

---------------主线程---------------

127.0.0.1:6379> WATCH money #监视money(上锁)
OK
127.0.0.1:6379> MULTI	#开启事务
OK	
127.0.0.1:6379(TX)> decrby money 20	#money-20
QUEUED
127.0.0.1:6379(TX)> incrby usedMoney 20	#usedMoney+20
QUEUED
127.0.0.1:6379(TX)> EXEC #执行事务
注意:这里还没有执行

当我们正要执行上述事务时,出现了下面的线程:
---------------插队线程---------------

127.0.0.1:6379> set money 500
OK

这时候我们再执行主线程的exec:

127.0.0.1:6379(TX)> EXEC	#执行事务
(nil)

这是因为,执行这个事务之前,另一个线程,修改了我们的值
简单点说,当我们对watch money上锁以后,我们就会在执行的时候把监视时候的money和执行前的money进行对比,如果两个money的值不一样(被修改了)就会出现(nil)

当事务执行失败了,我们需要先解锁,再重新上锁:

127.0.0.1:6379> UNWATCH	#解锁
OK
127.0.0.1:6379> WATCH money	#获取最新的money值,再上锁
OK
127.0.0.1:6379> MULTI	#开启事务
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby usedMoney 20
QUEUED
127.0.0.1:6379(TX)> EXEC	#执行事务时,会将现在的money和上锁时的money值进行对比,如果一样,那么执行成功,不一样则执行失败
1) (integer) 480
2) (integer) 20

watch指令在redis事物中提供了CAS的行为。为了检测被watch的keys在是否有多个客户
同时改变引起冲突,这些keys将会被监控。如果至少有一个被监控的key在执行exec命令
前被修改,整个事物将会回滚,不执行任何动作,从而保证原子性操作,并且执行exec会
得到null的回复。

这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。

8、redis持久化

为什么需要持久化?

Redis对数据的操作都是基于内存的,当遇到了进程退出、服务器宕机等意外情况,如果没有持久化机制,那么Redis中的数据将会丢失无法恢复。有了持久化机制,Redis在下次重启时可以利用之前持久化的文件进行数据恢复。理解和掌握Redis的持久机制,对于Redis的日常开发和运维都有很大帮助,也是在大厂面试经常被问到的知识点。Redis支持的两种持久化机制:

RDB:把当前数据生成快照保存在硬盘上。
AOF:记录每次对数据的操作到硬盘上。

8.1、RDB(Redis DataBase)持久化

(一)什么是RDB?

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

(二)备份是如何执行的

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

在这里插入图片描述
补充:

Fork

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

(三)相关配置

1、 默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件(redis.conf)中进行自定义。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

6、save
格式:save 秒钟 写操作次数
RDB是整个内存的压缩过的Snapshot,RDB的数据结构,可以配置复合的快照触发条件,
默认是15分钟内改了1次,或5分钟内改了10次,或1分钟内改了1万次.
就是如果15分钟内改了1次,就做持久化操作,以此类推。

flushall命令
flushall 命令也会触发持久化(可以先将dump.rdb文件删除,然后再执行flushall命令,就会发现会重新生成新的dump.rdb)

在这里插入图片描述

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

如何恢复rdb文件:

只需要将rdb文件放在我们redis启动目录就可以,
redis启动的时候会自动检查dump.rdb恢复其中的数据 查看需要存在的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"

(四)RDB的优缺点

优点:

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

缺点:

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

8.2、AOF(Append Only File)持久化

(一)什么是AOF?

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

(二)相关配置

因为AOF默认不开启,所以得在redis.conf中开启,还可以设置生成的文件名称,AOF文件的保存路径,同RDB的路径一致

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

在这里插入图片描述

AOF同步频率设置 :
appendfsync always 始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
appendfsync everysec 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
appendfsync no redis不主动进行同步,把同步时机交给操作系统。

在这里插入图片描述

(三)AOF持久化流程

  • (1)客户端的请求写命令会被append追加到AOF缓冲区内;
  • (2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
  • (3)AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
  • (4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;

在这里插入图片描述

(四)AOF文件的恢复和修复(文件损坏)

AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载。

  • 正常恢复(用两个终端来模拟,终端1和终端2)
    • 终端1:将有数据的aof文件复制一份保存到对应目录(查看目录:config get dir),然后删除原本的aof文件
    • 终端2:重启redis,会重新load加载AOF文件中的写操作达到数据恢复的目的。
    • 解释:就是如果A想将B的redis中的数据拿到自己的redis中,可以将B的aof文件复制一份,放到自己的目录下重启redis就行了

终端1:

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #写入三个key
OK
127.0.0.1:6379> shutdown 	#关闭redis

[root@localhost /]# ls  #发现有appendonly.aof文件
appendonly.aof  dev       home   media    opt   run   sys  var
bin             dump.rdb  lib    mnt      proc  sbin  tmp
boot            etc       lib64  myredis  root  srv   usr
[root@localhost /]# cp appendonly.aof appendonly.aof.bak	#复制一份appendonly.aof(这里复制的文件名随便取)
[root@localhost /]# rm -rf appendonly.aof	#删除appendonly.aof

终端2

[root@localhost /]# mv appendonly.aof.bak appendonly.aof	#将刚刚复制的文件改回来(appendonly.aof)

[root@localhost /]# redis-server /myredis/redis.conf	#重启redis
[root@localhost /]# redis-cli
127.0.0.1:6379> keys *	#查看之前存进去的key
1) "k3"
2) "k2"
3) "k1"
  • 异常恢复
    • 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof–fix appendonly.aof进行恢复
    • 恢复:重启redis,然后重新加载

先将之前的aof文件修改一下,让其损坏(这里加了hello):
在这里插入图片描述

[root@localhost /]# redis-server /myredis/redis.conf 	#启动redis
[root@localhost /]# redis-cli	
Could not connect to Redis at 127.0.0.1:6379: Connection refused	#提示连接拒绝
not connected> 

当启动redis的时候,会读取appendonly.aof文件,把文件中的内容进行加载。因为刚刚我对文件的内容进行的修改,文件就不对了,被我们玩坏了,就没法启动,告诉我们连接不了

/usr/local/bin中有一个redis-check-aof
在这里插入图片描述

[root@localhost /]# redis-check-aof --fix appendonly.aof 	#对appendonly.aof进行修复
0x              bb: Expected prefix '*', got: 'h'			#告诉我们哪里有问题
AOF analyzed: size=194, ok_up_to=187, ok_up_to_line=44, diff=7
This will shrink the AOF from 194 bytes, with 7 bytes, to 187 bytes
Continue? [y/N]: y					#询问是否继续,y修复,n取消
Successfully truncated AOF			#修复成功

再次查看appendonly.aof,会发现我们加的hello不见了
在这里插入图片描述
再去启动redis,就可以了

[root@localhost /]# redis-server /myredis/redis.conf
[root@localhost /]# redis-cli
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> 

(五)AOF的文件重写机制

什么是文件重写机制?

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.

相关设置

如果 no-appendfsync-on-rewrite yes ,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)
如果 no-appendfsync-on-rewrite no, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)

在这里插入图片描述

auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。

在这里插入图片描述

实现原理

首先创建新的AOF文件是通过aof_rewrite函数来实现,但是这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间的阻塞,因为Redis服务器使用单线程来处理命令请求;所以如果直接是服务器进程调用AOF_REWRITE函数的话,那么重写AOF期间,服务器将无法处理客户端发送来的命令请求。(说白了,就是占用主线程,导致其他请求无法被处理)。
为了解决以上问题,那么Redis将AOF重写程序放到子进程(后台)里执行,这样子进程进行AOF重写期间,主进程可以继续处理命令请求;子进程带有主进程的数据副本,使用子进程而不是线程,可以避免在锁的情况下,保证数据的安全性。

那么问题又来了,使用子进程进行AOF重写的问题,子进程在进行AOF重写期间,服务器进程还要继续处理命令请求,而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的AOF文件中的数据不一致。

为了解决这种数据不一致的问题,Redis增加了一个AOF重写缓存,这个缓存在fork出子进程之后开始启用,Redis服务器主进程在执行完写命令之后,会同时将这个写命令追加到AOF缓冲区和AOF重写缓冲区 。

重写流程

  • (1)bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。
  • (2)主进程fork出子进程执行重写操作,保证主进程不会阻塞。
  • (3)子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。
  • (4)1).子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。2).主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
  • (5)使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。

在这里插入图片描述

AOF文件重写的触发条件及触发方式

  • 触发条件

    • 没有BGSAVE命令(RDB持久化)/AOF持久化在执行;

    • 没有BGREWRITEAOF在进行;

    • 当前AOF文件大小要大于server.aof_rewrite_min_size(默认为1MB),或者在redis.conf配置了auto-aof-rewrite-min-size大小;

    • 当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比(在配置文件设置了auto-aof-rewrite-percentage参数,不设置默认为100%)

    • 如果前面三个条件都满足,并且当前AOF文件大小比最后一次AOF重写时的大小要大于指定的百分比,那么触发自动AOF重写。

  • 触发方式

    • 手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。
    • 自动触发:通过设置auto-aof - rewrite-min-size选项和auto- aof - rewrite- percentage选项来自动执行BGREWRITEAOF。
    • 只有当auto-aof- rewrite- -min-size和auto-aof -rewrite-percentage两个选项同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。

优劣

  • 优势:
    • 备份机制更稳健,丢失数据概率更低。
    • 可读的日志文本,通过操作AOF稳健,可以处理误操作。
  • 劣势
    • 比起RDB占用更多的磁盘空间。
    • 恢复备份速度要慢。
    • 存在个别Bug,造成恢复不能。

RDB和AOF比较和选择策略

9、主从复制

概念

主从复制:指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower),
数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)
Redis的主从复制是异步复制,异步分为两个方面,一个是master服务器在将数据同步到slave时是异步的,因此master服务器在这里仍然可以接收其他请求,一个是slave在接收同步数据也是异步的

在这里插入图片描述

主从复制作用:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  • 高可用基石:主从复制还是哨兵和集群能够实施的基础。

模拟主从复制:
这里我们开启三个redis,端口分别是6379、6380、6381
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

相关命令:

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

127.0.0.1:6379> info replication
# Replication
role:master	#当前角色:主机
connected_slaves:0	#从服务器个数
master_failover_state:no-failover
master_replid:fe0de1410ce3e019021dcdd5810d339154fa5817
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0


127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:1da470d0eda7fbce2e2e1bc224bcbfbf67c1dde1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0


127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:f6a329bac7c9d611484eb9dffc6cec7887e139a3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

这时候会发现,三个端口都是主机,那么怎么设置从机呢?(6379为主机,6380和6381为从机)
这个时候我们只需要在终端上输入以下命令就能将其变成从机。配从(库)不配主(库)

slaveof <ip><port> 成为某个实例的从服务器(ip和port是主机的ip和端口号)

在这里插入图片描述
在这里插入图片描述

这个时候我们在主机(6379)中写入数据,在两个从机中读取数据

127.0.0.1:6379> set a1 v1	#主机写入数据
OK
127.0.0.1:6379> keys *	
1) "a1"
127.0.0.1:6379> get a1
"v1"


127.0.0.1:6380> keys *	#从机6380读取数据
1) "a1"

127.0.0.1:6381> keys *	#从机6381读取数据
1) "a1"

但是当我们在从机中写入数据时:
在这里插入图片描述

我们这里是使用命令搭建,是暂时的,真实开发中应该在从机的配置文件中进行配置这样的话是永久的

在这里插入图片描述

可以把#去掉然后设置maserip和masterprot把配置写死。

复制原理

  • 当从服务器连接上主服务器后,从服务器会向主服务器发送进行数据同步的消息
  • 主服务器接到从服务器发送过来的同步消息,把主服务器数据进行持久化,生成rdb文件,把rdb文件发送给从服务器,从服务其拿到rdb进行读取。(这是从服务器主动请求)
  • 每次主服务器进行写操作之后,会和从服务器进行数据同步(这个是主服务器主动发起的)
  • 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步。
    但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

在这里插入图片描述

常用的三种“招”

  • “一主两仆”
    • 当主机宕机之后,默认情况下从机的角色不会发生变化,只是失去了写操作,当主机恢复以后,又 会连接上从机恢复原状。
      • 举个通俗的例子:一个家里面有一个主人(主机)和两个仆人(从机),主人有事出门(主机宕机),两个仆人依旧在家看家,还能做家务(从机不会发生改变,读功能正常),等主人回来。这个时候有客人登门拜访(写入数据),但是主人不在家,只能离去(主机宕机,失去写功能),当主人回到家之后(主机恢复),一切恢复正常。
    • 当从机(6380)宕机后,若不是使用配置文件配置的从机,那么当从机(6380)重启就会变成独立的主机了(可以使用info replication 会发现自己变成主机了),所以,再次启动后作为主机(6380)是无法获取之前主机(6379)的数据的,若此时重新配置这个重启后的主机(6380)为从机,又可以获取到主机(6379)的所有数据(从头开始复制主机中所有的数据)。

在这里插入图片描述

  • “薪火相传”

  • 上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。

    • 例子:皇帝去管理军队,军队人数太多,皇帝一个人管理起来很麻烦,就任命10个将军,让十个将军去管理,皇帝只需要管理将军就行了。 (类似二叉树)
  • slaveof <ip><port>中途变更转向:会清除之前的数据,重新建立拷贝最新的。风险是一旦某个slave宕机,后面的slave都没法备份。主机挂了,从机还是从机,无法写数据了。
    在这里插入图片描述

  • “夺权篡位”

  • 当一个主机宕机后,后面的从机可以立刻升为主机,其后面的从机不用做任何修改。

  • slaveof no one 将从机变为主机。

    • 例子:一个帮派的大哥挂了,这个时候就有小弟发表意见,不能没有头儿,于是就再选一个老大出来。

java代码实现主从复制:

private static JedisSentinelPool jedisSentinelPool=null;

public static  Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
            Set<String> sentinelSet=new HashSet<>();
            sentinelSet.add("192.168.11.103:26379");

            JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong

jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
        }else{
return jedisSentinelPool.getResource();
        }
}

10、哨兵模式

什么是哨兵模式?

“夺权篡位”的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
在这里插入图片描述

使用步骤

  • 在自定义的/myredis目录下新建sentinel.conf文件(名字不能错)
    文件中的内容:
#其中mymaster为监控对象起的服务器名称;主机ip地址;端口号; 1 为至少有多少个哨兵同意迁移的数量。
sentinel monitor mymaster 127.0.0.1 6379 1

启动哨兵

执行redis-sentinel /myredis/sentinel.conf

在这里插入图片描述

这个时候将主机(6379)挂掉:
在这里插入图片描述如图,这个时候6380变成了主机,而6379和6381变成了从机。并且就算6379重新启动,也无法变成之前的主机,依旧是从机。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那么当主机挂掉了,会根据什么条件来选择哪个从机变成主机呢?

首先判断优先级,在redis.conf中默认:replica-priority 100,值越小优先级越高。
例如:一个从机的是10 ,一个从机的是100,会优先选取10的做主机。

在这里插入图片描述

当优先级相同的时候,会比较偏移量。偏移量:是指获得原主机数据最全的
例如:一个从机有主机的10个数据,而另一个只有9个,会优先选取有10个数据的从机做主机。

当前面两个条件都无法做出判定的时候,会选择runid最小的从服务器(从机)。
每个redis实例启动后都会随机生成一个40位的runid。

在这里插入图片描述

11、集群

什么是集群?

Redis3.0加入了Redis的集群模式,实现了数据的分布式存储,对数据进行分片,将不同的数据存储在不同的master节点上面,从而解决了海量数据的存储问题。

Redis集群采用去中心化的思想,没有中心节点的说法,对于客户端来说,整个集群可以看成一个整体,可以连接任意一个节点进行操作,就像操作单一Redis实例一样,不需要任何代理中间件,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。

Redis也内置了高可用机制,支持N个master节点,每个master节点都可以挂载多个slave节点,当master节点挂掉时,集群会提升它的某个slave节点作为新的master节点。
在这里插入图片描述

集群的使用,运行机制及其优缺点

缓存穿透,缓存击穿,缓存雪崩

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值