Redis 基础知识

5 篇文章 0 订阅
4 篇文章 0 订阅

基础

非关系型NoSQL的键值对数据库
Redis 可以存储键和五种不同类型的值之间的映射
键的类型只能为字符串,值支持五种数据类型:string字符串、list列表、set集合、hash散列表、zset有序集合
Redis的数据是存在于内存中的,所以读写速度非常快,因此redis被广泛应用于缓存方向;Redis也经常用来做分布式锁;除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。


redis的优缺点

优点:
1、读写速度非常快
2、支持数据持久化,支持AOF和RDB两种持久化方式;
3、支持事务,redis的所有操作都是原子性的;
4、数据结构丰富:除了支持string类型的value外还支持hash、set、zset、list等数据结构。
5、支持主从复制,主机会自动将数据同步到从机,可以进行读写分离

缺点:
1、数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上
2、Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
3、主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性
4、Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。


为什么用Redis做缓存?

高性能和高并发两方面考虑:

高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
在这里插入图片描述
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
在这里插入图片描述


Redis为什么这么快?

1、完全基于内存,大多数都是纯粹的内存操作,很快;数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);
2、数据结构简单,对数据的操作也简单,Redis中的数据结构就是专门设计的;
3、采用了单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或多线程导致的切换而消耗CPU,避免了考虑锁的问题,不存在加锁释放锁的操作
4、使用IO复用模型,非阻塞IO;


redis是单线程的为什么还要加锁?

redis本身是不会产生并发安全问题的,不需要加锁。这里所谓的加锁是我们为了满足业务在使用的时候,可能会出现并发问题而需要加锁;比如,当一个 redis 服务器同时有多个客户端访问时,每个客户端会有一个线程。客户端访问之间存在竞争。因为存在多客户端并发,所以必须保证操作的原子性。比如银行卡扣款问题,获取余额,判断,扣款,写回就必须构成事务,否则就可能出错。
Redis单线程是指对Redis内的数据操作(读/写)是单线程的,比如超卖问题,有两个请求A和B都想要购买同一件商品,A请求读->B请求读->A请求写->B请求写(对于单个请求读/写是串行的,但是从整体看仍是并发的,所以需要加分布式锁)


redis分布式锁

分布式锁就是:控制分布式系统不同进程共同访问共享资源的一种锁的实现。
如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,来保证一致性;
分布式锁应该具备的特征:
在这里插入图片描述


redis是单线程的为什么IO效率这么高?

1、纯内存操作
2、核心是基于非阻塞的IO多路复用机制
有了非阻塞 IO 意味着线程在读写 IO 时可以不必再阻塞了,读写可以瞬间完成然后线程可以继续干别的事了。
3、单线程反而避免了多线程的频繁上下文切换带来的性能问题;
第一,单线程可以简化数据结构和算法的实现。并发数据结构实现不但困难而且开发测试比较麻
第二,单线程避免了线程切换和竞态产生的消耗,对于服务端开发来说,锁和线程切换通常是性能杀手。


四种分布式session解决方案

参考资料


数据结构类型

Redis主要有五种数据类型:string list set zset hash
重点Redis的应用场景:
计数器
可以对string 进行自增自减运算,从而实现计数器功能;redis这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量;
缓存–string
将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率;
排行榜系统
redis提供了列表和有序集合数据结构,合理的使用这些数据结构可以很方便的构建各种排行榜系统;
分布式锁–string
以使用 Redis 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现
数据共享分布式—string
String 类型,因为 Redis 是分布式的独立服务,可以在多个应用之间共享


Redis 跳表

参考资料

redis数据结构zset实现有序集合,底层使用的数据结构是跳表
跳表是对链表的增强,使用链表的时候,即使元素是有序的时候,如果要查询元素,需要从头一个个查询,是很耗费是时间的。跳表就是为了解决这个问题的,当查询链表的时候,可以跳过一些元素,大大节省查询的时间

如下图所示,比如我们要查找8,先在最上层L2查找,发现在1和9之间;然后去L1层查找,发现在5和9之间;然后去L0查找,发现在7和9之间,然后找到8。

当元素比较多时,使用跳表可以显著减少查找的次数。
在这里插入图片描述


持久化

持久化就是把内存中的数据写到磁盘中去,防止服务器宕机后内存的丢失;

持久化方式一:RDB
Redis DataBase 缩写快照
RDB是Redis默认的持久化方式。按照一定的时间将内存中的数据以快照的形式保存到磁盘中,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期;
在这里插入图片描述

持久化方式二:AOF持久化
AOF持久化即(Append Only File持久化),则是将redis执行的每次写命令记录到单独的日志文件中,当重启redis后会重新将持久化的日志中文件恢复数据;
当两中方式同时开启后,数据恢复redis会优先选择AOF恢复;
在这里插入图片描述

Redis持久化数据和缓存怎么做扩容?
如果Redis被当做缓存使用,使用一致性哈希实现动态扩容缩容
如果Redis被当做一个持久化存储使用,必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。


过期键的删除策略

redis过期键的删除策略:
我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有三种:
定期过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除;
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除;
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。

Redis中同时使用了惰性过期和定期过期两种过期策略;

内存相关

Mysql里有2000W数据,redis中只存20w数据,如何保证redis中的数据都是热点数据
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略;

Redis主要消耗内存这种物理资源;

如何进行内存优化?
可以好好利用Hash,list,sorted set,set等集合类型数据,因为通常情况下很多小的Key-Value可以用更紧凑的方式存放到一起。尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面;


Redis线程模型

Redis是单线程模型
redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler).它的组成结构为四个部分:多个套接字,IO多路复用程序,文件事件分派器,事件处理器;
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型;

单线程模型的实现机制
1、文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
2、当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性;


事务

事务概念:事务是一个单独的隔离操作,事务中的所有命令都会被序列化、按顺序的执行;事务在执行的过程中,不会被其他客户端发送来的命令所打断;
事务是一个原子操作:事务中的命令要么全部被执行,要么全部不执行;

Redis事务:redis事务就是一次性、顺序性、排他性的执行以一个队列中的一系列命令;
Redis会将一个事务中的所有命令序列化,然后按顺序执行;
Redis不支持回滚;

Redis事务中的ACID属性
原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生;
一致性:事务操作前后的数据的完整性必须保持一致;
隔离性:多个事务并发执行时,一个事务的执行不影响其他事务的执行;
持久性:指一个事务一旦被提交,它对数据库中数据的改变是永久性的;

Redis的事务总是具有一致性和隔离性
Redis是单进程程序,并且保证它在执行事务时,不会对事务进行中断,事务可以运行直到执行完所有事务队列中的命令为止;因此,Redis的事务总是带有隔离性的;
Redis中单条命令是原子性执行的,但事务不保证原子性,且没有回滚


集群方案

哨兵模式

在这里插入图片描述
什么是哨兵?

sentinel 哨兵 哨兵是redis集群模型里非常重要的组件;有以下功能:
集群监控:负责监控redis master 和slave进程是否正常工作;
消息通知:当有redis实例出现故障时,那么哨兵负责发送消息作为报警通知给管理员;
故障转移:如何master node挂掉了,会自动转移到slave node上;对于判断一个master node是否宕机,需要大部分的哨兵都同意才行,涉及到 了 分布式选举 的问题;
配置中心:如何故障转移发生了,通知client客户新的master地址;

哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作;


官方Redis集群方案 Redis Cluster

采用slot槽的概念,一共分成了16384个槽;请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行;
Redis哈希槽:redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群中的每个节点负责一部分hash槽。是key--slot--value(data)的结构;

实现机制(方案)
1、通过哈希的方式,将数据进行分片,每个节点均存储一定数量的哈希槽区间的的数据,默认分配了16384个槽位;
2、每份数据分片都会存储在多个互为主从的多节点上;
3、数据写入先写入主节点,再同步到从节点;
4、同一分片多个节点间的数据不保持一致性;
5、读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回指向指令,指向正确的节点;
6、扩容时需要把旧节点的数据迁移一部分到新节点;

在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

节点间的内部通信机制

Redis Cluster 节点间采用 goissp协议 进行通信;

分布式寻址算法

hash 算法(大量缓存重建)
一致性hash算法(自动缓存迁移)+虚拟节点(自动负载均衡)
redis cluster的 哈希槽 hash slot 算法

基于客户端进行分片
在这里插入图片描述
Redis Sharding 分片 是Redis Cluster之前普遍采用的集群方案;
主要思想是,采用哈希算法将redis数据的key进行散列,通过hash函数,特定的key会映射到特定的redis节点上

基于代理服务器进行分片
在这里插入图片描述
基本实现:客户端发送请求到一个代理组件,代理解析客户端的数据,并将请求转发到至正确的节点,最后将结果回复给客户端;


redis主从架构

redis的架构模式做成主从master-salve的架构一主多从,主负责写,并且将数据复制到其他的slave节点,从节点负责读;所有的读请求全部走从节点,这样也可以实现水平扩容,支撑读高并发;

在这里插入图片描述
Redis replication --主从架构–读写分离–水平扩容支撑高并发;

下面解析这几个步骤的核心实现机制

1、Redis replication 的核心机制

1)redis 采用异步方式复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
2)一个 master node 是可以配置多个 slave node 的;
3)slave node 也可以连接其他的 slave node;
4)slave node 做复制的时候,不会 block master node 的正常工作;
5)slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
6)slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
注意,如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。

另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。


主从复制核心机制

当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,
同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,
接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。
slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
在这里插入图片描述
为了使在部分节点失败或大部分节点无法通信的情况下集群仍可使用,集群采用了主从复制的模型,每个节点都会有N-1个复制品;

Redis并不能保证数据的强一致性,这意味着在特定的条件下可能会丢失写操作;

Redis集群之间是进行异步复制的;

Redis集群的最大节点个数是多少?16384

Redis默认0数据库

分区

Redis是单线程的,如何提高多核CPU的利用率?
可以在同一个服务器部署多个redis的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的 ,所以可以卡波率使用分片的方式来使用多个CPU;,分区可以让redis管理更大的内存;提高redis的计算能力和网络带宽;


分布式问题

缓存异常

缓存雪崩----缓存失效造成数据库崩掉
缓存雪崩指缓存在同一时间大面积失效,所以后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉;

解决方案
1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2、一般并发量不是特别多的时候,使用最多的解决方案是加锁排队
3、给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

缓存穿透
指访问一个缓存和数据库中都没有的数据,导致缓存层和存储层都没有命中;

缓存击穿
指访问一些缓存中没有而数据库中有的数据,这时由于并发用户过多,同时去访问数据库,导致数据库压力瞬间增大;


redis缓存与数据一致性问题

使用时,redis作为缓存可以加快程序响应速度;从缓存中读取数据比直接从数据库中读取数据会快很多。大致的业务流程如下:

在这里插入图片描述
每次设置缓存都有一个过期时间,根据业务不同,过期时间设置的也不一样,设置过期时间能保证缓存数据最终一致性问题;这样能保证在更新数据库成功,更新缓存失败,或者缓存了脏数据时,缓存过期后,能正确的读取到最新的值。

常见的三种缓存更新方案

方案一:先更新数据库 再更新缓存
这种方案采用的很少,因为这种方案存在以下问题:
1、并发更新问题 比如线程A更新了数据库,线程B更新了数据库,线程B更新了缓存,线程A更新了缓存;这样最终存入的数据就是脏数据;
2、更新次数较多,业务维护难度大

方案二:先删除缓存,再更新数据库
这种方案在我们实际中使用较多,大部分都能容忍可能出现的脏数据的业务,及时出现脏数据,缓存过期后,也会读取最新的值。但是这种方案存在这些问题:
1、存在脏数据的可能,比如线程A删除缓存,线程B查询缓存不存在数据,从数据库获取,获取成功后,数据存入缓存,现在A更新数据。这样缓存中的数据就是脏数据了。(脏数据解决方案是双删

方案三:先更新数据库,再删除缓存
这种方案虽然也会出现脏数据,但是概率极低,而且redis也有过期时间,能够保证最终一致性。
存在的问题
请求A查询数据库,得一个旧值,请求B将新值写入数据库,请求B删除缓存,请求A将查到的旧值写入缓存。这种情况下会存在脏数据。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.liang呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值