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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 这篇文章主要是说在进化的过程中可能会遇到的问题以及如何去解救这些问题。

三、缓存数据库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月前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
19天前
|
存储 Prometheus Cloud Native
分布式系统架构6:链路追踪
本文深入探讨了分布式系统中的链路追踪理论,涵盖追踪与跨度的概念、追踪系统的模块划分及数据收集的三种方式。链路追踪旨在解决复杂分布式系统中请求流转路径不清晰的问题,帮助快速定位故障和性能瓶颈。文中介绍了基于日志、服务探针和边车代理的数据收集方法,并简述了OpenTracing、OpenCensus和OpenTelemetry等链路追踪协议的发展历程及其特点。通过理解这些概念,可以更好地掌握开源链路追踪框架的使用。
76 41
|
3天前
|
存储 缓存 安全
分布式系统架构7:本地缓存
这是小卷关于分布式系统架构学习的第10篇文章,主要介绍本地缓存的基础理论。文章分析了引入缓存的利弊,解释了缓存对CPU和I/O压力的缓解作用,并讨论了缓存的吞吐量、命中率、淘汰策略等属性。同时,对比了几种常见的本地缓存工具(如ConcurrentHashMap、Ehcache、Guava Cache和Caffeine),详细介绍了它们的访问控制、淘汰策略及扩展功能。
21 6
|
6天前
|
存储 关系型数据库 分布式数据库
[PolarDB实操课] 01.PolarDB分布式版架构介绍
《PolarDB实操课》之“PolarDB分布式版架构介绍”由阿里云架构师王江颖主讲。课程涵盖PolarDB-X的分布式架构、典型业务场景(如实时交易、海量数据存储等)、分布式焦点问题(如业务连续性、一致性保障等)及技术架构详解。PolarDB-X基于Share-Nothing架构,支持HTAP能力,具备高可用性和容错性,适用于多种分布式改造和迁移场景。课程链接:[https://developer.aliyun.com/live/253957](https://developer.aliyun.com/live/253957)。更多内容可访问阿里云培训中心。
[PolarDB实操课] 01.PolarDB分布式版架构介绍
|
29天前
|
设计模式 存储 算法
分布式系统架构5:限流设计模式
本文是小卷关于分布式系统架构学习的第5篇,重点介绍限流器及4种常见的限流设计模式:流量计数器、滑动窗口、漏桶和令牌桶。限流旨在保护系统免受超额流量冲击,确保资源合理分配。流量计数器简单但存在边界问题;滑动窗口更精细地控制流量;漏桶平滑流量但配置复杂;令牌桶允许突发流量。此外,还简要介绍了分布式限流的概念及实现方式,强调了限流的代价与收益权衡。
74 11
|
1月前
|
设计模式 监控 Java
分布式系统架构4:容错设计模式
这是小卷对分布式系统架构学习的第4篇文章,重点介绍了三种常见的容错设计模式:断路器模式、舱壁隔离模式和重试模式。断路器模式防止服务故障蔓延,舱壁隔离模式通过资源隔离避免全局影响,重试模式提升短期故障下的调用成功率。文章还对比了这些模式的优缺点及适用场景,并解释了服务熔断与服务降级的区别。尽管技术文章阅读量不高,但小卷坚持每日更新以促进个人成长。
53 11
|
1月前
|
消息中间件 存储 安全
分布式系统架构3:服务容错
分布式系统因其复杂性,故障几乎是必然的。那么如何让系统在不可避免的故障中依然保持稳定?本文详细介绍了分布式架构中7种核心的服务容错策略,包括故障转移、快速失败、安全失败等,以及它们在实际业务场景中的应用。无论是支付场景的快速失败,还是日志采集的安全失败,每种策略都有自己的适用领域和优缺点。此外,文章还为技术面试提供了解题思路,助你在关键时刻脱颖而出。掌握这些策略,不仅能提升系统健壮性,还能让你的技术栈更上一层楼!快来深入学习,走向架构师之路吧!
67 11
|
1月前
|
自然语言处理 负载均衡 Kubernetes
分布式系统架构2:服务发现
服务发现是分布式系统中服务实例动态注册和发现机制,确保服务间通信。主要由注册中心和服务消费者组成,支持客户端和服务端两种发现模式。注册中心需具备高可用性,常用框架有Eureka、Zookeeper、Consul等。服务注册方式包括主动注册和被动注册,核心流程涵盖服务注册、心跳检测、服务发现、服务调用和注销。
92 12
|
3月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
1月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
163 5

热门文章

最新文章