本文使用的为Redis6.2.6版本
注:一到七章是基础,八到十二是进阶和面试会问的内容
数据库分为SQL数据库和NOSQL数据库,NOSQL的NO不是not(不)的意思而是not only的意思,也就是不只是SQL的数据库。
Redis就是一种NOSQL型数据库
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
单机数据库时代
90年代,一个基本的网站访问量较小,单个数据库可以顶住当时的访问量
在那个时候,都会去使用静态网页html,因为服务器压力不会太大
缺点:
如果说以上三个条件满足了至少一个,那么就需要做出改变了
DAL是数据访问层
Memcached(缓存)+ MySQL + 垂直拆分(实现读写分离)
网站在大部分情况下都是在读,当用户在界面中按下一个button,就会对数据库发送一个查询请求,如果说,当一个或者多个用户都在发送一个相同的请求,而这个请求每次都要查询数据库,这很耗费性能,这时候就需要减轻数据库的压力,可以使用缓存来保证效率。
发展:优化数据结构和索引----->文件缓存(IO操作)----->Memcached(当时的热门技术)
分库分表 + 水平拆分 + MySQL集群
技术与业务在发展的同时对人开始有越来越高的要求
本质都是在解决数据库的读和写问题
以MySQL的存储引擎为例
MyISAM:这个存储引擎支持表锁,并且不支持事务的ACID,影响操作,在高并发下会出现严重的锁问题
InnoDB:行锁+表锁,支持事务的ACID
慢慢的由于数据量的增大,慢慢的开始使用分库分表解决写压力!在当时MySQL还推出了一个 表分区 的概念,但是并没有多少的公司愿意去使用
MySQL集群的解决方案,已经在当时解决了大部分的需求
目前的现状
2010-2020,过了十年,世界发生了巨大变化,从按键手机到智能手机,定位,也成为了一种数据
MySQL等关系型数据库开始出现性能瓶颈!大数据,数据量很多,变化很快
MySQL可以用来存储一些比较大的文件,博客,图片!数据库表很大,执行IO操作的效率就会很低下,如果有一种数据库专门用来处理这种数据,就可以用来缓解MySQL的压力(如何处理这些问题)。因此诞生了NOSQL。
随着时代的发展,用户个人信息,社交网络,地理位置,用户自己产生的信息数据,等等一系列的弹性数据爆发式增长,关系型数据的传统的表结构已经承载不了了,这时候就需要使用NoSQL数据库来解决,NoSQL可以很好处理以上情况
NOSQL不是非SQL,而是not only sql,不仅仅是SQL,也就是我们常说的非关系型数据库
web2.0的诞生,传统的关系型数据库已经很难对付web2.0时代!特别是指大规模高并发社区!会出现很多问题,NoSQL在大数据时代发展的十分迅速,尤其是Redis
很多的数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式,不需要太多操作就可以实现横向拓展,就比如Redis,它是使用类似于Java的Map
方便扩展(数据之间没有联系可以快速拓展)
大数据量高性能,Redis可以支持8w的并发量(写操作),11w访问量(读操作),NoSQL的缓存记录级,是一种细粒度的缓存,性能比较高
数据类型多样性(不需要事先设计数据库,随取随用,数据量过大就无法设计)
传统DBMS
NOSQL
3V:
三高:
目前实践中用的最多的是NOSQL+传统SQL的组合来实现业务
目前淘宝等网站的各种数据的存储方式或技术:
商品的基本信息
如:名称、价格、商家信息
发展为使用MySQL ,摒弃Oracle也就是去IOE化(IOE:IBM、Oracle、EMC存储设备)
商品描述
如:评论,文本信息多
使用mongodb
图片
分布式文件系统 FastDFS
淘宝:TFS
Google:GFS
Hadoop:HDFS
阿里云:OSS
商品关键字搜索
搜索引擎一般用:solr或elasticsearch
淘宝:使用ISearch,ISearch作者是阿里的多隆
商品热门波段信息
Redis + Tair + Memcached
商品交易或支付接口
第三方应用接口
KV键值对
文档型数据库(使用了Bson,Binary Json,也就是二进制Json)
列存储数据库
图形关系数据库
PS:它不是存图片的!它存放的是关系,就好比一个人的社交圈,可以动态扩充
不同NOSQL的对比:
Redis(Remote Directory Server),中文译为远程字典服务,免费开源,由C语言编写,支持网络,可基于内存也可持久化的日志型,KV键值对数据库,并且提供多种语言的API,是当下NoSQL中最热门的技术之一!被人们称之为结构化数据库!
Redis支持目前常用的大部分语言,如:
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
注:Redis是单线程的,因为Redis主要是基于内存进行工作的,从单线程提升到多线程对于Redis的工作效率来说提升不大,但是6.0之后的版本也开始使用多线程了
为什么在Linux上而不是Windows上呢,因为官方建议在Linux上使用Redis,且Windows版本已经很久都没有更新了,非常不推荐使用。
Redis的默认端口号为6379
在Redis官网上下载最新版本的Redis
上传到linux服务器的/opt目录下
解压redis压缩包
进入解压文件中,可以看到redis.conf,这是redis的配置文件
安装c和c++环境
命令:yum -y install gcc
yum -y install gcc-c++
执行make命令,自动配置安装
执行make install命令,确认是否已经安装相关程序,相关程序默认安装在/usr/local/bin目录下
复制(cp命令)一份配置文件到任意位置,原配置文件留作备份,方便回复到原来的配置
将配置文件的后台运行项改为yes
指定我们修改后的配置文件打开redis服务
命令如:redis-server xiafanconfig/redis.conf,前面为redis服务,后面是我们指定目录下的配置文件
测试
使用redis客户端测试,具体如下:
如何退出redis
只需使用shutdown命令并exit退出(当然,也可以查看redis的进程ID使用kill命令直接杀死进程,但还是推荐使用shutdown)
使用redis-benchmark测试性能,测试时会根据设定的并发数和请求数为依据来对各个基本指令进行测试,返回测试结果
如,我想测试100个并发,每个并发有100000个请求,则命令为:redis-benchmark -h localhost -p 6379 -c 100 -n 100000
测试过程:
开启redis服务
命令:redis-server redis.conf
使用benchmark测试
命令:redis-benchmark -h localhost -p 6379 -c 100 -n 100000
从redis.conf这个配置文件中可以看出,redis默认拥有16个数据库database
可以用select+数据库编号
命令切换数据库(数据库编号是从0-15)
如:select 2(切换到第三个数据库)
用dbsize命令查看当前数据库存在数据的情况
清空Redis
官网原话的翻译:
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
String字符串
string类型的常见使用场景:
这里补充除了以上基本命令以外也很常用的命令:
那么如何设置对象呢,下面以简单的User用户对象为例,有两种设置方法
List列表
注意:列表是允许相同元素存在的,所以一个列表可能存在多个相同值
我们会发现以下这些命令很多前面都会带有L这个字母,这个L有时候指的是left有时候指的是list,我觉得很多时候两种理解都是可以的,不必太过纠结这个L到底是什么意思,并且很多命令其实是有重复功能的,可以根据自己的习惯来使用,或者查看完成同样任务的命令的效率如何,按性能来选择
这个数据类型还是很灵活的,根据需要可以当成栈、队列、各种变种队列来用
Set集合(无序集合)
注意:集合不允许相同元素存在的,所以一个集合中的某个元素的值是唯一存在的
Hash哈希
注意:hash数据类型很适合存储对象,因为它本身就是由键值对组成的
Zset有序集合
应用场景:由于这是有序的集合,所以可以应用于需要排序或者需要设置权重的场景,比如成绩表、工资表、设置消息权重、依据某个数据进行排序(比如xx的top n榜)等
geospatial
这是一个特殊的,和地理位置有关的数据类型,存储某地的经度纬度,可以应用于如:附近的人、外卖配送等领域
geospatial数据类型实际上是对Zset进行封装来使用的,所以Zset的操作在geo这里都可以使用,如zrem,zrange等
hyperloglog
hyperloglog是用来统计基数的,那么什么是基数,这里的基数是不重复元素的意思,也就是统计不重复元素的个数,这么听起来好像Set数据类型也可以完成这样的任务,但是hyperloglog有它自己的优点。
优点:hyperloglog最大的优点就是非常节省内存,如果项目所存的不重复元素个数非常多的话,那么hyperloglog肯定是首选,它所占用的内存是固定的,占用12KB
缺点:使用set和hashmap基本上可以保持不会出错,而hyperloglog可能会有略微的错误率,这个错误率在千分之一级别,如果允许这样的错误率使用它是最好的,如果要最大程度保持数据正确率则使用set等好一点
应用场景:比如统计网页的UV(单独访问数,同个用户多次访问算一次访问)
bitmaps
这里的bit是位的意思,这个数据结构是按位存储的。
应用场景:比如用户是否在线、用户是否活跃、是否已登录、是否打卡、健康码是否为绿、查看员工是否全勤等
bitmap就是位图,这个学过操作系统的朋友应该很熟悉,我们磁盘存储机制中也会使用位示图来表示磁盘块是否被占用
我们在MySQL中也已经学过事务transaction这个概念了,在MySQL中我们一直强调事务是有ACID四个特定的,但是到Redis这里就又不一样了。Redis只能保证单条命令是原子性的,但不能保证整个事务是原子性的,而且Redis也没有隔离的概念。Redis的事务具有一次性执行、顺序执行、排他性这三个特性。这里我们可以联想到我们所说的,Redis在6.0版本之前其实都是单线程的,这也就说明它的事务极有可能在底层是串行调度的,所以并不会被干扰。
Redis的事务操作非常简单,只有三个操作,开启事务multi、执行事务exec和取消事务discard
输入multi命令代表事务开启,然后输入一系列命令加入预执行列表,最后选择执行事务或取消事务,一般结构如下:
multi
命令1
命令2
命令3
exec|discard
当事务中的命令存在错误的时候,分为两种:
关于乐观锁和悲观锁,在MySQL中也好,在mybatis-plus也好,都提到过,这里再简单介绍一遍。
乐观锁就是“乐观”的锁,认为不会出现问题,不需要对数据进行上锁处理,更新的时候判断一下在这个过程中是否数据已经被修改过了。
在MySQL中我们是通过增加一个version字段来实现乐观锁的,在更新操作中自增version。
悲观锁就是“悲观”的锁,认为每一次都会出现问题,所以对数据的操作每一次都需要上锁。
一般来说,应用的比较多的是乐观锁,因为只需要多增加对一个字段的操作而已,而悲观锁需要频繁的上锁解锁会造成资源浪费,效率低下。
在Redis中,使用watch关键字实现乐观锁,起到监控(watch)的作用,如:watch num,可以监控num这个数据。而解锁则可以直接使用unwatch即可,常用于秒杀系统。
Jedis就是把Redis的操作封装成一个可以被Java操作的类,并且可以连接指定的Redis服务。
jedis封装的api和我们学过的Redis原生操作非常非常的相似,我们只需要导入jedis依赖就好,还要看过第三大章节可以迅速上手操作。这里主要补充说一下jedis如何操作事务:
这里用一段代码演示
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.1.107", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "xiafan");
jsonObject.put("age", "18");
jsonObject.put("sex", "man");
Transaction multi = jedis.multi(); // 开启事务
String user = jsonObject.toJSONString();
try {
multi.set("user1", user);
multi.set("user2", user);
multi.exec();
} catch (Exception e) {
multi.discard(); // 出现问题,放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.mget("user1", "user2"));
jedis.close(); // 关闭连接
}
}
}
springboot整合Redis有两个封装好的依赖,一个是我们已经提到的jedis,另一个是lettuce。
值得一提的是,springboot2.x的某些版本的redis依赖是只有lettuce而没有jedis的,我现在实操用的是2.6.2版本,会发现jedis又回来了,读者需要看一下自己的版本是两个都存在还是只有lettuce。
jedis:采用直连,模拟多个线程操作会出现安全问题。为避免此问题,需要使用Jedis Pool连接池!类似于BIO模式
lettuce:采用netty网络框架,对象可以在多个线程中被共享,完美避免线程安全问题,减少线程数据,类似于NIO模式
先配置Redis:
spring.redis.host=127.0.0.1
spring.redis.port=6379
接下来是springboot精髓的地方,就是任何整合的对象都会给它一个template,一个模板,任何通过模板对象来操作。
演示一下操作:
opsForxxx,这个xxx就是指定我们操作的数据结构,也就是我们学过的那八个数据结构,这里只用一个作为演示
@Test
void contextLoads() {
ValueOperations ops = redisTemplate.opsForValue();
redisTemplate.opsForGeo();
ops.set("k1", "v1");
Object o = ops.get("k1");
System.out.println(o);
}
以上是简单的使用演示。
springboot中的RedisAutoConfiguration下的redisTemplate方法可以自定义覆盖:
原RedisAutoConfiguration为:
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
推荐自定义redisTemplate方法为:
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
// 为了开发方便,可以直接使用
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 序列化配置
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
template.setDefaultSerializer(serializer);
template.setConnectionFactory(redisConnectionFactory);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
serializer.setObjectMapper(om);
StringRedisSerializer srs = new StringRedisSerializer();
// 对于String和Hash类型的Key,可以采用String的序列化方式
template.setKeySerializer(srs);
template.setHashKeySerializer(srs);
// String和Hash类型的value可以使用json的方式进行序列化
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
像我们操作JDBC、mybatis等技术一样,我们也可以自己定义一个redis的util工具类,来帮助我们使用redis,具体如何封装按照自己的操作习惯和公司业务来定义。
以下内容均是从配置文件中读出来的信息。
1.配置文件对大小写不敏感
2.Redis对单位的定义
3.Redis服务的开启需要附带配置文件
4.配置文件可以有多个
1.Redis绑定的ip地址,可以根据需要进行修改
2.是否开启保护模式,默认为yes
3.端口号,默认为6379
1.是否以守护进程方式运行,默认为no,守护进程就是在Linux系统中后台运行的意思
2.配置pid文件,pid文件一般都位于/var/run目录下
3.配置日志
4.日志文件的位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zSxgCt5e-1642663100518)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118104523639.png)]
5.默认的数据库数量,编号从0开始,连接时默认打开的为0号数据库
6.是否显示Redis的logo(没什么用…想开就开想关就关吧)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xqGX0Km-1642663100520)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118104842619.png)]
1.默认的有三个等级的持久化规则,我们也可以自己设定
第一个等级是如果3600秒内有1个key被改变就执行持久化
第二个等级是如果300秒内有100个key被改变就执行持久化
第三个等级是如果60秒内有10000个key被改变就执行持久化
2.持久化出错后是否保持工作,默认保持
3.是否压缩rdb文件,默认开启
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tacri20E-1642663100522)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118110038642.png)]
4.保存rdb文件时,是否进行错误校验,默认开启
5.rdb文件保存的目录,默认为当前目录
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uB01f8AB-1642663100523)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118110338824.png)]
6.rdb文件默认命名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TenUU4Zl-1642663100525)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118222516929.png)]
1.配置所属主机的ip地址和端口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jl3JzLi4-1642663100525)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220120151010634.png)]
2.配置主机密码(如果主机有密码的话)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyj3sgsw-1642663100526)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220120151033517.png)]
1.密码配置,Redis默认是无密码的,需要自己设置
可以在配置文件设置,但更常用的是在命令行设置
配置文件设置:
写在指向处,格式为:requirepass 密码
命令行设置:
config set requirepass 密码,设置密码
可以通过config get requirepass,获取当前密码
设置密码后,以后登录需要使用:auth 密码,才可以登录
如果要取消密码,则把密码置为空,命令:config set requirepass “”,即可
客户端配置
1.最大客户端连接数
默认最大客户端连接数为10000
2.最大内存容量
3.内存达到上限之后的处理策略
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6PHQkcIE-1642663100528)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118112321073.png)]
Redis拥有以下六种策略选择:
其实学过操作系统对如下策略都不会陌生
volatile-lru(默认值,最常用):从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越小越优先被淘汰。
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。
allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。
no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
1.是否开启aof模式,默认不开启
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ADKuWl6F-1642663100529)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118113122938.png)]
2.持久化文件名,默认为appendonly.aof
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tCr7zPHH-1642663100529)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220118113436839.png)]
3.设置同步sync频率,默认为每秒同步一次
always等级为每次操作都同步一次,很耗费资源
everysec等级为默认使用的,为每秒同步一次,我们最多丢失一秒的数据
no等级为不同步,由操作系统自行同步,资源利用率最高
Redis持久化在面试和工作中都是经常会涉及到的,这里也需要了解一下
由于Redis是内存数据库,如果不持久化,Redis的数据是存储在内存中的,如果服务器进程退出或机器断电,数据就会丢失,所以持久化是必须的
文件名默认为:dump.rdb,默认路径在redis服务开启的同级目录中,有意思的是rdb其实是redis database的缩写
rdb文件是以二进制格式存储数据的
rdb持久化的原理:redis的rdb持久化机制原理是redis会单独创建(folk)一个和主进程一模一样的子进程来进行持久化操作,这个子进程的所有数据局(变量,环境变量,程序计数器等)都跟原进程一样。当触发rdb操作时,redis会先将要持久化的数据写到一个临时文件中,等持久化结束之后再将这个临时文件替换上一次持久化好的文件,整个过程都由子进程完成,主进程不再进行任何io操作,从而保证了极高的性能。
我们知道,redis是一个单线程的高性能缓存key-value数据库。这里说的单线程实际上是指在接收网络请求时是单线程处理的,如下图三个客户端redis-cli同时发送命令过来时redis会将这多个同时过来的命令进行排队,然后按照排队顺序一个个往下执行,而将排队的命令取出来进行处理或持久化的过程中就不一定是单线程完成了,因此单线程的概念是对客户端网络请求处理时而言的,整个redis的工作过程并非完全是单线程完成的。
如下图所示:
这里先说明一下什么时候会触发rdb持久化的机制:
shutdown的时候(默认无开启aof时)会自动触发(bgsave)
redis.conf配置文件中自带的自动触发设置,(bgsave)也可以自己在配置文件设置,详细已在第八章说明
手动执行bgsave或save命令,会触发,这里讲一下bgsave和save的区别
可见,在线上维护中,如果需要手动持久化,使用bgsave更合适
除了以上三个触发机制之外,flushall命令也会触发rdb机制
我们再深入探讨rdb的机制,提出三个问题:
rdb持久化是什么时候开始fork子进程的
当持久化开始时即fork一个子进程,在持久化操作结束后,也会自动释放这个进程。口说无凭,如何验证?这里不进行实机操作,但是可以给予一个思路,可以通过jedis一次性放入百万级别的数据,然后在redis客户端执行bgsave,之后查看Redis相关进程的情况,就可以发现会出现一个redis-rdb-bgsave的进程,这就是我们fork出来的子进程了
rdb究竟是如何持久化的
rdb持久化不是直接覆盖原来的持久化文件,我想这是因为如果持久化失败会影响到原来的数据,所以rdb持久化是先产生一个临时文件来保存要持久化的内容的,然后再将原来的持久化文件替换成临时文件。如何验证?当持久化的时候,我们可以查看dump.rdb这一级目录的文件,会发现多了一个temp开头的rdb后缀文件,这就是它产生的临时文件
主进程在持久化的时候不参与IO操作,如何验证
我们手动save,而不是bgsave,此时主进程会被阻塞,其他客户端的IO请求都会被阻塞,直到持久化完成,所以在主进程使用rdb持久化是非常非常不明智的,会直接导致其他客户端无法对Redis数据库进程操作,这也就是我们主从复制中,rdb用于从机而不用于主机的原因
文件名默认为:appendonly.aof,默认路径在redis服务开启的同级目录中,aof是append only file的缩写
需要开启aof持久化时,只需要把redis.conf配置文件中的appendonly参数改为yes即可
我们在数据库理论基础中就学过了数据库的日志系统,简单来说就是,使用日志记录我们的操作,当需要持久化的时候就执行日志中的操作即可,日志不止可以用来持久化,也可以用来恢复数据。
aof持久化的原理:在aof机制中redis操作日志以追加的方式写入文件,其中读操作不做记录。
为什么需要aof:因为rdb方式在快照配置中假设默认60s查看触发一次rdb,但是在这60s内假设突然发生了宕机,那么后果就是在这60s内产生的redis数据都没有被持久化下来,从而造成数据的丢失,为了弥补这种意外情况发生导致数据丢失的问题,因此就出现了aof,而aof由于每秒操作因此极端情况下丢失的数据极少。
触发条件:
在配置文件中默认是每秒触发一次
文件内容大体如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sWdXCFxw-1642663100530)(C:\Users\利姆鲁\AppData\Roaming\Typora\typora-user-images\image-20220119231354430.png)]
我们解读一下这个文件:
*n代表下一个命令的操作数有多少个,比如select 0有两个,set k1 v1有三个,以此类推
$n代表下一个操作数的长度是多少,比如sleect是6,set是3,以此类推
select 0是aof文件自带的,因为默认进入Redis的数据库就是0号数据库
aof的重写机制:
什么时候会触发重写机制
由于aof的原理决定了aof文件随着Redis的使用必然会越来越大(因为记录的命令越来越多),当触发了配置文件中aof文件的大小阈值(默认是64M)时,就会启动重写机制
重写机制的原理
其实就是用占用内存更短的命令来替代原来的命令,减少空间的浪费,通常重写后aof文件会较原来小很多。
aof重写并不需要对原有AOF文件进行任何的读取,写入,分析等操作,这个功能是通过读取服务器当前的数据库状态来实现的。
比如:
lpush list “A”
lpop list “A”
lpush list “B”
lpop list “B”
lpush list “C”
lpop list “C”
lpush list “D” “E”
lpush list “F”
本来需要存储这8条完整的命令,但重写之后则会压缩成这1条命令:lpush list “D” “E” “F”
那么这里有一个问题,如果写入命令带有的参数太多怎么办,此时可能会导致客户端输入缓冲区的溢出,所以Redis设定了一个阈值,redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD,这个值一般是64
重写流程图:
aof文件可能会损坏,我们可以使用redis-check-aof文件进行修复
Redis中aof的优先级高于rdb
具体流程如下:
那么会有一个问题:如果我原来的数据存在rdb文件,但是我现在需要换成aof持久化,原来的文件怎么样才能不丢失?
解决:先不开启aof机制,直接连接Redis数据库,使得rdb文件被读入到Redis内存中,然后使用命令config set appendonly yes动态开启aof机制,这样生成的aof文件就会把当前内存中的数据存储进去了,接下来关闭Redis客户端,然后到配置文件中查看aof是否开启了,如果没有开启再手动修改成yes即可(目的是把内存作为一个中介,将rdb文件中的数据存储到aof文件中去)
rdb的优点:
rdb的缺点:
aof的优点:
它支持 同步记录 和 异步记录,可在配置文件中操作,如下:
appendfsync always # 同步记录,客户端中一有写操作,即刻记录,数据的完整性好,但是性能较差
appendfsync everysec # 异步记录,每秒记录一次,但是服务器如果在这一秒之内宕机,这一秒的数据就会丢失
appendfsync no # 不记录
aof的缺点:
比较:
rdb:数据丢失多,性能快,适合大数据导入
aof:数据丢失少,性能慢,不适合大数据导入
如果rdb和aof都设置为每秒执行一次持久化,二者有什么区别?
Redis的发布订阅(publish/subscribe)是一种消息通信模式,发送者(publish)发送消息,订阅者(subscribe)接收消息
Redis客户端可以订阅任意数量的频道
如图,有三个客户端订阅了一个Channel1
当Channel1的后台发送了一个数据到Channel1的频道中,这三个订阅了Channel1的客户端就会同时收到这个数据
命令:
这些都是用来实现数据通信的命令,现实中的场景可以是网络聊天室,广播等
演示:
订阅者:
127.0.0.1:6379> SUBSCRIBE xiafan # 订阅一个频道叫xiafan
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "xiafan"
3) (integer) 1
# 一旦开始订阅,会立即占用当前进程去监听自己所订阅的那个Channel
1) "message" #消息
2) "xiafan" #频道
3) "Hello!I love Java!!" #消息具体内容
1) "message"
2) "xiafan"
3) "Hello!I love Redis!!"
发布者:
127.0.0.1:6379> PUBLISH xiafan "Hello!I love Java!!" # 往频道xiafan中发布一条消息
(integer) 1
127.0.0.1:6379> PUBLISH xiafan "Hello!I love Redis!!"
(integer) 1
原理:
Redis是C语言编写,在实现消息的发布和订阅的时候,Redis将其封装在一个后缀名为点c的文件中,pubsub.c
通过subscribe和publish命令实现消息的发布和订阅,当用户订阅了一个频道之后,redis-server里面维护了一个字典,字典里有很多个Channel(频道),字典的值就是一个链表,链表中保存的是订阅了这个频道的用户,使用publish命令往频道发送数据之后,redis-server使用此频道作为key,去遍历这个指定的value链表,将信息依次发送过去。
类似下图:
使用场景(纯Redis使用的场景一般较为简单):
对于复杂场景:可以使用消息中间件来做,如:RabbitMQ,RocketMQ,kafka等中间件技术
Redis的主从复制,实际上就是将一台Redis服务器的数据,复制到其他Redis的服务器。前者被称为服务器的主节点(master/leader),后者就是从节点(slave/follower),数据的复制只能由主节点到从节点,Master以写为主,slave以读为主
实际上每台服务器都是一个主节点,一个主节点可以有零个或多个从节点,并且每一个从节点只能有一个主节点
1、主从复制可以实现数据备份,这是除了持久化的另一种实现数据保存的方式
2、实现故障快速修复,因为要实现主从复制必然需要多台服务器,一旦主节点挂掉,从节点可以代替主节点提供服务
3、主从复制配合读写分离,可以实现分担服务器负载,如果在生产环境中读的操作远大于写的操作时,可以通过多个从节点进行分担负载,以提高Redis的并发量
4、主从复制也是Redis集群搭建和哨兵机制的基础
将Redis运用于项目中,是不会只使用一台服务器进行搭建的,因为:
如果是单个Redis,那么单个服务器将会独自完成来自客户端的读写操作,负载较大,而且只有一台Redis服务器,太容易挂掉了,一不小心宕掉了就很麻烦
单个服务器,内存容量是有上限的,不管这台Redis服务器的内存再怎么大,也不可能完全用来存储内存,并且单台Redis服务器的内存占用一般不会超过20个G
下图为主从复制例子:
主从复制,读写分离,在大部分情况下,很多人浏览网页更多的则是读操作,这是架构中常常会用到的一种模式,用来缓解读压力
因为Redis默认启动时本身就是一个master,所以在进行主从复制的搭建时,只需要配置从库即可,查询当前库的状态信息,可以使用下面这个命令
需要几台从机就增加几个配置文件,我们以一主二从为例:
那么一共需要三个配置文件,配置文件需要修改自己特定的部分为:
1、搭建主从复制,不同的服务器就需要不同的端口号
2、pidfile的文件名称
3、logfile 日志文件名称
4、Redis默认RDB持久化,所以dump.rdb的名称也是需要修改的
修改完毕后即可启动Redis服务
使用info replication查看当前Redis服务的状态
配置从机
主机是不需要配置的,因为Redis服务打开时默认是主机,只需要配置从机。
使用slaveof命令配置从机即可:slaveof ip地址 端口
如:slaveof 127.0.0.1 6379
说明:
# Replication
role:slave # 当前角色 从机
master_host:127.0.0.1 # 主机信息 127.0.0.1
master_port:6379 # 主机端口号 6379
master_link_status:up # 主机状态 在线
查看主机情况
可以看到此时多了两个从机
这里是使用命令行方式进行演示,使用配置文件配置则是永久性配置,详细看第八章
从机会自动保存主机中的所有信息和数据,主机负责写入,从机负责读出
当主机断开的时候,从机原来保有的数据依旧存在,依然可以负责读操作
如果是命令行方式配置的临时性从机,如果从机断开再连接时是不会自动把原来的主机当主机的,而是默认把自己当主机,而命令行配置则不会,所以工作环境中还是推荐使用命令行配置
复制原理
如果一个Slave指定了一个master做老大,那么Master节点就会发送一个叫做sync的同步命令,在使用了SLAVEOF命令之后,Master接收到了,就会启动后台的存盘进程,开始收集修改数据集的命令,在存盘进程执行完毕之后,会将数据文件发送给Slave,实现一次完全同步
说到同步,这个有两个概念需要多聊一嘴
1、全量复制,Slave文件在接到Master发来的数据文件之后,会将其存盘并加载到内存
2、增量复制,Master和Slave已经实现同步的情况下,如果Master继续修改数据集,会将命令再次收集起来,再发送给Slave
只要是重新连接Master的服务器,在连接之后都会自动执行全量复制!
只有一个主机,从机只有一个等级,都直接连接主机,我们上面所说的主从就是这种,如图:
只有一个主机,但从机有多级,呈链状,如图:
这种主从复制的特性,在实际应用场景中可以有多个节点,这样的节点具有Master和Slave的双重身份(但是实际上它还是一个Slave节点,只不过它是一个可以进行写操作的Slave节点),可以分担Redis的写压力,并且在真正的Master节点挂掉之后,这些具有双重身份的就可以顶上去工作,有效解决了一主二从的不可写问题
手动更换主机
如果主机挂了,需要有一个从机来接替主机的作用,也就是变成一个新的主机,我们在从机上使用这个命令即可:
slaveof no one
原理:
由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
Redis提供了哨兵的命令,并且它是一个特殊的模式,它会创建出一个完全独立于Redis服务器的进程,一个哨兵可以对多台服务器进行监控,并且可以有多个哨兵,每个哨兵会定时发送PING命令给服务器,并且还要在一定时间内得到服务器的响应,得到响应之后哨兵模式才会判定你现在状态正常。如果在规定的时间内它发送的请求主机没有得到响应,那么哨兵便会初步判断,当前主机是主观下线,其余的哨兵发现这台主机没有在规定时间内响应数据,那么便会以每秒一次的频率对主机进行判断,它确实是主观下线了,那么主机就会被标记为客观下线,主机挂掉之后,哨兵便会通过投票的方式在挂掉的主机下的从机中选出一个作为新主机。
优点:
1、哨兵集群是基于主从复制来实现的,主从复制的优点全部具备
2、主从可以切换,故障可以转移,提升系统可用性
3、哨兵模式就是主从模式的升级,谋权篡位的手动到自动,更加健壮
缺点:
1、在线扩容比较麻烦,集群的数量达到上限,就会变得十分繁琐
2、实现哨兵模式的配置较为麻烦,如果出现故障,还会涉及到一些shell脚本的运行,这些都是非常麻烦的操作
哨兵配置文件sentinel.conf的内容说明和如何编写(根据自己的需求来定):
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长,
但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
一个是事件的类型,
一个是事件的描述。
如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
#
# 目前总是“failover”,
# 是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
Redis的缓存穿透、击穿和雪崩一直都是面试高频的问题,了解它非常有必要
在大多数场景中,数据库里的id字段一般来说都是自增。如果说,用户发送了一个请求,会首先进入缓存,查不到,就进入数据库,进了数据库,也查不到,查不到的话,如果说访问量较少,那还好,直接返回不存在嘛,因为这种少量的缓存穿透实际上是不可避免的,但是,一旦有一些不怀好意的坏蛋,在发送请求查询数据库的时候,主键的id字段故意给你来一个负数或者是一些远大于数据库最大id的数,而且进行巨大量的并发访问,这时候缓存中肯定是没有的,那这些请求就直接压给数据库了,数据库扛不住这么大的东西呀,那咋办,不解决数据库就只能挂掉呀。
三种解决方法:
1、在进行项目的整合时需要使用到API接口层,在接口层中定义自己的规则,对于不合法的参数可以直接返回,对于调用此接口的API的对象进行严查,任何可能发生的情况都要考虑到
2、在缓存中设置一个空对象,使用空对象完成后续请求,如图:
3、使用一个工具,叫做布隆过滤器(Bloom Filter),布隆过滤器使用的是一个bit数组和一个hash算法实现的数据结构,并且内存的使用率极低。使用布隆过滤器可以快速判断当前key是否存在,和Java的Optional类有点相似,布隆过滤器告诉你这个key不存在,那么它就一定不存在
缓存击穿,它是缓存穿透的一种特殊情况,一般情况下没有公司会去实现这样的业务,因为没有这样一条非常非常高频的热点数据能够搞垮一台服务器,可能性是非常小的
举个栗子,如果有一个非常高频的热点key,在某一个时刻过期,与此同时又有非常非常多的请求并发访问这个key,因为缓存时间已过,现在全部的请求又开始全部压在数据库上面了,很容易导致服务器挂掉
解决方法:
1、设置为当前key永不过期,但是不推荐这种做法,因为这样会长期占用Redis的内存空间
2、用Redis的分布式锁,如果说当前有非常非常多的请求传进来,这个时候有分布式锁的保护,可以允许这些请求中的其中一个放进来,缓存找不到就直接查数据库嘛,查完了再把数据放到缓存中让其他的请求直接查缓存即可,如图:
缓存雪崩指的是,在同一个时间内,一大批的缓存,集体失效,就好像没有被缓存过一样,这个时候假设说有双十一双十二的这种秒杀活动,恰好在这个时刻缓存是成批成批的失效,那么缓存失效,这些请求也还是直接压上数据库的,如果服务器在没有加锁的情况下,这种流量几乎是瞬间达到一个峰值的,对于一个服务器来说,也是会出现宕机的情况
解决方法:
1、搭建Redis集群,在高可用的情况下,配置多台服务器,在服务器中保存同样的数据,这样即使是集群中的一台甚至是多台挂掉的情况下,也依旧能够正常工作
2、如果不搭建集群,也可以这么做:项目启动时,将数据库和Redis进行数据同步,将数据库中部分的数据信息首先加载进入Redis缓存,并且在加载进入缓存时,可以将这些缓存数据的存活时间设置为随机,并且在数据库和Redis缓存需要在一定的时间之内进行同步更新