一、什么是NOSQL?
本质上Redis就是一个数据库,只不过它跟我们常见的MySQL、Oracle数据库不一样的是,它是一个NOSQL数据库。那么你可能会问,什么是NOSQL数据库呢?官方给出的翻译**”不只是SQL“。**可能你还会有些疑惑。其实Redis就是一个不需要用SQL语句的数据库,它是一种非关系型的数据库。底层采用的是键值对的方式存储数据。没错,也就是我们常说的,map结构。
二、为什么要使用NOSQL?
2.1、关系型数据库的缺点
- 无法应对每秒上万次的读写请求,无法处理大量集中的高并发操作。关系型数据的是 IO 密集的应用。硬盘 IO 也变为性能瓶颈
- 无法简单地通过增加硬件、服务节点来提高系统性能。数据整个存储在一个数据库中 的。多个服务器没有很好的解决办法,来复制这些数据。
- 关系型数据库大多是收费的,对硬件的要求较高。软件和硬件的成本花费比重较大。
而作为NOSQL数据库的代表产品之一的Redis,就是为了解决这些缺点而存在的。
2.2、NOSQL的优势
(1)大数据量,高性能
NoSQL 数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益 于它的无关系性,数据库的结构简单。关系型数据库(例如 MySQL)使用查询缓存。这 种查询缓存在更新数据后,缓存就是失效了。在频繁的数据读写交互应用中。缓存的性能 不高。NoSQL 的缓存性能要高的多。
(2)灵活的数据模型
NoSQL 无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关 系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直 就是一个噩梦。尤其在快速变化的市场环境中,用户的需求总是在不断变化的。
(3)高可用
NoSQL 在不太影响性能的情况,就可以方便的实现高可用的架构。 NoSQL 能很好的解决关系型数据库扩展性差的问题。弥补了关系数据(比如 MySQL) 在某些方面的不足,在某些方面能极大的节省开发成本和维护成本。MySQL 和 NoSQL 都有各自的特点和使用的应用场景,两者结合使用。让关系数据库关注在关系上,NoSQL 关注在存储上。
(4)低成本
这是大多数分布式数据库共有的特点,因为主要都是开源软件,没有昂贵的 License 成本
对于初学者的我们来说可能没有办法非常直观的接触到海量的数据的读写。但在真实的企业开发环境中,对于海量的数据,使用MySQL等关系型的数据库通常会存在读写缓慢,用户体验差的情况。而Redis就是为了解决这种问题,它在牺牲了一定安全性的情况下,获取了高性能的读写速度(map结构的读写十分迅速)。另外,它在处理高并发以及高扩展性也有很大的优势。
三、Redis的使用
详情可以参考官方文档:https://redis.io/topics/quickstart
3.1、安装
【1】首先,先去官网上下载Redis最新版。https://redis.io/
【2】将下载下来的压缩文件上传到Linux系统中
【3】安装最新版的gcc编译器
gcc --version # 测试gcc的版本
yum install gcc # 安装最新版的gcc编译器
【4】在/opt目录下解压
tar -zxvf redis-6.2.6.tar.gz
【5】解压完成后进入到Redis目录下
cd redis-6.2.6
【6】Redis目录下,执行编译命令
make install
【7】进入到Redis目录下,有一个redis.conf的文件,用vim编辑器打开
【8】搜索找到daemonize no,把no改成yes,它表明的是允许后台启动
【9】启动Redis
redis-server /opt/redis-6.2.6/redis.conf
启动Redis成功
【10】启动Redis客户端连接
【11】关闭Redis客户端,直接在终端驶入shutdown即可
3.2、数据类型及其常用命令
Redis的常用命令大全见官网网址:http://www.redis.cn/commands.html,这里不逐一介绍命令的使用,详情可参见官网。
数据类型的介绍详情见官网:http://www.redis.cn/topics/data-types.html
3.3、redis.conf
详情见官网:http://www.redis.cn/topics/config.html
这里需要注意的是,得修改一下配置文件中的一些设置,其他的默认即可:
vim /opt/redis-6.2.6/redis.conf
3.4、Redis的发布和订阅
3.4.1、是什么?
Redis 发布订阅(pub/sub) 是一种消息通信模式:发送者(pub) 发送消息,订阅者(sub) 接收消息。如下图所示:
服务端发送消息到频道上:
客户端订阅该频道,即可获取服务端发送的消息:
这跟我们日常生活中看电视“换台”是一个模式
3.4.2、怎么用?
打开一个客户端A,订阅channel1频道。
打开另外一个客户端B,往channel1频道发送消息
这时再返回客户端A,已经得到客户端B发送的消息,订阅成功!
3.5、使用Jedis操作Redis数据库
Jedis操作数据库其实与JDBC操作数据库本质上没有任何的区别。
【1】导入依赖
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version>
</dependency>
【2】连接测试
public class RedisTest01 {public static void main(String[] args) {Jedis jedis = new Jedis("192.168.56.128",6379);String value = jedis.ping();System.out.println(value);}
}
输出:PONG ,表明连接成功
注意:如果出现连接失败的异常,则需要检查是否已经关闭Linux的防火墙,Redis服务端是否已经开启
具体操作命令,可参见Jedis的API文档(百度搜索)。这里就不逐一演示了。
3.6、SpringBoot整合Redis
【1】新建一个SpringBoot工程
【2】导入依赖
<!-- 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>
【3】application配置
#Redis服务器地址
spring.redis.host=192.168.56.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
【4】Redis配置类
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic 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;}@Beanpublic 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;}
}
【5】编写Controller测试
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {@Autowiredprivate RedisTemplate redisTemplate;@GetMappingpublic String testRedis() {//设置值到redisredisTemplate.opsForValue().set("name","zhangsan");//从redis获取值String name = (String)redisTemplate.opsForValue().get("name");return name;}
}
需要注意的是:如果你完成以上配置之后启动Springboot报错,那么有可能你需要设置一下Springboot的版本,新版的Springboot对于Redis的兼容性很差。
四、Redis6中的事务操作
4.1、是什么?
Redis中的事务本质上**就是将命令进行串行执行,也就是说把一堆命令放在一组进行依次执行,这一组的命令要么同时成功,要么同时失败。**它在进行事务处理的时候大致分为两个阶段。通过三个命令来控制: Multi、Exec和discard
第一个阶段是组队阶段,在这个阶段,你可以不断输入不同的命令,这些命令会按照顺序,依次入队,最终会形成一个命令队列。输入Multi命令开启组队,组队的过程中可以通过discard命令放弃组队。
第二个阶段是执行阶段,将第一阶段形成的命令队列,按照入队的先后熟顺序依次执行。组队完成后,通过执行Exec命令执行命令队列。
4.2、错误处理
组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
4.3、为什么要使用事务?
当然是为了保证一组命令同时执行成功,在一些业务场景下比如账户余额的添加与删除等等,需要使用到Redis事务。详情课参见官方文档:https://redis.io/topics/transactions
4.4、事务冲突
模拟一个场景:一个请求想给金额减8000,一个请求想给金额减5000,一个请求想给金额减1000
三个请求同时对金额进行操作,则会出现事务的冲突问题,那么如何解决这个冲突呢?Redis是通过乐观锁的机制解决这个冲突的,通过执行命令watch来开启监控某一个或多个key,unwatch来关闭监控
4.4.1、悲观锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
4.4.2、乐观锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
4.5、Redis事务的三特性
-
单独的隔离操作
-
没有隔离级别的概念
-
不保证原子性
另外,还需要注意的是,Redis中的事务是不支持回滚的,这是因为,**Redis中的事务是通过命令来串行执行的,也就是说,只有在使用错误的语法(并且在命令排队期间无法检测到问题)或针对持有错误数据类型的键调用时,Redis 命令才会失败。这意味着,失败的命令实际上是编程错误的结果,是一种在开发阶段很容易被检测到的错误。这时,回滚已经没有很大的意义了,因为,你的命令队列中的命令本身就已经有问题了,即使回滚,也无法避免编程命令的错误。**但MySQL中的事务就不一样了,MySQL中的事务在生产环境中是会出现问题的,因此必须要通过回滚来保存数据。
五、Redis中的持久化操作
5.1、RDB
5.1.1、基本原理介绍
在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”
一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
5.1.2、优劣势
优势:
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
劣势:
-
Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
-
虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
-
在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
5.2、AOF
5.2.1、基本原理
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
5.2.2、持久化流程
(1)客户端的请求写命令会被append追加到AOF缓冲区内;
(2)AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
(3)AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
(4)Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
需要注意的是,AOF是默认不开启的,并且当Redis同时开启RDB和AOF时,系统默认读取AOF的数据,因为这样不会造成数据的丢失。
5.2.3、优劣势
优势:
-
备份机制更稳健,丢失数据概率更低。
-
可读的日志文本,通过操作AOF稳健,可以处理误操作。
劣势:
-
比起RDB占用更多的磁盘空间。
-
恢复备份速度要慢。
-
每次读写都同步的话,有一定的性能压力。
-
存在个别Bug,造成恢复不能。
两种持久化策略,官方的推荐是两个同时启用,如果对数据不敏感,可以选择单独用RDB,不建议单独使用AOF,因为可能会出现BUG,如果只是做纯内存的缓存,可以都不用。
六、主从复制
6.1、是什么?
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主。
6.2、能干嘛?
1、读写分离:将读操作与写操作分离开,减轻服务器的压力。
2、容灾的快速恢复:当其中一台服务器宕机之后,能快速启用备份从服务器进行数据恢复。
6.3、怎么用?
搭建一主二仆的形式进行模拟测试,搭建过程如下:
【1】在跟目录下新建一个myredis文件夹,并将Redis安装目录下的redis.conf复制到该文件夹下
mkdir myredis
cp /opt/redis-6.2.6/redis.conf /myredis
需要注意的是,redis.conf中的 appendonly 修改为no
开启daemonize yes
【2】在myredis目录下,新建三个redis配置文件,如下所示
redis6379.conf中填写以下内容:
include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
redis6380.conf中填写以下内容:
include /myredis/redis.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
redis6381.conf中填写以下内容:
include /myredis/redis.conf
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb
【3】分别启动三个Redis服务器
【4】分别启动三台服务器,使用info replication命令查看三台服务器的主从信息
【5】将6379设置为主机,其他的设置为从机,在6380、6381上分别指向以下命令
【6】这时,查看三台服务器的主从信息,发现只有6379的角色为主机,其余的均为从机
【7】测试读写,主机拥有读写权限,从机只拥有读权限,从而实现了读写分离
1、主机如果挂掉,重启即可,但从机如果挂掉,重启之后需要重新设置slaveof 127.0.0.1 6379,可以将其设置到配置文件中从而实现永久生效。
2、从服务器重启后会将主机中的数据从头进行全部复制
3、主机如果挂掉之后,默认情况下,从机不会**”上位“**为主机,依旧是从机,主机重启后依旧直接是主机
6.4、主从复制的原理
1、从机连接上主机之后,从机会向主机发送一个要进行数据同步的消息(sync指令)
2、主机接到从机发送的消息之后,会先把主机中的数据进行持久化,得到rdb文件中,然后再把RDB文件发送给从机,从机拿到RDB文件后再进行读取,从而实现持久化数据
3、每次主机进行写操作之后,会向从机中发送数据同步的消息,和从机进行数据的同步。
6.5、薪火相传
当从机数量增多的时候,如果主机仍然只有一个,这时主机向从机写数据的压力会十分巨大,你想象一下,一个主机给成百上千个从机同步更新数据会是什么样的一个场景。那么,怎么减轻主机的压力呢?
没错,就是使用**“伪主机”**的概念,有点类似于Java里的继承,可以继承多代,最后会有一个祖宗类。如下图所示:
依然使用slaveof ip port指令指定中间结点为**“伪主机”**,上一个从机作为下一个从机的主机,这样可以有效的减轻根主机的写压力。去中心化降低风险。
1、中途变更转向:会清除之前的数据,重新建立拷贝最新的
2、一旦某个slave宕机,后面的slave都没法备份
3、主机挂了,从机还是从机,无法写数据了
6.6、反客为主
当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。使用slaveof no one指令将从机变为主机
七、哨兵模式
7.1、是什么?
哨兵模式其实本质上就是一个反客为主的自动版,即从机派出一个哨兵去监控主机的状态,一旦主机挂掉,会将消息传递到从机,从机在得到消息之后,按照一定的选举规则,重新选举一个从机作为主机。原先的主机也自动降级为从机,重启之后会作为新主机的从机。如下图所示:
选举机制
最高优先级:优先级靠前的,在redis的配置文件中replica-priority 值越小优先级越高,默认为100
第二优先级:优先级一致的情况下,选择偏移量高的(与主机数据同步率最高的)
第三优先级:选择runid最小的(每一个服务器在成为从机之前会随机生成一个40位的runid)
7.2、为什么?
为什么要使用哨兵模式呢?这个应该就很容易理解了,当然是为了实现自动监控主机的状态啊,不可能当主机挂掉几天了,才知道它需要重新指定主机。这不仅造成了资源的浪费,而且还及其容易出现数据安全问题。
7.3、怎么用?
【1】在myredis文件夹下新建一个sentinel.conf文件,注意名字一定一定不能错
【2】往sentinel.conf里添加以下内容:
sentinel monitor mymaster 127.0.0.1 6379 1
mymaster表示主机的别名,可以随意取,最后一个数字1表示至少有几个哨兵同意迁移的数量,什么意思呢?不止有一个从机因此也就不止有一个哨兵监控主机的状态,它的值为1表示只需要有一个哨兵同意即可实现自动反客为主,如果为2则需要两个哨兵同意,以此类推。
【3】开启哨兵监控
【4】此时的状态是6379为主机,6380和6381为从机,当主机挂掉之后,已经切换为6381为主机
【5】原主机重启之后也变为了从机
自动监控可能会有一定的延时,因此,当完成某些操作之后需要等待一定的时间,哨兵才会完成相应的操作
八、Redis集群
8.1、是什么?
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
8.2、为什么?
当Redis容量不够的时候,如何进行扩容?
高并发写操作,Redis如何进行分摊?
解决这些问题都需要用到就集群。**另外,主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。**以前是通过代理主机来解决,但是redis3.0中提供了解决方案。就是无中心化集群配置。
8.3、怎么用?
使用六个端口号搭建六台Redis服务器进行模拟
【1】进入到myredis文件夹下,删除文件夹下的RDB文件、redis6380.conf、redis6381.conf
【2】打开redis6379.conf,删除多余的配置,并加上新的配置
【3】将redis6379.conf复制5份分别为6380,6381,6389,6390,6391
【4】修改其余五个配置文件的端口号,改为对应的端口号
【5】启动六个redis服务
确保redis服务都已经关闭,只剩下这个
重启六个redis服务
【6】将六个节点合成一个集群
组合之前,确保六个服务都已经开启,并且node-xxx文件已经生成
进入到src目录下
cd /opt/redis-6.2.6/src
执行合体命令,IP地址写你Linux的IP地址,注意千万别写错
redis-cli --cluster create --cluster-replicas 1 192.168.56.128:6379 192.168.56.128:6380 192.168.56.128:6381 192.168.56.128:6389 192.168.56.128:6390 192.168.56.128:6391
1、一定要进入到redis的安装目录下的src目录下,否则集群可能会失败
2、低版本的可能需要重新安装一个ruby环境,redis6以上的版本默认集成在src目录下,不需要重新下载。
【7】测试
使用任意一个节点都可以进行集群连接,-c表示采用集群策略连接
redis-cli -c -p 6379
查看集群配置相关信息
cluster nodes
主从关系一目了然,集群搭建成功。
一个集群至少要有三个主节点。
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
8.4、集群是如何存储数据的?
通过计算插槽值,当集群搭建完成之后,redis会为每一个节点分配一个插槽范围,一个 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 参数实现自动重定向。
注意,在集群环境下是不能进行存储多键操作的,只能存储单键,如果一定要存储多键,则需要借助组的概念
九、常见应用问题解决
9.1、缓存穿透
9.1.1、原理介绍
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。(非正常的URL访问)
特征
1、应用服务器压力突然变大,大量请求发送给应用服务器
2、redis命中率降低
3、缓存中查询不到数据,一直查询数据库
9.1.2、解决方案
一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
(1) **对空值缓存:**如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
(2) 设置可访问的名单(白名单):
使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
(3) 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
(4) **进行实时监控:**当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
9.2、缓存击穿
9.2.1、原理介绍
某个key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
特征
1、数据库的访问压力瞬时增加
2、redis里面没有出现大量的key过期
3、redis正常运行
9.2.2、解决方案
key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。
**(1)预先设置热门数据:**在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
**(2)实时调整:**现场监控哪些数据热门,实时调整key的过期时长
(3)使用锁:
-
就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
-
先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
-
当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
-
当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
9.3、缓存雪崩
9.3.1、原理介绍
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。缓存雪崩与缓存击穿的区别在于雪崩是针对很多key缓存,击穿则是某一个key
正常访问:
缓存失效:
9.3.2、解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可怕!
(1) **构建多级缓存架构:**nginx缓存 + redis缓存 +其他缓存(ehcache等)
(2) **使用锁或队列:**用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
(3) **设置过期标志更新缓存:**记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
(4) **将缓存失效时间分散开:**比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key
-
当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;
-
当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。
9.3、缓存雪崩
9.3.1、原理介绍
key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。缓存雪崩与缓存击穿的区别在于雪崩是针对很多key缓存,击穿则是某一个key
正常访问:
缓存失效:
9.3.2、解决方案
缓存失效时的雪崩效应对底层系统的冲击非常可怕!
(1) **构建多级缓存架构:**nginx缓存 + redis缓存 +其他缓存(ehcache等)
(2) **使用锁或队列:**用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
(3) **设置过期标志更新缓存:**记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
(4) **将缓存失效时间分散开:**比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。