分布式系统架构面试题汇总(下)

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 这篇文章主要是说在进化的过程中可能会遇到的问题以及如何去解救这些问题。

三、缓存数据库Redis


1、主从复制:实现高可用

一个缓存数据库压力太大,读写分离,通过哨兵机制监控各个节点和相互监督;

v2-ef91679ea6c576dbf470776a45de68fd_1440w.jpg

(1)哨兵模式原理


①哨兵如何实现相互监督的功能


第一:哨兵通过发布订阅__sentinel__:hello channel来实现这个功能。每个哨兵每隔2s会向自己监控的所有主从Redis节点发送hello message,包括自己的IP、端口、运行ID、自己监控的Master节点IP、Master节点端口。


第二:所有主从Redis节点也会反馈这样的信息


②哨兵如何故障检测


第一:某个哨兵节点判定master节点故障,他会投出一票S_DOWN,


第二:当有足够多的sentinel节点判定master节点故障都投出S_DOWN票时,master节点会被认为是真正的下线了。


也就是基于多数投票原则


③哨兵模式如何实现故障恢复


故障恢复需要完成如下几步操作:


第一:通过选主机制选择新的Master节点替换掉原来的故障节点


第二:其他的节点成为Slave节点用于主从复制,也就是不变


第三:告知客户端新的master节点地址信息,同时执行必要的脚本来通知系统管理员。


(2)选主机制


过程:


sentinel的选举过程基本上是Raft协议的实现,即所有节点会随机休眠一段时间,然后发起拉票,当某个节点获得的票数超过max(sentinel|/2 + 1), qurom时,该节点就被推选为leader节点。注意是哨兵去从节点里面选。


到底选谁呢?


①根据指定的优先级选择


管理员在启动redis从节点的时候,指定了其优先级,哨兵会先从优先级高的从节点去选择。


②根据数据更新程度选择


优先级相同,所有slave节点复制数据的时候都会记录复制偏移量,值越大说明与master节点的数据更一致。所以哨兵会选择复制偏移量最大的节点。


③根据runid选择:


到了这一步节点的孰优孰劣就没什么区别了,每个节点启动的时候都会有一个唯一的runId, 那么我们就选择runid最小的节点好了。


2、集群策略


(1)基本实现


Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384个槽,分布在不同的Redis数据库中,对于每个进入Redis的键值对,根据CRC16后16384取模hash映射,分配到这16384个slot中的某一个中。


缺点


要保证16384个槽对应的node都正常工作,如果某个node发生故障,那它负责的slots也就失效,整个集群将不能工作。


(2)更好的方案一致性哈希算法,解决扩容问题jedis:


Jedis里面有一致性哈希算法,首先构建一个一致性哈希环的结构。一致性哈希环的大小是我们计算机中无符号整型值的取值范围,

v2-1edea337093a6f663bfb011f4e0b5631_1440w.jpg

将一个数据库服务器虚拟成若干个虚拟节点,把这些虚拟节点的 hash 值放到环上去。在实践中通常是把一个服务器节点虚拟成 200 个虚拟节点,然后把 200 个虚拟节点放到环上。用户的key过来时,顺时针的查找距离它最近的虚拟节点,找到虚拟节点以后,根据映射关系找到真正的物理节点。


3、Redis可能出现的问题


(1)缓存雪崩


原理:


缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机。


解决方案:


1)主从复制

2)根据一些关键数据进行自动降级

3)提前数据预热


(2)缓存穿透


原理:


缓存穿透是指查询一个一不存在的数据。例如:从缓存redis没有命中,需要从mysql数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。


解决办法:


1)布隆过滤器


当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询。


2)缓存空对象


缓存中没有就回去存储层获取,此时即使数据库返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取


(3)缓存击穿


缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,瞬间对数据库的访问压力增大。


解决方案:在查询缓存的时候和查询数据库的过程加锁,只能第一个进来的请求进行执行,当第一个请求把该数据放进缓存中,接下来的访问就会直接集中缓存,防止了缓存击穿。


(4)Redis缓存与数据库数据一致性


不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。举一个例子:


1.如果删除了缓存Redis,还没有来得及写库MySQL,另一个线程就来读取,发现缓存为空,则去数据库中读取数据写入缓存,此时缓存中为脏数据。


2.如果先写了库,在删除缓存前,写库的线程宕机了,没有删除掉缓存,则也会出现数据不一致情况。


怎么保证缓存一致性?:读直接去缓存读,没有的话就读数据库,写直接写数据库,然后失效缓存中对应的数据


第一种方案:延时双删策略+缓存超时设置


在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。


具体的步骤就是:


1)先删除缓存;

2)再写数据库;

3)休眠一段时间;

4)再次删除缓存。


设置缓存过期时间


所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。也就是看到写请求就执行上面的策略。


第二种方案:异步更新缓存(基于订阅binlog的同步机制)


MySQL binlog增量订阅消费+消息队列+增量数据更新到redis

一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息通过消息队列推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。


(5)redis的热key问题如何解决


第一:热Key的概念


所谓热key问题就是,突然有几十万的请求去访问redis上的某个特定key。那么,这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机。那接下来这个key的请求,就会直接怼到你的数据库上,导致你的服务不可用。


第二:怎么发现热key


方法一:凭借业务经验,进行预估哪些是热key 其实这个方法还是挺有可行性的。比如某商品在做秒杀,那这个商品的key就可以判断出是热key。缺点很明显,并非所有业务都能预估出哪些key是热key。


方法二:在客户端进行收集 这个方式就是在操作redis之前,加入一行代码进行数据统计。那么这个数据统计的方式有很多种,也可以是给外部的通讯系统发送一个通知信息。缺点就是对客户端代码造成入侵。


方法三:在Proxy层做收集 有些集群架构是下面这样的,Proxy可以是Twemproxy,是统一的入口。可以在Proxy层做收集上报,但是缺点很明显,并非所有的redis集群架构都有proxy。

v2-8662c5e799d0b774b7d6f21b54fd9283_1440w.jpg

方法四:用redis自带命令


(1)monitor命令,该命令可以实时抓取出redis服务器接收到的命令,然后写代码统计出热key是啥。当然,也有现成的分析工具可以给你使用,比如redis-faina。但是该命令在高并发的条件下,有内存增暴增的隐患,还会降低redis的性能。


(2)hotkeys参数,redis 4.0.3提供了redis-cli的热点key发现功能,执行redis-cli时加上–hotkeys选项即可。但是该参数在执行的时候,如果key比较多,执行起来比较慢。

方法五:自己抓包评估


Redis客户端使用TCP协议与服务端进行交互,通信协议采用的是RESP。自己写程序监听端口,按照RESP协议规则解析数据,进行分析。缺点就是开发成本高,维护困难,有丢包可能性。


以上五种方案,各有优缺点。根据自己业务场景进行选择即可。那么发现热key后,如何解决呢?


三:如何解决


目前业内的方案有两种


(1)利用二级缓存 比如利用ehcache,或者一个HashMap都可以。在你发现热key以后,把热key加载到系统的JVM中。


针对这种热key请求,会直接从jvm中取,而不会走到redis层。假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。

现在假设,你的应用层有50台机器,OK,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从JVM中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。


(2)备份热key 这个方案也很简单。不要让key走到同一台redis上不就行了。我们把这个key,在多个redis上都存一份不就好了。接下来,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。


四、数据库优化


1、主从复制


主从复制一般采用多主多从的方案

v2-dfbd36af8f86a2a172a6d14eaf6bd781_1440w.jpg

(1)多主条件下数据一致性问题


也就是两台主数据库同时更新了数据,以谁的为主


①一种方法是根据时间戳进行判断


最后写入的,也就是时间戳在后面的,覆盖时间戳在前面的。

v2-735a24e3f4b47e50067614e3ac2bce5a_1440w.jpg

②还有一种冲突的解决方案是通过投票进行解决


(2)主节点挂掉了怎么办?


多主多从模式中,几台主服务器相互监督观察,只要对面的有更新自己也更新;

v2-ac05942e836f9edbf524d92ab9ec1608_1440w.jpg


2、分库分表


(1)垂直分库分表


垂直分表意味着对这个表大部分增删改查的操作需要跨库,系统开销太大,一般不使用。垂直分库也会带来事务等问题,解决办法是2pc


(2)水平分库分表


实现方案如下:


1、根据数值范围


按照时间区间或ID区间来切分。例如:将userId为1~9999的记录分到第一个库,10000~20000的分到第二个库,以此类推。


优点:


(1)单表大小可控

(2)天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移

(3)使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。


缺点:


热点数据可能较为集中,造成压力。


2、根据数值取模


例如:将 Customer 表根据 no字段切分到4个库中,余数为0的放到第一个库,余数为1的放到第二个库,以此类推。


优点:

数据分片相对比较均匀,不容易出现热点和并发访问的瓶颈


缺点:

(1)扩容比较麻烦,新增加一个数据库时,需要重新hash


3、分库分表出现的问题


(1)事务一致性问题(垂直分库问题)


分布式事务


当更新内容同时分布在不同库中,垂直分库,不可避免会带来跨库事务问题。跨分片事务也是分布式事务,没有简单的方案,一般可使用"XA协议"和"两阶段提交"处理。


最终一致性


只要在允许的时间段内达到最终一致性即可,可采用事务补偿的方式。也就是基于日志,进行同步;


2、跨节点关联查询 join 问题(垂直分库问题)


1)全局表


全局表,也可看做是"数据字典表",就是系统中所有模块都可能依赖的一些表,为了避免跨库join查询,可以将这类表在每个数据库中都保存一份。这些数据通常很少会进行修改,所以也不担心一致性的问题。


2)字段冗余


利用空间换时间,为了性能而避免join查询。例如:订单表保存userId时候,也将userName冗余保存一份,这样查询订单详情时就不需要再去查询"买家user表"了。


3)数据组装,多次请求


在系统层面,分两次查询,第一次查询的结果集中找出关联数据id,然后根据id发起第二次请求得到关联数据。最后将获得到的数据进行字段拼装。


4)ER分片


关系型数据库中,如果可以先确定表之间的关联关系,并将那些存在关联关系的表记录存放在同一个分片上,


3、跨节点分页、排序、函数问题(水平分库问题)


(1)分页问题


需要先在不同的分片节点中将数据进行排序并返回,然后将不同分片返回的结果集进行汇总和再次排序,最终返回给用户。

v2-054cc5c02dbf81f7600bf27c000aebce_1440w.jpg

(2)函数问题


在使用Max、Min、Sum、Count之类的函数进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。


4、分布式ID问题(水平分库问题)


在分库分表的环境中,数据分布在不同的分片上,不能再借助数据库自增长特性直接生成,否则会造成不同分片上的数据表主键会重复。简单介绍下使用和了解过的几种 ID 生成算法。


(1)Twitter 的 Snowflake(又名“雪花算法”)


这种方案把64-bit分别划分成多段,分开来标示机器、时间等,如下图所示:

v2-ceecb6109ae1bc72ebcef9b3dc1a6c41_1440w.jpg

这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。根据这个算法的逻辑,只需要将这个算法用Java语言实现出来,封装为一个工具方法,那么各个业务应用可以直接使用该工具方法来获取分布式ID,只需保证每个业务应用有自己的工作机器id即可,而不需要单独去搭建一个获取分布式ID的应用。


(2)利用zookeeper生成唯一ID


zookeeper主要通过其znode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。


(3)Redis生成ID


这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。

OK。有问题还望批评指正。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1天前
|
存储 缓存 NoSQL
架构面试题汇总:缓存(2024版)
架构面试题汇总:缓存(2024版)
11 2
|
1天前
|
存储 关系型数据库 MySQL
架构面试题汇总:mysql索引汇总(2024版)
架构面试题汇总:mysql索引汇总(2024版)
9 1
|
1天前
|
存储 缓存 安全
架构面试题汇总:并发和锁(2024版)
架构面试题汇总:并发和锁(2024版)
7 1
|
1天前
|
网络协议 Java 网络安全
架构面试题汇总:网络协议34问(2024版)
架构面试题汇总:网络协议34问(2024版)
14 0
|
1天前
|
存储 关系型数据库 MySQL
架构面试题汇总:40道题吃透mysql(2024版)
架构面试题汇总:40道题吃透mysql(2024版)
11 0
|
1天前
|
存储 监控 算法
架构面试题汇总:JVM全套(2024版)
架构面试题汇总:JVM全套(2024版)
7 0
|
1天前
|
存储 缓存 监控
架构面试题汇总(一)
架构面试题汇总(一)
8 0
|
3天前
|
缓存 运维 监控
探索微服务架构中的API网关模式
在微服务架构的海洋中,API网关是连接客户端与众多微服务群岛之间的桥梁。本文将深入探讨API网关的设计原则、核心功能以及在现代软件架构中的关键作用,同时分析其在实际应用中的效益和面临的挑战。
|
2天前
|
存储 监控 负载均衡
深入理解微服务架构中的服务发现机制
【6月更文挑战第25天】在微服务架构中,服务发现是确保各独立服务组件能够高效、可靠通信的关键环节。本文将探讨服务发现的基本原理、核心组件以及在现代云原生应用中的最佳实践,旨在为读者提供一套系统化理解和实现服务发现机制的指导思路。
|
3天前
|
存储 消息中间件 API
“论微服务架构及其应用”写作框架,软考高级,系统架构设计师
论微服务架构及其应用近年来,随着互联网行业的迅猛发展,公司或组织业务的不断扩张,需求的快速变化以及用户量的不断增加,传统的单块(Monolithic)软件架构面临着越来越多的挑战,已逐渐无法适应互联网时代对软件的要求。在这一背景下,微服务架构模式(MicroserviceArchitecturePattern)逐渐流行,它强调将单一业务功能开发成微服务的形式,每个微服务运行在一个进程中;采用HTTP等通用协议和轻量级API实现微服务之间的协作与通信。这些微服务可以使用不同的开发语言以及不同数据存储技术,能够通过自动化部署工具独立发布,并保持最低限制的集中式管理。

热门文章

最新文章