Redis使用

Redis介绍

NoSQL,非关系型数据库,有4种分类:键值对、列存储,如HBaseE;文档型,如mongoDB;图形。NoSQL数据库在性能上优于关系型数据库,但在安全上劣于关系型数据库,之所以在性能上高于关系型数据库,是因为它存在内存中,也因为如此,所以宕机、停电会导致数据丢失,才在安全上低于传统数据库,所以在实际开发中一个项目中nosql和关系型数据库会一起使用,达到性能和安全性的双保证。

Redis实现的是NoSQL系列中键值对格式,可以操作string、list、set、sortset、散列,可以看到操作的都是字符串。

Redis的事务和持久化

redis的事务与数据库事务不一样,redis的事务只是保证了多条指令的执行顺序,也就是说:多条指令执行的时候,如果存在失败的指令,不会影响其他成功的指令,不会让其他指令回滚

Redis提供了两种持久化方式

  1. RDB

    RDB是在设置的时间范围内满足某种条件进行持久化,比如:save 300 10,意思是在300s内,进行了10次数据库修改。在满足触发条件之后,对数据进行持久化,持久化完毕之后,替换原来的RDB,并且删除。所以说RDB是某个时间点全量的数据集,数据并不完整,如果发生事故,可能会丢失大部分的数据,但是恢复数据时加载快。另外,redis是单线程的,如果主线程去进行持久化,那这段时间内都将无法进行指令操作,所以,redis提供了bgsave,调用了linux的fork(),使用新的线程执行,虽然在进行fork操作时也会阻塞主线程,但是相比主线程去进行复制,这时间可以忽略。可以配置多个,如:

	save 300 10
	save 900 5
  1. AOF

    AOF是记录对数据库的每一次写入操作的指令,进行持久化。AOF提供了三种策略:

	1.enerysec每秒写一次:如果打开AOF功能,执行写入(add、update、del)指令时,redis会以协议的格式将指令追加到服务器的aof_buf缓冲区。当
	满足条件是,将aof_buf中的缓存写入并保存到AOF文件中(默认),主线程执行写、子线程进行保存。
	2.always每次修改操作写一次:每执行一个指令保存一次,由主线程进行,所以想能最低,但也是数据最全的。
	3.no不操作:不保存,虽然说不保存,只是他的触发条件不同而已,它是在aof关闭或者redis关闭时,才会进行保存,由主线程进行

	推荐enerysec并且也是默认的,

AOF文件的重写:正因为AOF方式的特性,所以AOF文件会特别大,因此Redis支持一种策略,对AOF进行重建,执行bgrewriteaof命令,redis会生成一个aof文件,该文件中的命令是重建当前数据集所需要的最小的命令。redis2.2需要手动执行,2.4的时候可以配置自动执行,也就是触发条件:

	auto-aof-rewrite-min-size 70mb
	auto-aof-rewrite-percentage 80
	在redis的配置文件中进行配置,两项配置组合使用,是当体积大于多少(参数:68mb)的时候并且体积比上一次重写之久的体积大了多少倍时(参数:
	80,为整数,若为100,则1倍),进行重写aof
  1. RDB和AOF对失效键的处理

    数据被持久化了,那设置了超时时间的key,在未超时时也被持久化了,那如果进行数据恢复的时候,那岂不是很多过期key都被加载了。所以,RDB和AOF有对失效键的处理。

	1.RDB的处理
	 (1)在持久化key之前,会检查是否过期,过期的key不会进入。
	 (2)从RDB中恢复数据的时候,也会对key进行检查,过期就不导入
	2.AOF的处理,我们知道AOF是发生操作才会被加入进去,所以
	 (1)在持久化的时候,如果AOF键过期了,但没有发生删除操作呢,是不会进AOF的
	 (2)如果AOF过期,并且发生了删除操作,则会发生一条del命令,加入到aof文件尾,那么恢复数据的时候也同样会直接改del操作。
	可见AOF只记录指令的操作

Redis的删除

有定时删除、惰性删除、定期删除三种方案

  1. 定时删除

    在设置过期key的时候,为该key创建一个定时器,在key过期时间来临是,进行删除。虽然能保证内存被尽快释放,但是如果过期key很多的话,会严重消耗内存(没人用)

  2. 惰性删除

    不主动删除key,即使过期,但在获取key的时候去检查是否过期,若过期,则删除,然后返回null。

  3. 定期删除
    就是字面意思,隔一段时间进行一次删除过期key操作,有两种实现方式,:

	1.设置刷新频率在redis.conf中设置hz选项,参数:整型,表示1s刷新几次。
	2.配置maxmemory,当已用内存超过这个是,就触发清理策略,Redis提供了6种淘汰策略,分为三类
		(1)从设置过期时间的数据集中,没有设置过期时间的,则不在淘汰数据集中
		 volatile-lru(最少使用的)、volatile-ttl(将要过期的)、volatile-random(任意选择)
		(2)从所有的数据集中随机
		 allkeys-lru(最小使用的)、allkeys-random(随机),因为是所有的key,所以就不会再有过期的选项了
		(3)不清理
		 no-enviction

Redis实现分布式锁及集群下锁失效问题

参考链接

你应该知道Redis中提供了一个属性:setnx,这个方法表示的是如果没有就set,返回boolean类型,通过这个属性实现分布式锁:
但是写一个分布式锁很容易,但是怎么写好一个分布式锁,这是没有可比性的。

	setnx key(相当于上锁)
	 逻辑
	delete key(解锁)
  1. 如果在发生逻辑过程中,发生了异常了,delete将不会被执行,所以delete经常写在finally
  2. 如果执行逻辑的时候或者在删除锁成功之前宕机了呢,那么这个锁,将永远不会被释放,所以setnx成功后,加上超时时间设置
  3. 同样的思考,你必须将超时设置与上锁纳为一个原子操作,如果不是原子操作,在setnx成功,加超时设置之前仍然有可能宕机,所以在使用的redisTemplate中有个setIfabsent方法将超时设置与上锁纳为了一个原子操作。
  4. 上述还有一个缺点,就是容易删除其他线程的锁,怎么回事呢:A线程上锁,超时时间到则自动释放锁,B线程获取到锁,但是A线程此时事务还未执行完成,然后A线程事务执行完成后,进行删除锁操作,但是此时的锁是B线程的。
    所以在删除锁时,加个版本号进行比对。

使用redisson包

更简单的方式:使用redission包,它把我们上面的顾虑都已经给封装了。使用的时候,就如同reentrantlock一样,就两步
redission.lock
逻辑
redission.unlock

使用redlock

为了解决上面提到的redis集群中的分布式锁问题,redis的作者antirez的提出了red lock的概念,假设集群中所有的n个master节点完全独立,并且没有主从同步,此时对所有的节点都去setnx,并且设置一个请求过期时间re和锁的过期时间le,同时re必须小于le(可以理解,不然请求3秒才拿到锁,而锁的过期时间只有1秒也太蠢了),此时如果有n / 2 + 1个节点成功拿到锁,此次分布式锁就算申请成功
这样就实现了一个分布式锁,还有一个点,不知道你有没有想到,就是当redis集群部署的时候,如果master主机宕机了,并且此时,还未来得及写入从节点,那么也该锁也就失效了:
目前有两个方案:redlock或者使用zk来解决集群部署分布式锁失效问题,如果要求性能更好一点的,就使用zk。

实现消息幂等性及redis实现消息幂等性

幂等,在java程序中,就是去重,避免重复消费,比如,客户端因为延迟网络延时的原因,用户在点击提交的时候,连续点击了多次,但应该只消费一笔请求,如果不做幂等性设置,那么肯定会出现重复消费的问题。所以,这个健康的系统,应该具备幂等性设计,而幂等设置,有两种方式:

  1. 通过数据库唯一索引

    消息入库,设置消息唯一性,在入库时,再设置数据库的唯一索引,该方式比较简单

  2. 通过redis的setnx实现幂等

    其实与数据库唯一索引方案一样,只不过是中间加了缓存层,通过redis的setnx实现,同样是设置消息的唯一性,进行setnx,如果失败,则表示已存在正在消费或消费完成的消息。然后进行业务消费时,将消息进行持久化,如果系统仍处于高峰时间,若此时不想影响数据库,也可以在业务截止后或业务低峰时段,进行消息的持久化。这样可以避免数据库的无效查询,可以起到保护应用的作用。

    使用redis实现目的,那么肯定redis的特性肯定满足你的要求,比如redis的缓存时间是有时间限制的,也就是说redis的key的生命周期完全可以支撑你的业务需求幂等性的有效期。

Redis与数据库中的数据保持一致

  1. 如果必须要求强一致性:则将写入数据库与写入redis,定义为一个事务,若其一失败,则都失败。

    @Transactional只会回滚MySQL异常的情况,而且redis本身也没有回滚的功能,采用这种方式时,先操作数据库,然后再操作redis,当redis操作失败的时候,进行手工回滚:

	TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 配合@Transactional注解即可解决问题

或者对这种要求的数据,首先直接操作数据库

  1. 采用延时双删策略
    写入操作过来后,先删除redis中的数据,然后操作对数据库进行操作,操作完之后,再删一次redis,为什么再删一次呢:在你第一次删除key到操作数据库之间,完全有可能有其他线程过来读取数据。所以在,操作完数据库再进行一次redis删除操作。
    这个方案适用于数据弱一致性的要求,因为会存在第二次删除之前,应用服务器宕机,那么就没法立即删除了,解决这一问题,只能是设置过期时间。所以,更优的一个方案是下面第3个方法

  2. 使用数据库的binlog日志
    读取mysql的binlog日志进行分析 ,然后利用消息队列,推送更新各台的redis缓存数据。阿里的canal框架就是基于这种方案做的

    参考博客

如何保证Redis中的数据都是热点数据

仔细读一下这个句话,“如何保证都是热点数据”,并不是“总是热点数据”,其实意思就是:在某个时间redis中存放的数据。因为redis中的数据总是热点数据是不可能的,redis在不断的被操作,怎么可能总是热点数据呢。
使用redis的定期删除配置maxmeory方式着来做。

Redis缓存穿透、雪崩、击穿、预热

  1. redis的穿透
    redis穿透是数据库中一定没有某个值,自然缓存中也不会有,所以每次查询这个值的时候,都要去数据库中查询,这样绕过缓存查询数据库称为穿透,而如果这个值被一直访问,那你这个系统就可以什么都不用干了。谁会这么无聊呢,—黑客。

    请仔细想一想,穿透的含义是绕过缓存请求数据库,那么解决的方式,也就是不让他绕过缓存不就行了嘛。

    解决:

	1.最简单粗暴的就是,也将这个结果缓存起来。但是超时设置的时间不会很长
	2.布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查
	询压力。

 布隆过滤器其实是布隆发明的,起到的过滤作用,所以叫布隆过滤器。一个bit数组,采用0、1表示,举例:一个key,经过4轮hash,每一轮hash都会在数组中
 找个位置,并标记为1。那么最终在数组中会有4个值都为1的位置.不用担心空间问题,因为只用0、1表示,1byte=8bit,如果你有1E个key,4轮hash规则,那
 么bit数组为4E个bit。也就是MB兆级别,所以不用担心空间问题
  1. redis缓存雪崩

    redis的缓存雪崩是在某一时刻,大量的key被过期,这就导致要去查数据库,一下子来了这么多请求,对性能造成巨大压力,影响吞吐量,严重的可能会被宕机,甚至出现连锁反应,导致整个生产环境崩溃。

    这个出现的问题是什么呢,是大量的key在某一时刻同时过期,造成大量请求去请求数据库,并且大批量查库,那么我们从两方面入手。

    解决:

	1.分散过期时间,随机设置每个key的过期时间
	2.缓存标记,其实就是两个key配合使用,可是感觉这个方案不合适,如果数据一致不被访问,那照样会出现大量key过期。(请知悉)
	 (1)缓存标记:记录缓存数据是否过期,如果过期会触发通知另外的线程在后台去更新实际key的缓存;
	 (2)缓存数据:它的过期时间比缓存标记的时间延长1倍,例:标记缓存时间30分钟,数据缓存设置为60分钟。 这样,当缓存标记key过期后,实
	 际缓存还能把旧数据返回给调用端,直到另外的线程在后台更新完成后,才会返回新缓存。
	3.对数据库方面,通过程序来限制访问数据库:加锁排队,但是这个在高并发场景下没有人用,因为并没有解决吞吐量的问题。

缓存标记:这里在缓存标记为空的时候,直接sql查库,更合适的做法应该是先判断cachevalue是否为空,如果cachevalue再为空的话,再去查sql

在这里插入图片描述

加锁排队(作为了解):

在这里插入图片描述

  1. redis缓存击穿

    redis缓存击穿是某个key过期了,但是这个key在某个时间点是个热点数据,会被大量的访问,而在大量访问之前,key过期了,不在缓存中了。

    那么我们就不让它失效,或者失效后,不让它大量访问数据库。

    解决:

	1.使用setnx互斥锁,就如果上面的加锁排队写法一样,更类似于单例模式的双重检查。
	2.永远不过期,永远不过期是相对来说的,就是相当于对数据过期时间的延长。
	3.或者设置定时任务:也就是根据业务发生的情况,掌握发生高并发的时间,是定时任务提前运行。
	4.资源保护,使用hystrix.

请分清与前两个的不同,一个是大量的key失效,不一定是热点数据,导致的大量请求数据库,而击穿是某个时刻被作为热点的数据失效

  1. 缓存预热

    缓存预热是提前将相关的数据加载到缓存中,避免在新启动的时候,大量的请求访问

    解决:

	1.写个页面,启动成功后,手动操作一下
	2.定时任务,定时刷新数据到缓存中
	3.如果数据量不大的话,可以项目启动的时候加载,不过个人感觉写个页面,手动操作的方式更方便一点。

mq消息的幂等性也可以用redis的setnx实现,实现的时候,还得要考虑主从复制不一致等情况,所以根据工具组件的特性,活学活用,这就是架构设计的真谛把

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值