- Redis是什么?为什么选择Redis?
- 与Redis类似的系统有哪些?挑选一个你比较熟悉的简单讲解一下?
- Redis的五大数据类型应用场景都是什么?
- 以上讲述了五大类型,Java里完全也可以实现,为什么非要用Redis来替代呢?
- 一直往Redis里加数据,不会溢出吗?它又是怎么删除的呢?
- Redis与数据库作交互,例如MySQL,它有事务,Redis有事务吗?跟MySQL事务有什么区别?
- 提到Redis很多文章会提压缩表与跳跃表,这两个又是什么东东?
- 讲述一下Redis的内存淘汰机制?
- 内存打满或者电脑瘫痪如何保证数据的有效性?
- 应用场景中你遇到过哪些问题呢? 这些你又是怎么解决的?
- Redis集群有哪几种模式?可以简单介绍一下吗?
- Redis如何解决并发的?
- Redis用在分布式的话又会遇到哪些问题呢?
TIP: 通过这篇文章你能学习到这些知识!想了解的继续深入,不想了解的赶紧离开,我不想浪费你们的学习时间。找准自己的定位与目标,坚持下去,并且一定要高效。我跟你们一样非常抵制垃圾文章,雷同文章,胡说八道的文章。
很多人会问,学底层多浪费时间,搞好实现功能不就好了吗?
可以这样想一下到了一定的工作年限技术的广度深度都有一定的造诣了,你写代码就这样了没办法优化了,机器配置也是最好的了,那还能优化啥? 底层,我们都知道所有语言到最后要运行都是变成机器语言的,最后归根究底都是要去跟机器交互的,那计算机的底层是不是最后还是要关注的东西了!
Redis
Redis是什么?
Redis相当于是一个内存数据库,说到数据库,传统的数据库都是存在硬盘中,redis的特点是存在内存中,所以读写速度非常的快,因此redis主要用于缓存业务。redis支持事务,持久化以及多种集群方案。
Tip:redis不负责编码工作,put进行什么类型的值,get出去的时候也就是什么类型的值。之间是有一个二进制安全的
为什么选择Redis?
我们要根据它的特点来论述。高并发,高性能!
我们先从高性能简单介绍一下,因为是在内存中操作所以它的读写能力非常的快,它可以比其他在磁盘上操作数据库有更高的速度与性能。
高并发主要体现在直接操作内存比操作数据库的磁盘有着更高的效率与性能,所以它在接受请求的时候往往可以承受更高的并发量。从磁盘读写数据都是有磁盘IO的,而内存的话就不需要这一点!
Memcached是什么?区别在哪?
memcached是一套分布式的快取系统,跟redis类似,和redis的区别如下。这里不过多介绍了,今天的主题是redis。
区别
- redis支持五大数据类型,Memcached支持的数据类型极少一定程度上根本完成不了业务需求
- redis支持事务
- memcached没有原生的集群模式,需要用户单独编写。redis是支持原生cluster模式。
- Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。
应用场景
Redis的五大数据类型应用场景都是什么?
hash:因为它的映射关系的特性,特别适合存一些存储对象。比如用户详情,商品详情,订单详情等。
string:是简单的key-value结构,value不仅可以是string类型也可以是数字,比如生活中常见的关注数,粉丝数等等。
list:是一个链表,有序的可重复存储。它的应用场景非常的多,比如关注列表,分析列表,消息列表,高性能分页(下拉数据一直不断刷新的那种)等。这里需要着重强调一下list有单链表以及双链表。这里的redis list实现的是双向链表,所以可以支持反向查询和遍历。
set:无序的,不支持重复存储。主要通过交并差集操作实现一些类似于微博的共同好友,共同粉丝,随机事件,抽奖,选名等功能。
sorted set:比set增加了一个权重阈值。也就是参照的意思,第一新增了score分值,rank排名。主要用于一些直播系统中的实时排名信息。包括在线用户列表,礼物排行列表,弹幕消息列表等功能。
Java里完全也可以实现,为什么非要用Redis来替代呢?
轻量级!
数据放入的是Redis不在本地CPU,所以不会影响当前的程序。如果放在本地数据量到了一定级别的时候有可能数据量已经消耗完了CPU的内存,会超过本地CPU溢出,所以分离了占用的位置。
过期策略
Redis过期是什么
redis可以自己手动设置一些值的过期时间,在我们set一个值的时候都可以规定这个值什么时候过期,这点的好处是极大的提高了数据的可用性。比如我们的短信验证码,token登录信息等功能
Redis的数据是按照什么删除的?
redis数据的删除主要分两种。第一种是定期删除,第二种是惰性删除。
顾名思义。
定期删除就是每隔一段时间定期遍历数据采用随机删除的思想(如果不采用随机删除的话那么大的数据量是非常影响性能的)。这样的话极大的影响了CPU的性能。
惰性删除是指在定期删除的基础上因为采用随机删除的关系所以会导致一部分数据经过定期删除后依然没有被删除,所以就有了惰性删除。假如你的过期key通过定期删除没有被删除,那么惰性删除要做的就是,客户端如果再一次访问这个过期的key,或者系统去查一下这个key才会被redis删除。这就是所谓的惰性删除。
如果定期删除漏掉了很多过期 key,然后你也没及时去查, 也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题 呢?redis 内存淘汰机制展开了。
内存淘汰机制
Redis的数据淘汰策略(暂时简单介绍吧后续会出一份Redis底层实现的文章)
- volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的).
- allkeys-random:从数据集中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。(这个应该没人使用吧!)
事务
概念
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
Redis事务没有隔离级别的概念
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到
Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行
Redis事务的三个阶段
- 开始事务
- 命令入队
- 执行事务
Redis事务命令操作(本篇文章介绍大概的知识点,比如常用命令自己搜watch,exec命令找案例,不过多介绍了)
- watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )
- multi : 标记一个事务块的开始( queued )
- exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 )
- discard : 取消事务,放弃事务块中的所有命令
- unwatch : 取消watch对所有key的监控
压缩表与跳跃表
压缩表与跳跃表之间的关系就是 单个元素过于庞大时,转换为跳跃表。简单来说,也就是牺牲空间换速度的一种方案。任何一种便捷的技术都会有弊端的!
持久性
概念
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。Redis 提供了两种持久化方式:RDB(默认) 和AOF
RDB
RDB也就是快照,快照的作用就是在Redis崩掉的时候起到了恢复的作用。快照是保存当前某一个时间点范围的数据,每个小时保存一次。 例如9点整开启一次快照,在9点59的时候系统崩掉了。那么就损失了59分钟的数据。这种的弊端是性能挺好,就是丢失的数据比较多一些。
AOF
AOF是追加文件的方式。AOF实时性更好。这种方式默认是不开启的。AOF是实时的,所以就算丢失数据顶多只会丢失一秒的数据
持久性的方案
首先利用RDB快照进行整点恢复。然后再利用AOF进行剩余部分的实时恢复。RDB的恢复速度非常快,AOF恢复速度虽然慢一点但是恢复的数据量不大因为最多只会恢复一个小时内的数据量。
还是举以上的例子:9点钟利用RDB进行了一次快照保存数据,9点59分的时候来了一个原子弹把服务器炸成灰了。持久化的方案就是利用RDB快照恢复了大部分的数据,但是丢失了59分钟的数据,因为RDB是一个小时执行一次快照所以RDB解决不了。利用AOF恢复剩下的59分的数据不需要利用AOF恢复整体的数据了。
常见问题
- 缓存穿透
- 缓存雪崩
- 缓存预热
- 缓存降级
缓存穿透以及解决方案
缓存穿透:一般访问缓存的流程,如果缓存中存在查询的商品数据,那么直接返回。 如果缓存中不存在商品数据,就要访问数据库。由于不恰当的业务功能实现,或者外部恶意攻击不断地请求某些不存在的数据内存,由于缓存中没有保存该数据,导致所有的请求都会落到数据库上,对数据库可能带来一定的压力,甚至崩溃。解决方案:
针对缓存穿透的情况, 简单的对策就是将不存在的数据访问结果, 也存储到缓存中,避免缓存访问的穿透。最终不存在商品数据的访问结果也缓存下来。有效的避免缓存穿透的风险
缓存雪崩以及解决方案
缓存雪崩:
当缓存重启或者大量的缓存在某一时间段失效,这样就导致大批流量直接访问数据库,对 DB 造成压力, 从而引起 DB 故障,系统崩溃。
举例来说, 我们在准备一项抢购的促销运营活动,活动期间将带来大量的商品信息、库存等相关信息的查询。 为了避免商品数据库的压力,将商品数据放入缓存中存储。 不巧的是,抢购活动期间,大量的热门商品缓存同时失效过期了,导致很大的查询流量落到了数据库之上。对于数据库来说造成很大的压力。
解决方案:
- 将商品根据品类热度分类, 购买比较多的类目商品缓存周期长一些, 购买相对冷门的类目
商品,缓存周期短一些; 2. 在设置商品具体的缓存生效时间的时候, 加上一个随机的区间因子, 比如说 5~10 分钟 之间来随意选择失效时间; 3. 提前预估 DB 能力, 如果缓存挂掉,数据库仍可以在一定程度上抗住流量的压力
这三个策略能够有效的避免短时间内,大批量的缓存失效的问题
缓存预热以及解决方案
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据。如果不进行预热, 那么 Redis 初识状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。如图所示:解决方案:
- 数据量不大的时候,工程启动的时候进行加载缓存动作;
- 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
- 数据量太大的时候,优先保证热点数据进行提前加载到缓存
缓存降级以及解决方案
降级的情况,就是缓存失效或者缓存服务挂掉的情况下,我们也不去访问数据库。我们直接访问内存部分数据缓存或者直接返回默认数据。举例来说:对于应用的首页,一般是访问量非常大的地方,首页里面往往包含了部分推荐商品的展示信息。这些推荐商品都会放到缓存中进行存储,同时我们为了避免缓存的异常情况,对热点商品数据也存储到了内存中。同时内存中还保留了一些默认的商品信息。
降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。
集群
作为缓存数据库,肯定要考虑缓存服务稳定性相关的保障机制
持久化机制就是保证系统崩溃的一个机制
内存淘汰机制就是保证系统内数据是否一直有效的一个机制
那么如果单机数据直接挂掉,电脑炸成渣了怎么办?怎么保证数据的备份呢? 单点故障!延伸了集群!
Redis集群模式一共有三种
- 主从模式
- 哨兵模式
- cluster模式
解释一下:
单机模式下如果数据被炸毁,也就是出现了单点故障,可以利用主从复制方式,进行多台redis数据的全量同步。主从复制集群主要有三种分别是强一致性,弱一致性,最终一致性。强一致性可以保证redis1与redis2数据保证同步,但是真实的场景中往往影响性能。redis3与redis4无法保证数据必须一致,不出意外的话这种方式既可以解决主从复制也可以解决性能的问题所以这种方式是默认的。redis5与redis6利用黑盒最终一致性。
主从复制
顾名思义,Redis服务器分为两类也就是主服务器(Master)与从数据库(Slave)。这是为了避免单点故障的数据丢失引出的一个方案。通常的做法就是一台服务器的数据复制多个副本以部署在不同的服务器上。即使有一台服务器崩掉了也不至于瘫痪整个服务,另外几台服务器依然可以为系统提供服务。
Redis 提供了复制功能。可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
优点
- 一个主,可以有多个从,并以非阻塞的方式完成数据同步;
- 从服务器提供读服务,分散主服务的压力,实现读写分离;
- 从服务器之前可以彼此连接和同步请求,减少主服务同步压力。
缺点
- 不具备容错和恢复功能,主服务存在单点风险;
- Redis 的主从复制采用全量复制,需要服务器有足够的空余内存;
- 主从模式较难支持在线扩容
哨兵模式
Redis 提供的 sentinel(哨兵)机制,通过 sentinel (哨兵)模式启动redis后,自动监控 主Master/从Slave的运行状态,基本原理是:心跳机制 + 投票裁决。
简单来说,哨兵的作用就是监控 Redis 系统的运行状况。它的功能包括以下两个:
- 监控主数据库和从数据库是否正常运行;
- 主数据库出现故障时自动将从数据库转换为主数据库。
哨兵模式主要有下面几个内容:
- 监控( Monitoring ):Sentinel 会定期检查主从服务器是否处于正常工作状态。
- 提醒( Notification ):当被监控的某个 Redis 服务器出现异常时,Sentinel 可以通过API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover):当一个主服务器不能正常工作时,Sentinel 会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
Redis Sentinel 是一个分布式系统,你可以在一个架构中运行多个 Sentinel (哨兵)进程( progress )
优点
- 哨兵模式主从可以切换,具备基本的故障转移能力;
- 哨兵模式具备主从模式的所有优点。
缺点
- 哨兵模式也很难支持在线扩容操作;
- 集群的配置信息管理比较复杂
cluster模式
Redis Cluster 是一种服务器 Sharding 技术,采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
Cluster 集群结构特点:
- Redis Cluster 所有的物理节点都映射到 [ 0-16383 ] slot 上(不一定均匀分布),Cluster
负责维护节点、桶、值之间的关系; 2. 在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384 的值,从之前 划分的 16384 个桶中选择一个; 3. 所有的 Redis 节点彼此互联(PING-PONG 机制),内部使用二进制协议优化传输效率; 4. 超过半数的节点检测到某个节点失效时则判定该节点失效; 5. 使用端与 Redis 节点链接,不需要中间 proxy 层,直接可以操作,使用端不需要连接集群 所有节点,连接集群中任何一个可用节点即可。
优点
- 无中心架构,节点间数据共享,可动态调整数据分布;
- 节点可动态添加删除,扩展性比较灵活;
- 部分节点异常,不影响整体集群的可用性。
缺点
- 集群实现比较复杂;
- 批量操作指令( mget、mset 等)支持有限;
- 事务操作支持有限
如何解决并发?
多实例的方式
分布式的话又会遇到哪些问题呢?
redis分布式并发竞争key的问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!
推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。在实践中,当然是从以可靠性为主。所以首推Zookeeper。参考:www.jianshu.com/p/8bddd381d…
如何保证缓存与数据库双写时的数据一致性?
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如 何解决一致性问题?
一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的 情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致 的情况串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。参考zhuanlan.zhihu.com/p/63428500