Redis

Redis的线程模型

Redis6.X之前是纯单线程,Redis6.X之后引入多线程,Redis中的多线程用来处理网络请求,因为单线程不会出现线程安全问题所以,读写还是单线程操作。

为什么读写单线程但速度还很快?

1.基于内存操作:Redis 的所有数据都在内存中,因此所有的运算都是内存级别的 不需要磁盘IO,所以它的性能比较高。
2.数据结构简单:Redis 的数据结构是为自身专门量身打造的哈希表,而这些数据结构的查找和操作的时间复杂度都是 O(1).
    全局哈希表
hash 可以在 O(1)的时间内计算出 hash 值并且找到对应的 entry 位置,entry里面是一个一个 key 指针和 value 指针,其实还有其他信息。这也是 redis 之所以性能高的原因之一.       
3.多路复用和非阻塞 I/O:Redis 使用 I/O 多路复用功能来监听多个 socket 连接客户端,这样就可以使用一个线程来处理多个情况,从而减少线程切换带来的开销,同时也避免了 I/O 阻塞操作,从而大大地提高了 Redis 的性能.
4.避免线程切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的开销,而且单线程不会导致死锁的问题发生.
 Redis持久化

因为redis数据,平常存储在内存中,一旦机器故障可能数据就会丢失redis提供数据持久化机制. 持久化的作用主要是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。

实现持久化的主要方式有两种,RDB 方式和AOF方式

RDB方式
RDB持久化的触发分为手动触发和自动触发两种,生成RDB文件,将数据存储在其中。重新启动redis服务时,会把dump.rdb文件内容还原回来.

 手动触发
save命令和bgsave命令都可以生成RDB文件。
save命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在Redis服务器阻塞期间,服务器不能处理任何命令请求。而bgsave命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求。bgsave命令执行过程中,只有创建子进程时会阻塞服务器,而对于save命令,整个过程都会阻塞服务器,因此save已基本被废弃,线上环境要杜绝save的使用。

自动触发
配置中的save m n

自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。例如,查看redis的默认配置文件(Linux下为redis根目录下的redis.conf),可以看到如下配置信息:

 其中save 900 1的含义是:当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave;save 300 10和save 60 10000同理。当三个save条件满足任意一个时,都会引起bgsave的调用。

优点:

恢复数据幅度快。

缺点:

每次bgsave时都会fork子进程有时间成本。

因为每次存储数据都有时间间隔,当Redis突然宕机,会导致最近的数据丢失。

 AOF方式

RDB持久化是将进程数据写入文件,而AOF持久化,则是将Redis执行的每次写命令记录到单独的日志文件中;当Redis重启时再次执行AOF文件中的命令来恢复数据。

                                                            

 开启AOF

Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在配置文件中配置:appendonly yes

配置改为yes后,AOF回记录每一次的写操作。AOF是文件形式存储的,文件有可能会出现问题,当AOF文件出错redis将无法正常启动,对于这个问题Redis提供了一个修复工具 redis-check-aof可对ADF文件进行修复,但会丢失一些Key值,这种丢失是可以接受的。

AOF 同步机制
appendfsync always  #每次的写操作都会同步到AOF文件中。消耗性能
appendfsync everysec #每秒同步,可能会丢失这 1s 的数据(默认)

appendfsync no :完全依赖操作系统来进行日志同步,Redis不主动进行同步。这种方式性能最高,但在系统崩溃时可能会导致数据丢失。

AOF重写机制

随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取写入操作。

文件重写的触发

文件重写的触发,分为手动触发和自动触发:

手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。

自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。

 auto-aof-rewrite-min-size 64mb:当AOF文件体积达到64mb时会触发重写。
auto-aof-rewrite-percentage 100:当AOF文件体积在上次重写之后的基础上体积增加了100%时触发重写。

优点:

安全性更高,配置得当最多丢失一秒钟的数据,

AOF太大时会自动进行重写。

缺点:

恢复数据慢。

相同数量来说,AOF文件体积大于RDB。

RDB-AOF混合模式

在执行写操作时,先持久化的数据写入AOF文件的开头,之后的写操作命令拼接到后方。可以将其理解为,在 aof 持久化的基础上,进行的一些优化,本身也依然是基于 aof 持久化

Redis事务

Redis 事务本质是一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行.所有的命令在事务中,并没有直接被执行.只有发起执行 exec 命令的时候才会执行.
事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。但是redis中事务非常弱 在出现基本的命令错误时会出现回滚,但是出现运行时错误时不会出现回滚,依旧会把没有出错的命令进行执行。
redis 的事务操作:
开启事务(multi)
执行命令, 命令入队(.....)
执行前可以放弃事务(discard)
执行事务(exec)
Redis主从复制
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
使用一个 Redis 实例作为主机,其余的作为备份机。主机和备份机的数据完全一致,主机支持数据的写入和读取等各项操作,而从机则只支持与主机数据的同步和读取。也就是说,客户端可以将数据写入到主机,由主机自动将数据的写入操作同步到从机。主从模式很好的解决了数据备份问题,并且由于主从服务数据几乎是一致的,因而可以将写入数据的命令发送给主机执行,而读取数据的命令发送给不同的从机执行,从而达到读写分离的目的。

 主从复制的作用主要包括:

1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
Redis哨兵机制 
哨兵模式是一种特殊的模式,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例

 哨兵的主要工作任务:

(1)监控:哨兵会不断地检查你的Master和Slave是否运作正常。

(2)提醒:当被监控的某个Redis节点出现问题时,哨兵可以通过 API 向管理员或者其他应用程序发送通知。

(3)自动故障迁移:当一个Master不能正常工作时,哨兵会进行自动故障迁移操作,将失效Master的其中一个Slave升级为新的Master,并让失效Master的其他Slave改为复制新的Master;当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用新Master代替失效Master。

 RedisKey的过期策略

1.立即删除

当Key的时间到了之后会立即删除Key,好处是不浪费内存空间,缺点是如果大量的Key同时到期会增加的CPU负荷。

2.惰性删除

Key到期后,不会立即删除,而是等待下次要使用该Key时会检测到Key已过期,此时才会删除Key,惰性删除的缺点就是浪费内存空间。

3.定时删除

每隔一段时间删除过期的健

redis 使用的过期键值删除策略是:惰性删除加上定期删除,两者配合使用。
缓存穿透、缓存击穿、缓存雪崩
缓存处理流程
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
           
缓存穿透
key 对应的数据在数据库中并不存在,每次针对此 key 的请求从缓存获取不到,请求都会到数据库,从而可能压垮数据库。比如用一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
           
解决办法:
1.将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。这种情况我们一般会将空对象设置一个较短的过期时间.
2.对参数进行校验,不合法参数进行拦截
缓存击穿
某个 key 对应的数据库中存在,但在 redis 中的某个时间节点过期了,此时若有大量并发请求过来,这些请求发现缓存过期,都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。
            
解决办法:
1.热点数据设置永不过期
2.加锁:上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后将数据放到 redis 缓存起来。后面的线程进来发现已经有缓存了,就直接走缓存.
缓存雪崩
缓存雪崩是指,在高并发情况下,大量的缓存失效,或者缓存层出现故障。于是所有的请求都会达到数据库,数据库的调用量会暴增,造成数据库也会挂掉的情况。
      
解决方法:
1.随机设置 key 失效时间,避免大量 key 集体失效。 setRedis(Key,value,time + Math.random() * 10000);
2.若是集群部署,可将热点数据均匀分布在不同的 Redis 库中也能够避免 key全部失效问题
3.不设置过期时间
4.跑定时任务,在缓存失效前刷进新的缓存
总结
雪崩是大面积的key缓存失效;穿透是redis里不存在这个缓存key;击穿是redis某一个热点 key 突然失效,最终的受害者都是数据库。对于“Redis 宕机,请求全部走数据库”这种情况,我们可以有以下的思路:
事发前: 实现 Redis 的高可用(主从架构+Sentinel(哨兵),尽量避免 Redis挂掉这种情况发生。
事发中: 万一 Redis 真的挂了,我们可以设置本地缓存(ehcache)+限流,尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)
事发后: redis 持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

redis实现分布式锁

       分布式锁,即分布式系统中的锁。在单体应用中我们通过 java 中的锁解决多线程访问共享资源的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程.+
redis中有一个setnx方法,该方法在每次存储时,会检测key值是否已经存在,若存在则不会存储,利用这一原理可以实现分布式锁。在线程到来时调用 redisTemplate.opsForValue().setIfAbsent(); 
方法(该方法的底层实现为setnx 方法 )判断所要存储的key是否存在,若不存在则存储key并获取到锁,若存在则等候锁的释放。以下几个例子是分布式锁一步一步优化的过程,便于更好的理解其原理

第一个版本

但是,以上实现存在一个很大的问题,当客户端 1 拿到锁后,如果发生下面的场景就会造成死锁

1. 程序处理业务逻辑异常,没及时释放锁

2. 进程挂了,没机会释放锁

以上情况会导致已经获得锁的客户端一直占用锁,其他客户端永远无法获取到锁。

第二版本 对以上代码进行改进,在 finally 中释放锁,以及设置键失效时间.

 

但该写法依旧会有问题,假设有两个线程,当第一个线程执行时间大于健的失效时间时,当执行到一半时锁释放,第二个线程获取到锁并执行,这时第一个线程执行完毕,并且执行了删除key这个步骤,但是他删除的是第二个线程所存储的的key值,这样会导致锁失效。

第三版本 我们可以使用 UUID.randomUUID().tostring();方法生成版本号,在存储健时加上一个版本号,在最后删除时判断该线程的版本号是否一致,这样以来执行时间超过key失效时间的线程就不会出现误删情况

 redis中提供了一个组件,名叫Redission可以简单的实现分布式锁

redission的实现

导入依赖
 <dependency>
 <groupId>org.redisson</groupId>
 <artifactId>redisson</artifactId>
 <version>3.6.5</version>
 </dependency>
创建 Redisson 对象
 @Bean
public Redisson getRedisson(){
 Config config = new Config();
 config.useSingleServer().setAddress("redis://120.48.37.232:6379").setDatabase(0);
 return (Redisson)Redisson.create(config);
 }

在需要使用该对象的地方使用@Autowired标签注入即可

使用 Redisson 实现加锁,释放锁.

 Redission执行过程如下图

        两个线程同时来到,但只有一个线程可以获取到锁,获取失败的锁会不停的尝试继续获取锁直到超时。获取成功的线程 会用UUID和线程id作为KEY存储到redis中,在此期间还会有一个watch dog机制,每过几秒 查看该线程是否还持有锁,如果有会,加长key的存活时间,直到任务执行完毕,删除KEY释放锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值