NoSQL (Not Only SQL),不仅仅是 SQL ,泛指 非关系型的数据库
RDBMS :关系型数据库NoSQL :非关系型数据库Redis 是基于内存数据存储,被用作为 数据库、缓存、消息中间件
> Redis 是一个内存型的数据库。 为什么数据存储在内存(读写快、断电立即消失)中,还会作为数据库? 因为 Redis 有持久化的机制,内存中的数据会定期写入到磁盘中。在同一台机器上操作 redis
不能退出上面的启动界面 另外一个窗口,执行Redis 官方提供了两种不同的持久化方法将数据存储到硬盘中:
快照(Snapshot)AOF(Append Only File)只追加日志文件这种方式可以将某一时刻的所有的数据都写入硬盘中,这种方式也是 redis 的默认开启的持久化方式,保存的文件是以 .rdb形式结尾的文件,因此这种方式也称为 RDB 方式。
快照生成的方式:
客户端方式:BGSAVE 和 SAVE 指令,shutdown指令服务器配置自动触发 # 客户端方式之 BGSAVE: 客户端可以使用 BGSAVE 的命令来创建一个快照。当服务器接收到客户端的 BGSAVE 的命令时,redis 会调用 fork 来创建一个子进程,然后子进程负责将快照写到磁盘中,而父进程则继续处理命令的请求。 名词解释:【fork】当一个进程创建子进程的时候,底层操作系统会创建该进程的一个副本,在 类UNIX 系统中创建子线程的操作会进行优化:刚开始的时候,父子进程共享相同的内存,直到父进程或子进程对内存进行写操作之后,对被写入的内存的共享才会结束。 # 客户端方式之 SAVE: 客户端还可以使用 SAVE 命令来创建一个快照,接收到 SAVE 命令的 redis 服务器在快照创建完毕之前将不再响应任何其他的命令。 # 注意:SAVE 命令并不常用,使用 SAVE 命令在快照创建完毕之前,redis处于阻塞状态,无法对外服务。 # 服务器配置方式之满足配置自动触发: 如果用户在 redis.conf 中设置了 save 配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任何一个save配置选项条件满足,redis也会触发一次BGSAVE命令。 # 服务器接收到客户端 shutdown 指令 当redis通过shutdown指令接收到关闭服务器的请求时,会执行一个save指令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器。配置生成快照的名称和位置
修改生成快照的名称 - dbfilename dump.rdb修改生成位置 - dir ./这种方式可以将所有的客户端执行的写命令记录到日志文件 中,AOF 持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要 redis 从头到尾执行一次 AOF 文件所包含的所有写命令,就可以恢复 AOF 文件的记录的数据集。
开启 AOF 持久化
在 redis 的默认设置中 AOF 持久化机制是没有开启的,需要在配置中开启 # 开启 AOF 持久化 1. 修改 appendonly yes 开启持久化 2. 修改 appendfilename "appendonly.aof" 指定生成文件名称日志追加的频率设置
1. always 【谨慎使用】 每个 redis 写命令都要同步到硬盘,严重降低 redis 速度。 这种同步机制需要对硬盘进行大量的写入操作,所以 redis 处理命令的速度会受到硬盘性能的限制 不断写入少量的数据的做法可能会引发严重的写入放大问题,会大大降低硬盘的寿命。 2. everysec 【推荐】 每秒执行一次同步显式的将多个写命令同步到磁盘 redis 可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据 3. no 【不推荐】 由操作系统决定何时同步 这个选项不会对 redis 性能带来影响但是系统崩溃时,会丢失不定量的数据 另外,如果用户的硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘的数据填满时,redis 会处于 阻塞状态,并导致redis的处理命令请求的速度变慢。AOF 带来的问题
> AOF 的方式也同时带来了另外一个问题,持久化的文件会变得越来越大。例如我们调用 incr num 命令1000 次,文件中必须保存全部的 1000 条命令,在实际恢复时,999条都是没有必要恢复的,其实我们的日志文件只需要保存一条 set num 1000 就可以了。 为了压缩 AOF 持久化文件,Redis 提供了 AOF重写(ReWrite)机制。 AOF 重写 在一定程度上减小了AOF文件的体积触发 AOF 重写方式
1. 客户端方式触发重写 执行 BGREWRITEAOF 命令【不会阻塞redis服务】 2. 服务器配置方式自动触发 配置 redis.conf 中的auto-aof-rewrite-percentage 选项 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb 代表:当 AOF 文件体积大于64M时,并且AOF的体积比上一次重写之后的体积大了至少一倍(100%)时,会自动触发重写原理
> 注意:重写 aof 文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换了原有文件。【类似于快照】 # 重写的流程: 1. redis 调用fork,有父子两进程,子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令 2. 父进程继续处理client的请求,一边会将命令写入原来的aof文件,同时,一边把收到的写命令缓存起来,只样做能够保证如果子进程重写失败的时候,数据不会丢失。 3. 当子进程把快照内容写到临时文件后,子进程会发出信号通知父进程,然后父进程会把缓存的写命令也写入到临时文件。 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始会往新的aof文件追加。SpringBoot Data Redis 中提供了 RedisTemplate和 StringRedisTemplate .其中 StringRedisTemplate 是 RedisTemplate 的子类,两个方法基本一致,不同之处主要体现在:操作的数据类型不同,RedisTemplate
中的两个泛型都是 Object,意味着存储的 key 和 value 都可以是一个对象,而 StringRedisTemplate 的两个
泛型都是 String ,意味着存储 key 和 value 只能是字符串。
注意:使用 RedisTemplate 默认是将对象序列化到 Redis中,所以放入的对象必须实现对象序列化接口
引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 配置 application.properties spring.redis.host=121.89.207.234 spring.redis.port=6379 spring.redis.database=0 spring.redis.password=root 测试 //用于测试的坐标 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>因为用RedisTemplate,key和value都是Object类型,所以在存储到Redis中时,会先将key和value序列化,再存储到Redis中,我们可以在Redis中查看上面存储的key在Redis中的显示
并且在Redis查询时,也无法查询
get “\xac\xed\x00\x05t\x00\x04user”
此种方法存在局限性:在客户端我们是无法查询(使用)这个key所对应的value了。
为了解决这个问题,我们必须将 RedisTemplate中的key 的JDK序列化方式改为String序列化的方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
public void TestString(){ //修改 key 的序列化方案为 String序列化 redisTemplate.setKeySerializer(new StringRedisSerializer()); User user = new User(); user.setName("zs"); user.setId(1); redisTemplate.opsForValue().set("user", user); User user1 = (User) redisTemplate.opsForValue().get("user"); System.out.println(user1); }此时,Redis中的key就是user
并且可以通过key来查询
hash类型的key的序列化:redisTemplate.setHashKeySerializer(new StringRedisSerializer());利用redis中的字符串类型完成项目中的手机验证码存储的实现。
利用redis中的字符串类型完成项目中的订单】失效性的业务。
利用redis 实现分布式集群系统中Session共享
利用redis中的ZSet类型,排行榜之类的功能
利用redis 实现分布式缓存
利用redis 存储认证之后token 信息
利用redis 解决分布式集群系统中分布式锁的问题
…
什么是缓存?
就是计算机内存中的一段数据内存中数据有什么特点?
读写快断电立即消失缓存解决了什么问题?
提高了网站的吞吐量、提高了网站的运行效率核心解决的问题: 缓存的存在是用来减轻数据库的压力什么地方需要使用缓存?
数据库中极少发生修改,更多用于查询的数据需要使用缓存。本地缓存和分布式缓存 的区别?
1. 本地缓存:存在于应用服务器内存中的数据称之为本地缓存 例如:MyBatis中的缓存,是存在于Tomcat的内存中的,当Tomcat关闭,缓存的数据也就消失了【占用JVM内存】 2. 分布式缓存:存储在当前应用服务器之外的内存中数据称之为分布式缓存 例如:在 redis 中的数据,当Tomcat关闭时,缓存的数据不会消失,而是存在于redis中。集群 和 分布式
1. 集群:将同一种服务的多个节点放在一起共同对系统提供服务 例如:Tomcat集群、MySQL集群、Redis集群... 2. 分布式:有多个不同的服务集群共同对系统提供服务这个系统称之为分布式系统 例如:Tomcat集群、MySQL集群、Redis集群共同为一个系统提供服务利用 MyBatis 的本地缓存来看 Redis 如何实现分布式缓存
1. MyBatis中的应用级缓存(二级缓存) SqlSessionFactory级别的缓存是所有会话共享的。 2. 开启二级缓存 在 XxxMapper.xml 中加入 <cache/> <mapper namespace="cf.duanzifan.dao.UserDao"> <!--开启二级缓存--> <cache/> <select id="findAll" resultType="User"> select * from t_user; </select> </mapper> 使用缓存,实体类必须实现序列化 public class User implements Serializable { 第二次查询时:本地缓存的缺点:
1.本地缓存关闭应用服务器就会断电消失,并且占用JVM的内存
2.在启用Tomcat集群时,Nginx会进行负载均衡,此时如果还是用的本地缓存,在负载均衡时会重复建立缓存
此时我们需要分布式缓存解决问题
我们先对本地缓存的 < cache/ > 进行分析:
MyBatis中的 Cache 是由 PerpetualCache 实现的
我们可以看一下 PerpetualCache 的源码
public class PerpetualCache implements Cache { private final String id; //此处可以看出底层实现缓存是:HashMap private Map<Object, Object> cache = new HashMap(); public PerpetualCache(String id) { this.id = id; } public String getId() { return this.id; } public int getSize() { return this.cache.size(); } //我们将这里两个方法修改为 Redis 的实现 public void putObject(Object key, Object value) { this.cache.put(key, value); } //我们将这里两个方法修改为 Redis 的实现 public Object getObject(Object key) { return this.cache.get(key); } public Object removeObject(Object key) { return this.cache.remove(key); } public void clear() { this.cache.clear(); } public boolean equals(Object o) { if (this.getId() == null) { throw new CacheException("Cache instances require an ID."); } else if (this == o) { return true; } else if (!(o instanceof Cache)) { return false; } else { Cache otherCache = (Cache)o; return this.getId().equals(otherCache.getId()); } } public int hashCode() { if (this.getId() == null) { throw new CacheException("Cache instances require an ID."); } else { return this.getId().hashCode(); } } }我们可以看出
<cache/> ========》 <cache type="org.apache.ibatis.cache.impl.PerpetualCache"/> 结论 # MyBatis 的缓存底层默认使用的是 org.apache.ibatis.cache.impl.PerpetualCache 来实现的 # 我们可以自定义 RedisCache 来实现 Redis 的分布式缓存 1.我们可以自定Cache类来实现Cache接口,对接口中的方法进行实现 2.我们以后在使用分布式缓存时就可以 <mapper namespace="cf.duanzifan.dao.UserDao"> <!--开启Redis缓存--> <cache type:"xxx.RedisCache"/> <select id="findAll" resultType="User"> select * from t_user; </select> </mapper>在设计分布式缓存时我们应该采用什么样的Redis类型
由此我们可以看出采用RedisHash类型
//用来获取springboot创建好的工厂,保证在 RedisCache 缓存类中可以使用 RedisTemplate @Component public class ApplicationContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; //将创建好的工厂以参数的形式传递给这个类 @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { //将参数中的工厂赋给定义好的工厂 this.applicationContext = applicationContext; } //提供一个静态方法通过工厂获取对象 //RedisTemplate redisTemplate public static Object getBean(String beanName){ return applicationContext.getBean(beanName); } } public class RedisCache implements Cache { //当前放入缓存的mapper的namespace private final String id; //自定义Cache实现类,必须存在一个带有id参数的构造方法 public RedisCache(String id) { this.id = id; } //此处必须返回Cache的唯一标识 @Override public String getId() { return this.id; } //封装redisTemplate private RedisTemplate getRedisTemplate(){ //通过application工具类获取 RedisTemplate RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate"); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); return redisTemplate; } //缓存中放值 用 RedisTemplate @Override public void putObject(Object key, Object value) { //使用redishash类型作为缓存存储模型 key hashkey value getRedisTemplate().opsForHash().put(id.toString(), key.toString(), value); } //缓存中取值 @Override public Object getObject(Object key) { return getRedisTemplate().opsForHash().get(id.toString(), key.toString()); } //根据指定的key删除缓存(保留方法,默认无实现) @Override public Object removeObject(Object key) { return null; } //清空缓存(增删改的时候,默认走的就是清空缓存) @Override public void clear() { //清空namespace getRedisTemplate().delete(id.toString());//清空缓存 } //用来计算缓存数量 @Override public int getSize() { //获取hash中的key、value数量 return getRedisTemplate().opsForHash().size(id.toString()).intValue(); } @Override public ReadWriteLock getReadWriteLock() { return null; } } <mapper namespace="cf.duanzifan.dao.UserDao"> <!--开启Redis分布式缓存--> <cache type="cf.duanzifan.cache.RedisCache"/> <select id="findAll" resultType="User"> select * from t_user; </select> </mapper>上面的设计有什么缺陷吗?
显然此种方法在单表查询时,没有任何问题但是,如果存在联合查询,则上面存在一定的问题: 例如我们现在有一张用户表和一张员工表,当用户表更新时,员工表的缓存不会清空,会存着之前旧的值,再次查询时必然会出现问题我们如何来解决:当两张表有关联查询时,我们要保证两张表的缓存存在Redis的同一个HsahKey中<cache-ref namespace="cf.duanzifan.dao.UserDao"/>单表查询时
多表联合时
我们如何更进一步的去优化此种分布式缓存?
# 我们发现在放入hash中的key,默认生成的比较长,这样能加消耗内存 1. key:-35986374:1262414444:cf.duanzifan.dao.UserDao.findAll:0:2147483647:select * from t_user;:SqlSessionFactoryBean # 所以,我们有了缓存的优化策略 1. 对放入 redis 的 key 进行优化,key的长度不能太长 我们可以采用 MD5 算法【加密算法】对 key 进行处理 MD5算法的特点: 1. 一切文件字符串等经过md5处理之后,都会生成32为16进制的字符串 2. 不同内容经过md5进行加密,加密结果一定不一致 3. 相同内容的文件多次经过md5生成的结果始终一致 所以,我们可以在redis整合mybatis的过程中,可以将key进行md5优化处理我们在 ResdisCache 中加一个方法
//封装一个对key进行md5处理的方法 private String getKeyToMD5(String key){ return DigestUtils.md5DigestAsHex(key.getBytes()); } //在放值和取值的方法中将key进行md5处理 @Override public void putObject(Object key, Object value) { //使用redishash类型作为缓存存储模型 key hashkey value getRedisTemplate().opsForHash().put(id.toString(), getKeyToMD5(key.toString()), value); } @Override public Object getObject(Object key) { return getRedisTemplate().opsForHash().get(id.toString(), getKeyToMD5(key.toString())); }此时key值为
79773edac7a7a41d58076c76af068c9a
什么是缓存穿透?
客户端查询了一个数据库中没有的数据导致缓存无法利用
注意:mybatis中的cache解决了缓存穿透:将数据库中没有的数据的查询结果也进行缓存
什么是缓存雪崩?
在系统运行的某一时刻,突然系统中缓存全部失效,恰好在这一时刻涌来大量的用户请求,导致所有的模块缓存无法利用,导致数据库阻塞或挂起(一般实战中缓存是有时效性的,如果缓存在同一时间设置了相同的时效,就会发生雪崩)
解决方案:
1.缓存永久存储 不推荐2.不同业务的数据设置不同的超时时间 主从复制架构是用来解决数据的冗余备份,从节点仅仅是用来同步数据的。
无法解决的问题:
自动故障转移
单节点并发压力问题
单节点内存和磁盘物理上限
主从复制架构图
搭建主从复制
# 1.准备三台机器并修改配置 - master port 6379 bind 0.0.0.0 - slave1 port 6380 bind 0.0.0.0 slaveof xxx.xxx.xx.x(asterip) 6379(masterport) - slave1 port 6381 bind 0.0.0.0 slaveof xxx.xxx.xx.x(asterip) 6379(masterport)Sentinel(哨兵) 是 Redis 的高可用解决方案:由一个或者多个 Sentinel 实例组成的 Sentinel 系统,可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线的主服务器的某个从服务器升级为新的主服务器,当原先的主服务器恢复时,会成为新的主服务器的从节点
简单说:哨兵就是带有自动故障转移功能的主从架构
无法解决的问题:
单节点并发压力问题单节点内存和磁盘物理上限搭建哨兵架构
集群搭建
进行 MSM 和 RSM 的比较:
是应用服务器管理 Session,在服务器上的所有应用都会session共享
当用到 session 的时候会在 Memcache 中复制到应用服务器中使用
是具体的应用管理 Session,加上session共享的应用才会session共享
当用到 session 的时候会在 Redis 中直接获取session
# redis的session管理是利用spring提供的session管理解决方案,将一个应用session交给Redis存储,整个应用中所有的session的请求都会去redis中获取对应的session数据。开发 session 管理
引入依赖 <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> 开发 session 管理配置类 @Configuration @EnableRedisHttpSession public class RedisSessionManager { } 打包测试即可127052163)]
[外链图片转存中…(img-hWj0PXQ8-1599127052166)]
[外链图片转存中…(img-37NMTkLK-1599127052168)]
[外链图片转存中…(img-uD5lSycK-1599127052170)]
[外链图片转存中…(img-TTnYb7g3-1599127052172)]
[外链图片转存中…(img-TbS8is7e-1599127052174)]
[外链图片转存中…(img-CaR12vaN-1599127052177)]
[外链图片转存中…(img-31jRAZ63-1599127052180)]
进行 MSM 和 RSM 的比较:
[外链图片转存中…(img-UKdlc4o8-1599127052182)]
是应用服务器管理 Session,在服务器上的所有应用都会session共享
当用到 session 的时候会在 Memcache 中复制到应用服务器中使用
[外链图片转存中…(img-KfOhXsWE-1599127052185)]
是具体的应用管理 Session,加上session共享的应用才会session共享
当用到 session 的时候会在 Redis 中直接获取session
# redis的session管理是利用spring提供的session管理解决方案,将一个应用session交给Redis存储,整个应用中所有的session的请求都会去redis中获取对应的session数据。开发 session 管理
引入依赖 <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> 开发 session 管理配置类 @Configuration @EnableRedisHttpSession public class RedisSessionManager { } 打包测试即可