浅谈Redis

感谢bilibili的up主 **狂神说**,此文档仅作为整理redis文档。观看视频可点击    狂神说-Redis

狂神说-Redis

在早期的架构中,用户请求都是直接操纵数据库,如果请求的数据量不是很大,这是没有问题的,但是如果数据量上来了,即使表中添加了索引,那么并发访问对数据库的压力依然很大,很可能直接拖垮数据库。在此场景下产生了高性能的非关系型数据库 如:redis 、也称远程字典服务。传统的数据库表与表之间都有一定的关系,大部分情况下都是通过表关联查询,而redis则不同,它仅仅是通过键值对存储,没有所谓表的概念。
1、什么是Redis
官方回答:

	Redis是开放源代码(BSD许可)的内存中数据结构存储,用作**数据库****缓存****消息代理**。
Redis提供数据结构,例如 **字符串****哈希****列表****集合**,
带范围查询的排序集合,**位图****超日志****地理空间索**引和****。
Redis具有内置的复制,Lua脚本,LRU逐出,事务和不同级别的磁盘持久性,
并通过以下方式提供高可用性:Redis Sentinel和Redis Cluster自动分区。
您可以 对这些类型运行原子操作,例如追加到字符串; 在哈希中增加值; 
将元素推送到列表; 计算集的交集, 并集和差; 或在排序的集合中获得排名最高的成员。
为了获得最佳性能,Redis使用 内存中的数据集。根据您的用例,可以通过定期将数据集转储到磁盘 
或通过将每个命令附加到基于磁盘的日志来持久化数据。
如果只需要功能丰富的网络内存缓存,则还可以禁用持久性。
redis还支持异步复制,具有非常快速的非阻塞式首次同步,以及在网络拆分时具有部分重新同步的自动重新连接。

2、相对于传统数据库为什么那么快?

    2.1 redis是基于内存操作而且还是单线程,cpu并不是性能瓶颈,计算机的内存和带宽才是。
    (多线程服务时cpu会有上下文切换,耗时操作,对于内存来说,没有上下文切换效率就是最高的,
    多次读写都在一个cpu,在内存情况下,就是最佳方案)
    2.2  传统数据库数据存在磁盘中,而redis直接操作内存。
    2.3  采用非阻塞式IO

3、常用命令
redis命令非常大,要想每个都记住确实不容易,此处贴出redis官网超链接,方便查找命令 redis命令

	flushdb:清空当前数据库
	flushall:清空所有数据库
	select[ 0-15](redis有16个数据库,默认使用第一个,
	具体可以查看redis.conf配置文件)
3.1  常用key命令
127.0.0.1:6379> keys * 		查看所有的key
1) "key"
2) "key2"
127.0.0.1:6379> exists key   判断指定的key是否存在
(integer) 1
127.0.0.1:6379> type key      查看key的数据类型
string
127.0.0.1:6379> expire key 50 指定key失效时间
(integer) 1
127.0.0.1:6379> ttl key       查看key剩余失效时间
(integer) 46
127.0.0.1:6379> del key			删除指定的key
(integer) 1
127.0.0.1:6379> move key2 1		将指定key移到别的数据库
(integer) 1
127.0.0.1:6379> 	



3.2 redis支持五大数据结构类型:String、List、Set、ZSet、Hash

**String**:
字符串应用场景比较多如:计数器、统计访问量、粉丝数、对象存储、接口唯一性校验
127.0.0.1:6379> set name chenchen		设置值
OK
127.0.0.1:6379> get name				获取值
"chenchen"
127.0.0.1:6379> append name ,hello	   	末尾追加值,如果值不存在,就创建
(integer) 14
127.0.0.1:6379> 

127.0.0.1:6379> set view 0				 设置key:view 初始化为0
OK
127.0.0.1:6379> incr view				  给view 每次自增操作+1
(integer) 1									如浏览量、访问量
127.0.0.1:6379> incr view
(integer) 2
127.0.0.1:6379> decr view				  给view每次自减操作-1
(integer) 1
127.0.0.1:6379> 
127.0.0.1:6379> getrange name 0 1		  截取范围内数据[start,end]起始位置。
"ch"
127.0.0.1:6379> setrange name 0 h		  替换指定位置的数据
(integer) 14
127.0.0.1:6379> 
127.0.0.1:6379> setex name 10 chenchen	  指定key设置失效时间,单位:秒
OK											如:接口唯一性校验
127.0.0.1:6379> ttl name
(integer) 5
127.0.0.1:6379> setnx name2 songyi		 指定key设置值,若不存在就新建,存在就覆盖。
(integer) 1


127.0.0.1:6379> mset key value key2 value2	 同时设置多个key(原子性操作)
OK
127.0.0.1:6379> mget key key2				 同时获取多个key中value
1) "value"
2) "value2"
127.0.0.1:6379> 
127.0.0.1:6379> getset key3 songyi			先get再set,如果不存在值,则返回nil
(nil)
127.0.0.1:6379> 
**List**
list数据应用也比较广泛 如:可以做栈、可以做队列,比如从左边存数据,然后从右边拿数据,依次消费、
或者热点数据
127.0.0.1:6379> lpush list one			从左边存值
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1       根据key获取值[0-1]若为-1就取出所有
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpush list for			从右边开始存值
(integer) 4
127.0.0.1:6379> 

127.0.0.1:6379> lpop list              从左边弹出元素
"three"
127.0.0.1:6379> lindex list 0		   指定位置获取元素
"one"
127.0.0.1:6379> llen list              获取key长度
(integer) 2

127.0.0.1:6379> lrem list 0 one		  移除指定位置的值
(integer) 1

Set
4 、事务
Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行,也就是说所有命令在事务中,并没有直接执行,而是发起执行命令的时候才会依次执行,另外redis中事务不保证原子性(多条执行命令情况下),但是单条命令是保证原子性。
事务特性:一致性、顺序性、排他性。
相当于队列 ---- set、set set、--------,redis事务本身并没有隔离的概念,自然不会有什么脏读、不可重复读、幻读等。
redis事务语法:
开启事务:multi
命令入队:…
执行事务:exec

4.1 正常执行事务

127.0.0.1:6379> multi			开启事务
OK
127.0.0.1:6379> set k1 v1		命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> exec			执行事务
1) OK
2) OK

4.2 取消事务
如果执行过程中,不想使用事务,也可以放弃事务,同时事务队列中命令也不会执行。

127.0.0.1:6379> multi				开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> discard				取消事务
OK
127.0.0.1:6379> get k2				此时获取k2值为null,这就说明队列中命令未执行。
(nil)
127.0.0.1:6379> 

4.3 编译型异常
如果命令有错,那么事务队列所有命令也不会执行。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k2				错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec					执行:说明编译异常后,执行命令报错。
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2				   此时获取k2值未null,
(nil)
127.0.0.1:6379> 

4.4 运行时异常
如果语法性错误,执行命令时,其他命令可以正常执行,错误命令抛异常。

127.0.0.1:6379> 
127.0.0.1:6379> set k v
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k		执行失败(只接受int类型)
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec		第一条执行失败,剩余两条执行成功,这也说明多命令下并不能保证原子性
1) (error) ERR value is not an integer or out of range
2) OK
3) v2
127.0.0.1:6379> 

5、监控
悲观锁:
很悲观,认为什么时候都会出现问题,无论做什么都会加锁。
乐观锁:
5.1、很乐观,认为什么时候都不会出现问题,所以不会上锁,更新数据的时候判断一下,是否有人修改过这条数据。
5.2、获取version
5.3、更新的时候比较version
Redis监测测试
正常执行成功
在这里插入图片描述
此时另外开启一个客户端,模拟多线程修改值,使用watch可以当做redis的乐观锁操作。
在这里插入图片描述
然后再次执行money操作,可以发现当另外一个线程修改了money,watch就会监测到,此时当前线程提交就会失效。
在这里插入图片描述
事务执行失败的话,再次执行需要先解锁:unwatch,然后获取最新的值。
在这里插入图片描述
6 、Jedis
Jedis是官方推荐的java连接开发工具,但是在spring中从2.x之后,内部不再是jedis连接,而是换成了lettuce,这个待会再说。
使用jedis步骤相对简单一些,大致分为以下几步。
6.1、导入依赖

	<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.7.1</version><!--版本号可根据实际情况填写-->
        </dependency>

6.2、测试代码

public class JedisTest {
    public static void main(String[] args) {
    JSONObject jsonObject= new JSONObject();
            jsonObject.put("hello", "world2021");
            jsonObject.put("worl", "worl");
         //开启事务
            Transaction multi = jedis.multi();
       try {
            /**
             * String
             */
            
            String s = jsonObject.toJSONString();
              multi.set("h", s);
            multi.set("h2", s);
            int d = 1 / 0;//执行失败,事务抛出异常
            multi.incr("ww");
            multi.set("h233", s);
            multi.exec();
            multi.set("key", s);
            multi.set("key2", s);
            //List
            jedis.lpush("list", "one");
            jedis.lpush("list", "two");
            jedis.lpush("list", "three");
            System.out.println("list:" + jedis.lrange("list", 0, -1));
            //Set
            jedis.sadd("set", "setValue");
            System.out.println("set:" + jedis.srandmember("set"));
            //ZSet
            jedis.zscan("zset", "zsetValue");
            System.out.println("zset:" + jedis.zscan("zset", "zsetValue"));
            //Hash
            jedis.hdel("hashkey", "hashvalue");
            System.out.println(jedis.hget("hashkey", "hashvalue"));
            System.out.println(jedis.hgetAll("hashkey"));
            multi.exec();
        } catch (Exception e) {
             multi.discard();//放弃事务
            e.printStackTrace();
        } finally {
            System.out.println("String:" + jedis.get("key"));
            jedis.close();
        }
    }
}

7 SpirngBoot整合
SpringBoot整合redis相对于jedis就简单的很多,使用起来也是很爽,一个Template就搞定,但是建议还是对jedis要了解,有时候直接用现成的api也不是好事,鬼知道里面到底是干了啥,所以啊我们要从基本命令再到jedis最后是SpringBoot,顺序一定不能弄反了。
刚才说到SpringBoot2.x之后,原来的jedis替换成了lettue,下面以图说话
在这里插入图片描述
点进去查看
在这里插入图片描述
jedis:采用的直连,多个线程操作的话,是不安全的,如果想要避免,可以选择jedis pool连接池
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数据,更像NIO

源码分析:

@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}//此处的redisTemplate可以自定义替换
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    //默认的redisTemplate没有过多的设置,redis对象都是需要序列化
    //泛型全是Object的类型,自己使用需要强转<String,Object>
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

springboot集成步骤
7.1 、导入依赖

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.4.RELEASE</version>
        </dependency>

7.2、如果是本地测试,注入RedisTemplate 即可(内部初始化了一些属性),里面封装的大量的方法

@SpringBootTest
public class RedisTemplateTest {
    @Autowired
    private RedisTemplate redisTemplate;

    public void Test() {

        redisTemplate.opsForValue().set("test", "value");
        System.out.printf(redisTemplate.opsForValue().get("test").toString());
    }
}

7、在说持久化之前,有必要简单说一下redis的配置文件,里面有很多核心配置,以后使用过程中需要学会合理的配置。
1、NETWORK 网络
在这里插入图片描述
2、GENERAL
在这里插入图片描述
3、SNAPDHOTTING(数据快照)
在这里插入图片描述
在这里插入图片描述

8、 Redis持久化
redis是内存数据库,如果不将内存中的数据库状态保存在磁盘中,如果服务器进程退出,(比如最恶劣的情况,突然断电了)服务器中的数据库状态也会消失,所以redis提供了持久化技术。redis持久化一般分为RDB和AOF两种方式。
RDB(Redis DataBase)
在这里插入图片描述
在指定的时间间隔内将内存中的数据集快照(Snapshot)写入磁盘,恢复时是将文件直接读到内存里,redis会单独创建(fork)一个子进程进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作,这样就确保了极高的性能,如果需要大规模数据的恢复,并且对于数据恢复的完整性不是非常敏感,那么RDB方式要比AOF方式更加的高效,但是RDB确定是最后一次持久化的数据可能丢失。

触发机制
在这里插入图片描述
在这里插入图片描述

8.1、save的规则满足的情况下,自动触发reb规则
8.2、执行flushall命令,也会触发rdb规则
8.3、退出redis也会产生rdb文件
备份就自动生成一个dump.rdb文件。
在这里插入图片描述
测试的话非常简单,我们首先吧dump.rdb文件删除,再新设置5个key(60s内),此时模拟突然断电操作(SHUTDOWN),此时查看redis进程已经不存在,然后再次重启redis,获取key,可以发现值依然可以获取的到,说明设置的规则生效。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
一般情况下,无需修改原config文件,默认的配置已经满足很多应用场景,特殊情况下需要自行配置。
RDB
优点:
1、适合大规模的数据恢复
2、对数据的完整性要求不高
缺点
1、需要一定的时间间隔进程操作,如果redis意外宕机,那么最后一次修改的数据就没有了
2、fork进程的时候,会占用一定的内容空间。

AOF(Append Only file) 追加文件
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部执行一遍。
在这里插入图片描述
配置文件中默认是no(不开启的)。需要手动配置,改为yes,就开启了aof,最后重启redis就可以生效了。如果本身aof文件有错误,redis起不来,我们需要修复aof文件,官方提供了一个工具,
redis-check-aof --fix
在这里插入图片描述

在这里插入图片描述
aof配置文件中属性:
appendonly no :默认不开启aof模式,默认使用rdb方式持久化,大部分情况下rdb就可以
appendfilename '‘appendonly.aof’ :持久化文件名称
appendfsync always: 每次修改都会sync。消耗性能
appendfsync everysec: 每秒执行一次sync,可能会丢失这 1 s的数据
appendfsync no: 不执行sync,这个时候操作系统自己同步数据,速度最快

**优点:**
		1、每一次修改都同步,文件的完整性会更加好
		2、每秒同步一次,可能会丢失一秒的数据。
		3、从不同步,效率最高
		
		**缺点**
		 1、相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢,
		 2、aof运行效率也比rdb慢,所以默认redis配置就是rdb持久化。

在这里插入图片描述
在这里插入图片描述
8 、Redis发布订阅
redis发布订阅(pub/sub)是一种消息通信模式,发送者pub发送消息,订阅者sub接收消息,
redis客户端可以订阅任意数量的频道。
订阅/发布消息图
第一个:消息发送者,第二个:频道 第三个:消息订阅者在这里插入图片描述在这里插入图片描述
订阅者(SUB)
在这里插入图片描述
发布者(PUB)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Pub/Sub字面理解就是发布与订阅,在redis中可以设定对某一个key值进行消息发布与消息订阅。当一个key值上进行 消息发布后,所有订阅他的客户端都会收到相应的的消息。
使用场景:
1、实时消息系统
2、实时聊天
3、订阅、关注系统。

9、Redis主从复制
主从复制是指将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master/leader),后者称为从节点(slave/follwer);数据的复制是单向的,只能是主节点到从节点。Master以写为主,Salve以读为主。
默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用:
1、数据冗余,主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复,当主节点出现问题时,可以由从节点提供服务,实现快速 的故障恢复,实际上是一种服务的冗余。
3、负载均衡,在主从复制的基础上,配合读写分离,可以由主节点提供服务,由从节点提供服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载均衡,尤其是在写少读多的场景下,通过多个从节点分担负载,可以大大提高redis服务器的并发量。
4、高可用(集群)基石,除了上述作用以为,主从复制还是哨兵和集群实施的基础,因此说主从复制是redis高可用的基础。

一般来说,要将redis运行于工程项目中,只使用一台redis是万万不能的(可能宕机 ,一主二从),原因如下,
1、从结构上,单个redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大。
2、从容量上,单个redis服务器内存容量有限,就算一台redis服务器内存容量256G,也不能将所有内存用作redis存储内存,一般来说,单台redis最大使用内存不应超过20G。例如像电商网站商品,一般都是一次上传,无数次浏览,也就是多读少写
主从复制,读写分离,80%情况下都是读操作,减轻服务器的压力。
低配一主二从。
在这里插入图片描述
集群环境配置
只配置从库,不配置主库(”默认当前的就是主库“)
在这里插入图片描述
在这里插入图片描述
复制3个配置文件,依次修改对应的信息
1、端口
2、pid名字
3、log文件名字
4、dump.rdb名字
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
配置完毕,依次启动,查看启动状态

redis-server resdisconf/redis6379.conf //指定配置文件启动
redis-server resdisconf/redis6380.conf
redis-server resdisconf/redis6381.conf
redis-cli -p  6379 //指定端口号启动客户端
redis-cli -p  6380
redis-cli -p  6381

在这里插入图片描述
一主二从(默认情况下,每台Redis服务器都是主节点,所以一般情况下,只需要配置从机)
例如:本例 一主(6379)二从(6380,6381)也就是说让6379当6380和6381的领导。
在这里插入图片描述
配置完毕,可以看到主机下有两个从机的。
在这里插入图片描述
真实的主从配置应该在conf配置文件中配置(REPLICATION),因为这样是永久配置,刚才仅仅只是命令操作,暂时的。
在这里插入图片描述

细节
主机可以写,从机不能写,只可以读!主机中所有的信息和数据,都会自动被从机保存。
主机写:
在这里插入图片描述
从机只能读取内容!
在这里插入图片描述
测试:主机断开连接,从机依旧连接到主机,但是没有写操作,如果这个时候主机回来了,从机依旧可以直接获取到主机写的信息。
如果是使用命令,来配置的主从,这个时候如果重启了,就会变回主机,但是如果变为从机,立马会从主机中获取值。
复制原理
Slave启动成功连接master后会发送sync同步命令。
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并将一次完全同步。
全量复制:slave服务在接收的数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传递给slave,完成同步。
但是只要重新连接到master,一次完全同步(全量复制)将被自动执行,我们的数据一定可以砸从机中看到!

哨兵模式(自动选举老大的模式)
主从切换技术的方法是:当主武器宕机后,需要手动吧一台服务器切换为主服务器,这就是需要人为干预,费时费力,还会造成一段时间内服务不可用。这不是推荐的方式,更多时候,我们优先考虑哨兵模式。Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。
谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库切换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行,其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis的实例。
在这里插入图片描述
哨兵作用:
1、通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
2、当哨兵检测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机。
但是实际情况下,一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此我们可以使用多个哨兵进行监控。各个哨兵之间互相监控。这样就形成了多哨兵模式。
在这里插入图片描述
假设主服务器宕机,哨兵1首先检测到这个结果,此时系统并不会马上进行failover(故障转移/选举)过程,仅仅是哨兵1主观的认为主服务器不可用,这个想象称为主观下线,当后面的 哨兵也检测到主服务器不可用,并且数量达到一定数值时,那么哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover操作,切换成功后,就会通过发布订阅模式,让各个哨兵吧自己监控的从服务器实现切换主机,这个过程称为客观下线。

例子:我们目前状态是一主(6379)二从(6380/6381)
1、创建哨兵配置文件

touch sentinel.conf 名字一定不能写错!!!!

2、配置哨兵配置文件sentinel.conf

#sentinel monitor 被监控的名称 host port 1
# 数字 1,表示主机6379挂了,slave投票看让谁接替成为主机,票数最多的就成为主机
sentinel monitor myRedis 127.0.0.1 6379

3、启动哨兵

redis-sentinel redisconf/sentinel.conf 

在这里插入图片描述
4、模拟Master主机断开
在这里插入图片描述
5、此时哨兵已经选举完毕,分别在6380、6381 查看信息
在这里插入图片描述
如果旧主机6379已经恢复正常,但是此时只能回归到新的主机下6381,也就说只能当从机,(除非6381宕机了,6379才有可能再次被选举为主机)这就是哨兵的规则。
在这里插入图片描述
哨兵模式优缺点
优点:

	1、哨兵集群基于主从复制模式,所有的主从配置优点,他全有。
	2、主从可以切换,故障可以转移,系统的可用性就会更好。
	3、哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点:

	1、Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦。
	2、实现哨兵模式的配置其实是很麻烦的,里面有很多选择。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
10 、Redis缓存穿透、击穿、雪崩
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时他也带来了一些问题,其中最最严重的的问题,就是数据一致性问题,从严格意义上来讲,这个问题无解,如果对数据的一致性要求很高,那么不能使用缓存。另外一些典型的问题缓存穿透、缓存击穿、雪崩。

缓存穿透

概念:如果用户查询一条数据,发现redis内存数据库没有,
	也就是缓存没有命中,于是向持久层数据库查询,发现也没有,
	于是本次查询失败,当很多用户的时候(秒杀活动),缓存都没有命中,
	于是都请求了持久层数据库,造成持久层数据库很多的压力,严重时直接数据库直接崩了,
	这时候就出现了缓存穿透。

解决方案一

布隆过滤器
		布隆过滤器是一种数据结构,对所有可能查询的参数已hash的形式存储,在控制层先进行校验,
		不符合则丢弃,从而避免了对底层存储系统的查询压力。

在这里插入图片描述
解决方式二

缓存空对象
	当存储层未能命中,即使返回的空对象也将其缓存起来,同时设置一个过期时间,
	之后再访问这个数据将会从缓存中获取,保护了持久层数据库。

在这里插入图片描述

但是这种方式会存在两个问题:
	1、如果空值对象能够被缓存起来,这就意味着缓存需要更多的空间存储更过的键,
	因为这当中可能会有很多空值的键。
	2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,
		这对需要保持一致性的业务会有影响。

.
缓存击穿

	 	缓存击穿是指一个key非常热点,在不停的扛着大并发,当这个key 在失效的瞬间,
	 	持续的大并发就穿透缓存,直接请求数据库,就像一个屏障上凿开了一个洞,
	 	当这个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据
	 	(如 微博某个明星出轨.....),由于缓存过期,
	 	会同时访问数据库来直接查询最新数据,并且回写缓存,会使数据瞬间压力过大。

·
解决方式一

设置热点数据永不过期
	 			从缓存层面里来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。

解决方式二

	 		加分布式锁
	 			分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,
	 			其他线程没有获得分布式锁的权限,因此只需要等待即可。
	 			这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩
指的是某一个时间段,缓存集中过期失效。(更像Redis宕机)
产生雪崩的原因之一,比如淘宝双十一抢购,这波商品时间比较集中的放入 了缓存,假如缓存一个小时,那么到了凌晨一点的时候,这波商品缓存就都过期了,而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰,于是所有的请求都会到达存储层,存储层的调用量会暴涨,造成存储层也会挂掉的情况。
在这里插入图片描述

其实集中过期,倒不是非常致命的,比较致命的缓存雪崩,是缓存服务器某个节点宕机或者断网,因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已,而缓存服务节点宕机,对数据服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。有些情况下 保证服务高可用,会做一些措施,比如双十一 停掉一些服务,像退款服务。
解决方案一

  	redis高可用
  		既然redis有可能挂掉,那我多增设几台redis,这样 一台挂掉之后其他的还可以继续工作,
  		其实就是集群搭建(易地过活)。

解决方式二

	限流降级
		在缓存失效后,通过加锁或者队列控制读数据库写缓存的线程数量,
		比如对某个key只允许 一个线程查询数据和写缓存,其他线程等待。

解决方式三

	数据加热
		数据加热的含义就是在正式部署之前,先把可能的数据预先访问一遍,
		这样部分可能大量访问的数据就会加载到缓存中,
		在即将发生大并发访问前手动触发加载缓存不同的key,
		设置不同的过期时间,让缓存失效的时间尽量均匀。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值