目录
什么是NoSql
理解
- NoSql是为了弥补关系型数据库在某些特定场景下,性能比较差的短板。
- 为一些并发量比较高的网站提供了比较高的性能。
- 所以针对不同的业务数据类型,NoSql有不同的实现方式。
- 比如Key/Value存储结构的Redis数据库,还有文档存储结构的MongoDB数据库。
标准回答
- NoSql可以理解为Not Only SQL,它是对非关系型数据库的一个统称。
- 在分布式高并发的架构里面,传统的关系型数据库存在着一些短板。比如说像性能,扩展性,大数据 量的存储。随着网站流量的一个增长,这些短板严重的影响了网站的性能,造成了一些业务的影响。
- NoSql它强调的是非关系型,分布式,可扩展性,不需要标准化的SQL语句来获取数据。
- 所以能够有效的避免表关联关系查询的一些操作,更好的实现水平扩展的这样一个特性。
- 同时针对不同类型的数据,可以灵活的去使用更加高效的存储形态,让性能得到进一步的提升。
是什么
- Redis数据库是基于内存的数据库。
- 它也没有严格的数据格式,支持多种数据类型。
- 而且也可以把数据持久化保存到本地磁盘中。
- 也支持主从复制和集群。
应用场景
单体架构里面用户的身份证信息如何存储
- 像某些公司,它们的客户群体主要是一些会展主办方。
- 这些会展主办方会把一些展位号卖给展商来赚钱。
- 这些展商会邀请全国各地的用户来参加它们的展会。
- 用户来到指定的地点,要进入展会现场,需要先用手机进入系统进行注册。
- 填写个人的基本信息,防疫信息,以及一些问卷调查,注册之后获取到二维码,然后扫码进入会场。
- 这里面最重要的一步就是要验证用户的身份证信息。
- 用户在填写完注册信息以后,会去点注册按钮,这个时候会去调用后端的API接口进行注册。
- 在该API接口中,会去调用阿里云的一个身份证实名认证查询接口。
- 阿里云的身份证实名认证查询接口是要钱的,对于公司来说每查询一次就是一毛线。
- 有些会展主办方比较大,像中国制冷展,参加展会的人一天会高达十几万人。
- 这个时候现场的情况是很难预知的。
- 你想想,十几万人再请求后端的API接口进行注册,这个时候并发量是很高的。
- 服务器在收到HTTP的请求以后需要对数据库进行查询,新增,修改,删除的IO操作。
- 并发量越高,对于数据库来说处理起来就越慢,从而就会导致HTTP请求的响应变慢。
- 对于用户来说,就是点了注册按钮以后,半天没有反应,前端会做个遮罩层显示“正在处理中,请稍 后”。
- 很多用户等不急了,就会返回到上一个界面,然后重新填写注册信息,再点注册按钮。
- 每点一次注册按钮,就会去调用阿里云的身份证实名认证查询接口进行查询,每次一毛钱,一天下来 两到三万。
- 那这个时候就可以考虑使用Redis数据库,在对数据库进行查询,新增,修改,删除的IO操作之前, 如果用户的身份证信息是合法的,就把它存到Redis数据库里面。
- 这样用户不管点击多少次注册按钮都是先从Redis数据库里面去查,而且不是直接去调用阿里云的身份 证实名认证查询接口进行查询。
- 用Redis数据库还有一个好处,就是已经上线的项目出问题了,需要改一改然后在发布一下。
- 如果把用户的身份证信息当作静态资源去存储,所有的静态资源都会被存储在”静态存储区”里面,重 新发布的过程就是程序退出的过程,”静态存储区”里面的静态资源都会被释放掉。
- 所以重新发布之后,用户重复点击注册按钮还是会先调用阿里云的身份证实名认证查询接口进行查询。
- 不光如此,同行还要攻击你,它们会请一些专业人员,把你们公司的系统进行扫描,找出一些敏感接 口,疯狂的去发送HTTP的请求,即使没有Token也无所谓,就是占用你的带宽,你的流量,正常用户 的业务流量根本进不来,这种攻击手法也就是最没有技术含量的DDoS攻击。
- 阿里云的服务器每天被攻击上百万次,你去查都是虚拟IP,根本找不到是谁攻击你。
- 阿里云的DDos基础防护,一个月2W,人家有专门的流量清洗设备对这些非法流量进行清洗。
- 面对TCP协议的SYN洪水攻击,客户端发起的SYN先经过流量清洗设备,由清洗设备回复SYN和Ack, 如果对方应答了,那说明是正常的流量,清洗设备再把本次连接交给后方的服务器进行正常的通信, 如果对方不应答,清洗设备该重试就进行重试,超时后就断开连接。
- 但是一个月2W太贵了,小公司根本用不起,那这个时候怎么办呢?
- 除了买阿里云的DDos基础防护,还可以用CND节点进行稀释,把一些相对静态的资源作为缓存分发 给各个CDN节点,用户在请求的时候从最近的节点返回,从而在一定程度上能缓解DDoS攻击。
- 还可以进行分布式集群,如果某台服务器因为DDoS攻击挂了,还有其它的服务器暂时能用。
- 还可以设置IP黑名单,但是不一定有效,因为大部分的IP都是虚拟的IP。
- 还可以通过系统防火墙或者ESC安全组禁止非业务的端口。
- 还可以更换源站的业务公网IP地址,但是也不一定有效。
- 最有效的解决方案,还是买阿里云的DDos基础防护。
分布式架构里面Session如何存储
- 像有些公司的项目是用MVC做的,用户识别还是用的Cookie和Session的模式。
- 那Cookie和Session的模式到底是什么呢?
- 如果是第一次的HTTP请求,服务端就会自动给当前的HTTP请求分配一个SessionID然后返回给客户 端,客户端会把SessionID存储在Cookie里面。
- 浏览器后面再进行HTTP请求的时候就会基于Cookie默认带上这个SessionID,服务器就会检查当前 SessionID是否有效,如果SessionID有效就会从Session对象中获取当前用户的信息,如果SessionID 失效了,就会重定向到登录页面。
- 虽然公司的项目是单体架构,而且还是MVC的模式,也没有进行前后端的分离,业务也一直也没有太 大的增长,但是目前用的人却多了。
- 用户的数量变多了,那也就意味着并发量变高了,数据库的压力也变大了。
- 所以就把单体架构搞成了分布式,就是多买几台服务器,然后用Nginx做一个负载均衡,让多台服务 器来分担一台服务器的请求压力。
- 但是是基于Cookie和Session的模式,那Session如何存储呢?
- 现在用户第一次登录,登录也成功了,通过负载均衡把HTTP的请求转发到A服务器上面,这个时候 在A服务器上面产生了Session对象。
- 那用户下一次在发送HTTP的请求,这个时候通过负载均衡把HTTP的请求转发到了B服务器上面,B 服务器上面没有Session对象,没有办法对当前的用户进行识别,还是会重定向到登录页面,让用户再 次登录。
- 那这个时候怎么办呢?
- 如果直接把Session对象都存到了Cookie里面,那肯定不安全,HTTP的请求头里面就有Cookie这个键 名,是个人都能看到。如果Session对象太大的话,数据在网络中的传输效率也会变低。
- 如果把Session对象存到关系型数据库里面,那数据库就会产生IO操作,效率也会变低。
- 那有些人就会把Session对象进行复制,给每个服务器都复制一份Session对象,这个时候Session对象 的数据就会产生冗余,服务器的数量越多,浪费也就越大。
- Nginx里面有一种负载均衡的策略是ip_hash,如果当前HTTP请求访问的是8080端口的服务器,那么 只要HTTP请求的IP地址没有变化,就会一直访问8080端口的服务器,所以可以用来做Session共享。
- 那如果Nginx里面的负载均衡策略不是ip_hash,而是轮询,权重,Fair呢?
- 这个时候就可以考虑使用Redis数据库,把Session对象存储到Redis数据库里面。
- 不管负载均衡把当前的HTTP请求负载到哪一个服务器上面,都先通过SessionID去Redis数据库里面 查找Session对象,进行用户识别。
- 如果用户识别通过了,在把当前的HTTP请求发送给具体的服务器去处理。
微服务架构里面RefreshToken如何存储
- 像有些公司的业务比较大,研发也比较多,这个时候做成单体架构就不太合适,都是把一个大系统拆 分成很多个子系统,每个子系统对应一个项目组去做,而且每个子系统对应的有自己的数据库。
- 而且也不用MVC了,像某些公司用的就是gRPC的接口,进行前后端分离。
- 那现在进行用户识别,用Cookie和Session的模式,肯定就不合适了,都是用的Token。
- 那为什么要用Token呢?
- 既然现在都已经做成前后端分离了,都已经是API接口的模式了,那肯定需要有一个鉴权中心根据用 户的账号和密码颁发Token,HTTP的请求带着Token就可以访问API,API也认可Token,不需要再去 鉴权中心进行校验,第三方的API也认可Token,从而实现了单点登录。
- 这个鉴权中心可以用IdentityServer4来做,就是一个单独的服务器专门用来做鉴权,生产Token。
- 那Token失效了怎么办?用户正在客户端进行操作的时候,突然跳转到登录界面需要你重新登录,那 肯定不合适。
- 这个时候就需要用RefreshToken来换取的新的Token,保持用户的登录状态。
- RefreshToken的有效时间一定要比Token的有效时间要长。
- RefreshToken可以是GUID,雪花ID,或者是时间戳,只要能保证唯一性就行。
- 前端每次访问API接口都会在HTTP的请求头中携带Token,如果Token失效了服务端就会返回401状 态码。
- 前端判断如果是401状态码,就从localStorage里面获取RefreshToken,再去请求服务端获取Token的 API接口。
- 服务端会先检查RefreshToken的合法性。
- 如果RefreshToken有效,服务端就会返回一个新的Token和RefreshToken给前端。
- 然后前端就会更新localStorage里面的RefreshToken,同时用这个新的Token向服务端发起第三次请求。
- 如果RefreshToken无效,服务端还是返回401状态码,前端第二次判断如果还是401状态码,就会跳 转到登陆页面。
- 如果用的是IdentityServer4,RefreshToken都不用你自己去生成,IdentityServer4本身就具备生成 RereshToken的能力。
- 那这个时候RefreshToken如何存储呢?
- 各个子系统对应的都是单独的服务器,每个服务器对应的都有自己的数据库。
- 如果把RefreshToken存到关系型数据库里面,那数据库就会产生IO操作,效率会变低。
- 如果把RefreshToken进行复制,给每个服务器都复制一份RefreshToken,那数据就会产生冗余,服务 器的数量越多,浪费也就越大。
- 所以这个时候可以考虑先访问IdentityServer4的服务器,验证RefreshToken的合法性,然后在把当前 的HTTP请求发送给具体的服务器去处理。
- 那有些公司就想把IdentityServer4当成一个只生产Token的鉴权服务器,RefreshToken要自己生成。
- 所以这个时候就可以考虑把RefreshToken存储到Redis数据库里面。
- 当用户再次发送HTTP的请求时,先去Redis数据库里面看看当前RefreshToken是否过期了,如果 RefreshToken过期了,那Redis数据库也会把它自动给删除掉。
- 然后在验证当前RefreshToken的合法性,如果是伪造的,那肯定验证是不通过的。
- 如果RefreshToken在Redis数据库中的验证都通过了,在把当前的HTTP请求发送给具体的服务器去处 理。
数据类型
String
String类型是Key/Value的数据结构。一个Key对应多个Value,Value就是动态的字符串。
Hash
- Hash类型是每一个HashID对应一个Dictionary。
- Dictionary可以存储多条数据,它也是Key/Value的数据结构。
- 所以Hash类型在存储数据的时候,必须要有一个HashID。
- 通过HashID和Key就可以查询到对应的Value值。
- 利用Hash类型的这种特性就可以存储用户信息。
- 比如HashID就是用户的ID,然后Dictionary里面就可以存储用户的多条信息。
- 比如存储姓名,那么Key就是name,Value就是李鹏。
- 比如存储年龄,那么Key就是age,Value就是28。
Set
- Set类型也是Key/Value的数据结构。一个Key对应多个Value,但是可以对Value进行去重。
- 像对IP的地址进行统计,就想看看当前网站到底都被哪些IP地址的用户访问过,一个用户的IP地址 如果一直没有变化,它可以把你的网站访问很多次,这个时候IP地址肯定就会有重复,所以像这种场 景就可以用Set类型来做。
ZSet
- ZSet类型也是Key/Value的数据结构。但是它是一个Key对应多个Value和Score,每一个Value会对 应的有自己的Score。
- 所以ZSet类型不但可以对Value进行去重,还能根据Score进行排序。
- 像一些直播平台的礼物排行榜就可以用ZSet类型来做。
List
- List类型是一个双向链表的数据结构。跟C#里面的LinkedList类型比较像。
- 所以List类型存储的元素不是连续分配的,每个存储的元素都会记录前后每个元素的节点。
- 这种双向链表的数据结构,再进行修改和新增操作的时候,效率是非常高的,
- 但是利用索引去访问某个元素的时候,效率是比较低的。
- 那利用List类型的这种特性,就可以用来构建一个队列。
- 但是目前List类型我确实也没用过,所以不太好说。
Bitmaps
- 还有一些新的数据类型,比如Bitmaps类型。
- 但是Bitmaps类型本身不是一种数据类型,就是字符串,但是它可以对字符串的位进行操作。
- 所以Bitmaps类型就是一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps 中叫做偏移量。
- 那利用Bitmaps类型的这种特性,就可以用来做布隆过滤器,解决缓存穿透的问题。
- 布隆过滤器就是一个很长的二进制向量和一系列随机映射的函数。
- 所以就可以把所有可能存在的数据都哈希到一个足够大的bitmaps数组中,那么一定就有一个不存在 的数据会被这个bitmaps给拦截掉,从而避免了对底层数据库的查询压力。
HyperLoglog
还有一个HyperLogLog类型,Redis官方网站对它的介绍是用来做基数统计的算法,实际项目中具体也没有这样的场景,也没用过。
Geospatial
还有一个Geospatial类型,好像是用来存储经纬度的,实际项目中有存储经纬度的场景,但是没用过Geospatial这个类型。
发布订阅
怎么做
- 客户端可以订阅channel,比如现在有A和B两个客户端都订阅了channel1。
- 命令就是subscribe channel1。
- 然后A客户端给这个channel1发送消息,B客户端也能收到。
- 命令就是publish channel1 hello。
示意图
原子操作
- Redis数据库的原子操作指的是不会被线程调度机制打断的操作。
- 所以这种操作一旦开始,就会一直运行直到结束,中间不会切换到另一个线程去处理。
- Redis数据库有原子操作,主要是因为Redis数据库是单线程模型。
单线程模型
- Redis数据库的单线程模型指的是向Redis数据库发送命令的时候,Redis数据库是不阻塞的。
- 所以不管命令在单位时间内同时发送多少次,Redis数据库还是用单线程去处理。
- 但是Redis数据库的单线程模型只是指执行指令的时候是单线程的,而不是指Redis数据库的服务只有 一个线程。
单线程模型处理并发
- 因为Redis数据库是单线程模型,所以在处理并发请求的时候是一个一个去处理的。
- 在类似于天猫,京东这样的秒杀商品活动中,通过Redis数据库去执行是不存在并发的。
- 因为在Redis数据库中加和查询是一个动作,不是分开执行的。
为什么适合处理高并发
- 如果当前网站的并发量很高的话,直接使用.Net提供的混合锁,多线程会进行排队,会非常影响性能。
- 但是如果不加锁的话,比如数据库中某个字段的值为10,A用户和B用户在同一个时刻都会看到这 个字段的值为10。
- 所以利用Redis数据库的单线程模型去执行指令的时候,即使当前网站的并发量很高,也是单线程处理。
事务
主要作用
- Redis数据库中的事务主要作用就是用来串联多个命令防止别的命令进行插队。
- 如果组队中某个命令出现了错误,那么在执行时所有的队列都会被取消。
- 如果在执行阶段某个命令出现了错误,那么只有报错的命令不会被执行,而其它的命令都会被执行, 不会回滚。
三个特性
- Redis数据库中的事物主要有三个特性。
- 第一个特性是单独的隔离操作,也就是事务中所有的命令都会被序列化,按照顺序执行。而且事务在 执行的过程中,也不会被其它的客户端发送过来的命令给打断。
- 第二个特性是没有隔离级别的概念,也就是队列中的命令在没有提交之前都不会被实际的执行,因为 事务在提交之前任何指令都不会被实际的执行。
- 第三个特性是不保证原子性,也就是事务中如果有一条命令执行失败了,那么后面的命令仍然会被执 行,没有回滚。
锁
悲观锁
悲观锁就是每次获取数据的时候都认为别人会修改,所以每次在获取数据的时候都会上锁,这样别人想获取这个数据的时候,就会block直到它获取到了锁。
乐观锁
乐观锁就是每次获取数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号,时间戳来进行判断。
分布式锁
- 分布式锁是多个进程不在同一个系统中,用分布式锁来控制多个进程对资源的访问。
- 因为Redis数据库是单线程模型,它的命令操作都是原子性,利用Redis数据库的这个特性就可以很容 易的实现分布式锁。
- 比如A用户在Redis数据库中利用SETNX指令写入了1个KEY,其它用户暂时无法写入这个KEY,从而 实现锁的效果。
RDB
是什么
RDB是在指定的时间间隔内将内存中的数据集快照写入到磁盘中。
备份是如何执行的
- Redis数据库会单独创建出一个子进程来进行持久化,会先将数据写入到一个临时文件中,持久化的过 程都结束了,再用这个临时文件替换上次已经持久化的文件。在整个过程中,主进程是不会进行任何 IO操作的,这就能确保比较高的性能。
- 如果需要进行大规模的数据恢复,并且对于数据恢复的完整性不是非常敏感,那么使用RDB的方式要 比使用AOF方式更加高效。
- 但是使用RDB的方式在最后一次持久化之后的数据可能会丢失。
Fork
- Fork的作用是复制一个与当前进程一样的进程。新进程所有的数据都和原进程一样,但它是 一个全新 的进程,并且作为原进程的子进程。
- 在Linux系统中,Fork会产生一个和父进程完全相同的子进程,但是子进程在此之后会多次被系统调 用,所以在Linux系统中引入了写时复制技术。
- 一般情况下父进程和子进程都会共用同一段物理内存,只有进程空间的内容要发生变化时,才会将父 进程的内容复制一份给子进程。
持久化流程
save/bgsave/lastsave
- 使用save命令时,Redis数据库只管保存,会阻塞全部的进程,而且是手动的保存,不建议使用。
- 使用bgsave命令时,Redis数据库会在后台进行异步的快照操作,快照的同时还可以响应客户端的请 求。
- 使用lastsave命令时,Redis数据库会获取最后一次成功执行快照的时间。
文件压缩
- 默认情况下,Redis数据库会自动对RDB的文件进行压缩。
- Redis数据库中的conf文件,rdbcompression默认配置的就是 yes。
- Redis数据库采用LZF算法进行RDB文件的压缩,对RDB文件的压缩只是针对数据库中的字符串进行 压缩,并且字符串达到一定的长度(20字节)时才会进行压缩。
数据校验
- Redis数据库中的conf文件,rdbchecksum默认配置的就是 yes,用来检查数据的完整性。
- Redis数据库在存储完快照以后,可以让Redis数据库使用CRC64算法来进行数据的校验。
- 如果这样做会增加大约10%的性能消耗,如果想获得比较大的性能提升,建议关闭。
优势
- RDB比较适合大规模的数据恢复,如果对数据的完整性和一致性要求不高,可以考虑使用RDB。
- RDB是将内存中的数据集快照写入到磁盘中,所以在进行数据恢复时比较快。
劣势
- RDB在进行数据备份时,Fork会复制一个和当前进程一样的进程,相当于内存中的数据被克隆了一份, 那么大概2倍的膨胀性需要考虑。
- 虽然Redis数据库在Fork时使用了写时拷贝技术,但是如果数据非常庞大时还是比较消耗性能的。
- 如果Redis数据库宕机了,就会丢失最后一次快照之后的所有修改。
总结
AOF
是什么
- AOF是以日志的形式来记录每个写操作指令,并且是增量保存。
- 可以将Redis数据库执行过的所有写操作指令都记录下来,读操作指令不记录。
- AOF只允许追加文件但是不可以改写文件,Redis数据库启动时会读取该文件重新构建数据。
- 如果Redis数据库进行重启的话,就会根据日志文件的内容将写操作指令从前到后执行一次,然后完成 对数据的恢复工作。
持久化流程
- 客户端的请求命令会被追加到AOF的缓冲区中。
- AOF的缓冲区会根据AOF的持久化策略将操作同步到磁盘的AOF文件中。
- 当AOF文件的大小超过重写策略或者进行手动重写时,会自动对AOF文件进行重写,压缩AOF文件的 容量。
- 当Redis数据库进行重启时,会重新加载AOF文件中的写操作指令,然后完成对数据的恢复工作。
同步频率设置
- appendfsync配置为always表示始终同步,每次Redis数据库的写操作指令都会被立刻写入到日志文件 中,性能比较差但是数据的完整性比较好。
- appendfsync配置为everysec表示每秒同步,也就是每秒都对日志文件进行写入,如果Redis 数据库宕 机了,那么本秒的数据可能会丢失。
- appendfsync配置为no表示redis数据库不主动进行同步。
Rewrite压缩
当AOF文件的大小超过了设定的阈值时,Redis数据库就会对AOF文件的内容进行压缩,只保留可以恢复数据的最小指令集,可以使用bgrewriteaof命令进行手动压缩。
优势
- AOF的备份机制属于日志文件,所以丢失数据的概率比较低。
- 而且AOF的日志文件是可读的,所以可以通过操作AOF文件进行重建,处理一些错误的操作。
劣势
- AOF相比于RDB会占用更多的磁盘空间。
- 而且在进行数据恢复或者数据备份时速度会比较慢。
- 如果每次的读写操作都进行同步的话,就会有对性能造成影响。
总结
RDB和AOF用哪个好
- 官方推荐RDB和AOF两个都启用。
- 如果对数据不敏感,可以选择单独使用RDB。
- 但是官方不建议单独使用AOF,因为单独使用AOF可能会出现某些Bug。
- 如果只是用Redis数据库做纯内存的缓存,那么RDB和AOF可以都不用。
主从复制
是什么
- Redis数据库的主从复制是指Master节点的数据更新后会自动同步到slave节点的机制。
- Master节点以写为主,Slave节点以读为主。
能干嘛
- Redis数据库的主从复制能进行读写分离,对性能进行扩展。
- 也能进行容灾的快速恢复,就是当一台从服务器挂掉之后,能够快速的切换到其它从服务器中,提供 数据的读操作。
复制原理
- Redis数据库的主从节点包括全量复制和增量复制。
- 全量复制发生在初始化阶段,从节点会主动向主节点发起一个同步请求,主节点收到请求以后会生 成一份当前数据的快照,发送给从节点。从节点收到主节点发送的数据快照以后进行数据加载,从而 完成全量复制。
- 增量复制发生在Master节点发生数据变更的时候,会把发生变化的数据同步给从节点。增量复制主要 是通过维护offset偏移量来实现同步的。
复制流程
- Redis数据库在进行主从复制时,从节点会保存主节点的IP地址和端口号。
- 然后从节点和主节点会建立Socket连接。
- 从节点会主动发送PING命令给主节点。
- 主节点返回PONG,表示主从连接没有问题。
- 如果主节点没有返回PONG,可能是因为阻塞或者其它原因导致了超时,从而让主从断开了连接。
- 如果是权限验证,连接的时候还需要验证密码。
续传问题
- 主节点和从节点都会维护一个offset偏移量,主节点会不断的累加offset偏移量,从节点也会不断的 累加offset偏移量。
- 从节点每秒都会上报自己的offset偏移量给主节点,主节点要维护每一个从节点的offset偏移量。
- 主节点给从节点复制数据的时候,也会将数据在backlog中同步写一份,就是在全量复制的时候或者断 开连接的时候能够实现续传。
哨兵模式
是什么
哨兵模式就是反客为主的自动版,后台自动监控主机是否发生了故障,如果发生了故障就自动将从库转换为主库。
复制延迟
- Redis数据库的复制延迟是因为所有的写操作都是先在Master节点上面进行。
- 然后同步更新到Slave节点。
- 所以从Master节点同步到Slave节点的时候就有一定的延迟。
- 比如系统比较繁忙的时候,延迟的问题会比较严重。
- 当Slave节点的机器数量增加的时候,也会使复制延迟的问题变得严重。
故障恢复
集群
是什么
- Redis数据库的集群就是启动多个Redis数据库的节点,将某个Redis数据库的数据分布存储在多个节 点中。
- Redis数据库的集群通过分区来提供一定程度的可用性,即使集群中有一部分节点失效了或者无法进行 通讯时,集群也可以继续处理命令请求。
代理主机/无中心化集群
无中心化集群是指任何一台服务器都可以作为集群的入口,它们之间可以互相连通。
优势
- Redis数据库的集群可以实现扩容。
- 主要就是分摊某个Redis数据库的请求压力。
劣势
- 在Redis数据库的集群中,多键的操作和事务都是不被支持的。
- lua脚本不被支持。
哈希取余分区
一致性哈希算法分区
是什么
- 一致性哈希算法用来解决在分布式集群中,缓存的数据变动和数据映射问题。
- 如果某个Redis数据库宕机了,分母数量就会发生改变,自然取余数就会有问题。
能干嘛
当服务器的个数发生改变时,尽量减少影响客户端到服务器的映射关系。
哈希槽分区
是什么
哈希槽就是一个数组,由数组[0,2^14 -1]形成hash槽的存储空间。
能干什么
- 哈希槽分区主要是为了解决均匀分配的问题,在数据和节点之间又加入了一层,把这层就叫做哈希槽。
- 利用哈希槽可以管理数据和节点之间的关系,就相当于给节点上放的是槽,而槽里面放的是数据。
Redis数据库的hash槽
Redis数据库的集群中内置了16384个哈希槽,Redis数据库会根据节点的数量大致均等的将哈希槽映射到不同的节点上。
缓存穿透
是什么
- 缓存穿透是指重复查一条数据,缓存中没有,数据库中也没有。
- 比如用一个不存在的用户ID去获取用户的信息,不论是缓存还是数据库都没有。
解决方案
对空值缓存
- 如果一个查询返回的数据为空,那么不管数据是否存在,都把这个空结果进行缓存。
- 设置空结果的过期时间最长不要超过五分钟。
布隆过滤器
- 布隆过滤器是一个很长的二进制向量和一系列随机映射函数。
- 它可以用于检索一个元素是否在一个集合中。
- 优点是空间效率和查询时间都比一般的算法快,缺点是有一定的误识别率和删除困难。
- 可以用C#里面的HashSet来实现布隆过滤器,HashSet最大的优势就是检索的性能,它的Contains方 法在数据量比较大时性能非常高。
- 所以可以把所有可能存在的数据都存储到HashSet中,一定就有一个不存在的数据会被HashSet给拦截 掉。
- 也可以使用Redis数据库的Bitmaps类型,Bitmaps类型是一个以位为单位的数组,数组的每个单元只 能存储0和1,数组的下标在Bitmaps中叫做偏移量。
- 那就可以把所有可能存在的数据都哈希到一个足够大的Bitmaps数组中,一定就有一个不存在的数据 会被这个Bitmaps给拦截掉。
- 利用C#里面的HashSet类型和Redis数据库的Bitmaps类型都能实现布隆过滤器,从而避免了对底层数 据库的查询压力。
缓存击穿
是什么
- 缓存击穿是指某条数据缓存中没有,数据库中有。
- 比如某一时刻,多个用户同时查询Redis数据库中的某个Key。
- 如果这个Key过期了,大量的请求就会直接访问数据库。
解决方案
添加混合锁
缓存击穿后,多线程会同时查询数据库的这条数据,可以在第一次查询数据的请求上使用一个混合锁来锁住它,其它线程拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面线程发现已经有缓存了,就直接走缓存。
预先设置热门数据
在Redis数据库的高峰期访问之前,先把一些比较热门的数据提前存入到Redis数据库里面,然后把这些热门数据的过期时间设置的长一些。
缓存雪崩
是什么
- 缓存雪崩是指多条数据的缓存过期了或者Redis数据库宕机了,大量的请求就会直接访问数据库。
- 也就是在某个时间段内,大量的Key出现了集中过期的情况。
缓存雪崩和缓存击穿的区别
缓存雪崩是指多个Key过期或者失效了,缓存击穿是指某几个Key过期或者失效了。
解决方案
设置热点数据不过期
构建多级缓存
还可以构建多级缓存,用Nginx进行缓存,再用Redis数据库进行缓存,还可以用C#里面的MemoryCache类型在进行缓存。
使用锁或队列
- 还可以考虑用加锁或者队列的方式来保证不会有大量的线程对数据库进行一次性读写。
- 从而避免缓存失效时大量的并发请求直接访问数据库。
设置过期标志更新缓存
还可以记录缓存的数据是否过期,如果缓存的数据过期了就让其它线程在后台去更新缓存。
将缓存过期时间分散开
还可以在缓存过期时间的基础上增加一个随机值,比如1到5分钟的随机值,这样每一个缓存过期时间的重复率就会降低,也就能避免缓存集体失效的情况。
哨兵模式或者集群
还可以基于Redis数据库的主从复制做哨兵集群或者直接做Redis数据库的集群。
缓存淘汰策略
- 当Redis数据库的内存使用率达到maxmemory参数配置的阈值时,Redis数据库就会根据配置的缓存 淘汰策略,把访问频率不高的Key从内存里面移除掉。
- maxmemory的默认值是服务器的最大内存。
- Redis数据库常用的缓存淘汰策略有以下几种:
- 第一种是采用LRU策略,就是把不经常使用的Key直接给淘汰掉。
- 第二种是采用LFU策略,它确保被淘汰的数据是非热点的。
- 第三种是采用随机策略,就是会随机的删除一些Key。
- 第四种是采用TTL策略,就是从设置了过期时间的Key里面,挑选出过期时间最近的一些Key,进行优 先淘汰。
- 第五种是直接报错,也就是当内存不足的时候,会直接抛出一个异常,这也是一个默认的处理策略。
- 这些策略可以在Redis数据库的conf文件里面去手动配置和修改,可以根据缓存的类型和缓存的使用 场景去选择合适的缓存淘汰策略。
- 在使用缓存的时候,增加这些缓存的过期时间,知道这些缓存大概的一个生命周期,能更好的利用内 存。
Redis数据库存在线程安全问题吗?
- Redis本身就是一个线程安全的Key/Value数据库,也就是说在Redis数据库上面执行的指令,不需要 有任何同步机制,不会存在线程安全的问题。
- 虽然在Redis 6.0里面,增加了多线程的模型,但是增加的多线程只是用来处理网络IO的请求,对于指 令的执行过程,仍然是由主线程来处理,所以不会存在多个线程同时执行操作指令的情况。
- 那为什么Redis 数据库没有采用多线程来执行指令呢?
- Redis数据库本身可能出现的性能瓶颈,无非就是网络IO,CPU,内存。但是CPU不是Redis数据库的 瓶颈点,所以没有必要使用多线程来执行指令。
- 如果非得要采用多线程来执行指令,那么就意味着对Redis数据库的所有指令操作,都必须要考虑到线 程安全的问题,也就是说需要加锁来解决,采用这种方式带来的性能影响反而会更大。
- 从Redis数据库的客户端层面来说,虽然Redis数据库在执行指令的时候是原子性的,但是如果有多个 Redis数据库的客户端同时执行多个指令的时候,就无法保证原子性。
- 假设有两个Redis Client同时获取Redis Server上的 Key1,同时进行修改和写入时,因为多 线程环境 下的原子性无法被保障,还有多进程情况下的共享资源在进行访问的时候会存在竞争的问题,这些都 会导致数据的安全性无法得到保障。
- 对于客户端层面的线程安全性问题,解决方法有很多,比如尽可能使用Redis数据库里面的原子指令, 或者对多个客户端的资源访问进行加锁,还可以通过Lua脚本来实现多个指令的操作。
Redis数据库多线程模型怎么理解
- Redis 6.0是支持多线程的,并不是说执行指令支持多线程,而是网络的IO支持多线程。
- 也就是说Redis数据库的命令操作,仍然是线程安全的。
- Redis数据库本身的性能瓶颈,主要取决于网络,CPU,内存。
- 而Redis 6.0的多线程,就是为了提高网络IO的处理效率。
- 在Redis 6.0之前。Redis Server端在处理客户端请求的时候,从Socket连接建立成功一直到指令的读 取,解析,执行,回写都是由一个线程来处理。这种方式,在客户端请求比较多的情况下,单个线程 的网络处理效率太低了,从而也导致客户端的请求处理效率变低。
- 于是在Redis 6.0中,对于网络IO的处理方式改成了多线程,采用多线程并行的方式提高了网络IO 的处理效率。
- 但是对于客户端执行指令的过程,还是使用单线程的方式来执行。
- Redis 6.0里面的多线程默认是关闭的,需要在Redis数据库的conf文件里面修改io-threads-do-reads 配置才能开启。
- 另外,执行指令不使用多线程,我认为有两个方面的原因。
- 内存的IO操作,本身不存在性能瓶颈,Redis数据库在数据结构上已经做了非常多的优化。
- 如果执行指令使用多线程,那么Redis数据库为了解决线程安全的问题,需要对数据操作增加锁的同步, 这样不仅增加了复杂度,还会影响性能,代价太大了。
请描述一下Redis数据库中的AOF重写过程
为什么AOF要重写
- AOF是Redis数据库里面的一种数据持久化方式,它采用了指令追加的方式,而且是一种几乎实时的持 久化方式。
- AOF会把每条数据的写操作指令,都追加存储到AOF文件里面。
- 所以就很容易导致AOF文件变大,造成磁盘IO的性能问题。
- Redis数据库为了解决这个问题,设计了AOF重写机制,也就是说把AOF文件里面相同的指令进行压 缩,只保留最新的数据指令。
- 如果AOF文件里面存储了某个Key多次变更的记录,最终在做数据恢复的时候,只需要执行最新的指 令操作就可以了,历史的指令操作没有必要在文件里面占用空间。
AOF文件重写步骤
- 首先,根据当前Redis数据库里面的数据,重新构建一个新的AOF文件。
- 然后,读取当前Redis数据库里面的数据,写入到新的AOF文件的后面。
- 重写完成以后,用新的AOF文件覆盖现有的AOF文件。
- 因为AOF 在重写的过程中需要读取当前内存里面所有的键值数据,再生成对应的一条指令进行保存。
- 而这个过程是比较耗时的,对业务会产生影响。
- 所以Redis数据库把重写的过程放在一个后台子进程里面来完成,这样一来,子进程在做重写的时候, 主进程仍然可以继续处理客户端的请求。
- 为了避免子进程在重写过程中,主进程的数据发生变化,导致AOF文件和Redis数据库内存中的数据 不一致,Redis数据库还做了一层优化。
- 就是在子进程重写的过程中,主进程的数据变更需要追加到AOF重写缓冲区里面。等到AOF文件重写 完成以后,再把AOF重写缓冲区里面的内容追加到新的AOF文件里面。
RDB和AOF的实现原理,优缺点
- RDB是通过快照的方式来实现持久化,也就是说会根据快照的触发条件,把内存里面的数据快照写 入到磁盘,以二进制的压缩文件进行存储。
- RDB快照的触发方式有很多,比如执行bgsave命令会触发异步快照,执行save命令会触发同步快照。
- 也能自动触发bgsave命令在进行主从复制的时候,再去触发AOF的持久化。它是一种几乎实时的方式, 把Redis数据库的写操作指令进行追加存储。
- 简单来说,就是客户端执行一个数据变更的操作,Redis数据库就会把这个命令追加到AOF缓冲区的末 尾,然后再把缓冲区的数据写入到磁盘的AOF文件里面。
- 另外,因为AOF这种指令追加的方式,会造成AOF文件过大,带来明显的磁盘IO问题。
- 所以Redis数据库针对这种情况提供了AOF重写机制,也就是说当AOF文件的大小达到某个阈值的时 候,就会把这个文件里面的相同指令进行压缩。
- RDB就是每隔一段时间就会触发持久化,所以数据的安全性比较低。
- AOF可以做到几乎实时的持久化,所以数据的安全性比较高。
- RDB文件默认就是采用压缩的方式来进行持久化,而AOF文件存储的是执行的指令,所以RDB在做数 据恢复的时候性能要比AOF好。
Redis数据库如何保证数据的一致性?
- 一般情况下Redis数据库就是用来做应用层和关系型数据库之间的缓存层。
- 它的主要目的就是为了减少关系型数据库的磁盘IO。
- 当应用层需要读取某个数据的时候,首先会尝试从Redis数据库里面去加载,如果命中就直接返回,没 有命中就直接从关系型数据库里面进行查询,查询到数据之后,再把数据缓存到Redis数据库里面。
- 如果一条数据同时保存在关系型数据库和Redis数据库里面,当该条数据发生变化的时候,需要同时更 新Redis数据库和关系型数据库。
- 由于更新操作是有先后顺序的,它并不像关系型数据库中多表的事物操作,可以满足ACID(数据库事物 正确执行的四要素)的特性,所以就会出现数据一致性的问题。
- 那么解决方案有两种:
- 第一种是先更新关系型数据库的数据,再更新Redis数据库的数据。
- 如果Redis数据库的数据更新失败就会导致Redis数据库和关系型数据库的数据不一致。
- 第二种是先删除Redis数据库的数据,再更新关系型数据库的数据。
- 理想情况下,当应用层下次访问Redis数据库的时候,发现Redis数据库里面的数据为空,那么就会从 关系型数据库里面把数据保存到Redis数据库里面。
- 在一些极端的情况下,由于删除Redis数据库的数据和更新关系型数据库的数据这两个操作并不是原子 操作。所以在这个过程中,如果出现了其它的线程来修改某条数据,还是会出现数据不一致的问题。
- 如果在极端的情况下,还是需要保证数据的一致性,可以基于MQ的可靠性消息通信,来实现数据的 最终一致性,但是会增加系统的复杂度。
- 还可以写一些定时调度的服务来监控关系型数据库中的日志,把更新后的数据同步到Redis数据库里 面。
- 如果业务场景不接受数据的短期不一致性,那么就不能使用这样的方案去实现。
- 正常情况下出现数据的不一致性都是发生在修改和删除这种操作中,所以在修改的时候可以先更新关 系型数据库,然后再更新Redis数据库。删除的时候先删除关系型数据库再删除Redis数据库。
Redis数据库的哨兵模式和集群有什么区别
- Redis数据库的集群有两种模式,第一种是主从复制,第二种是Redis Cluster。
- Redis数据库的哨兵模式是基于主从复制实现的,所以它可以实现读写分离,分担Redis数据库读操作 的一个压力。
- Redis数据库用哨兵模式做集群就是为了实现反客为主的自动版。后台自动监控Master节点是否发生 了故障,如果Master节点发生了故障就会自动将Slave节点转换为Master节点。
- Redis Cluster里面的Slave节点,只是实现了冷备的一个机制,它只有在Master节点宕机之后才会工作。
- Redis数据库的哨兵模式无法在线扩容,所以它的并发压力受限于单个服务器的资源配置。
- Redis Cluster提供了基于Slot槽的一个数据分配机制,可以实现在线扩容,提升读写的一个性能。
- 从集群的架构来说,Redis数据库的哨兵模式是一主多从,Redis Cluster是多主多从。