当前位置:   article > 正文

微服务常见面试题及解答_微服务面试题,2024年最新我的阿里春招之路分享

微服务面试

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

// 获取初始数据版本
response, err := client.Get(context.Background(), “/config/alert_threshold”)
if err != nil {
// 处理错误
}

initialRevision := response.Header.Revision

  1. 定期检查数据版本:在一个循环中,可以定期检查etcd键的数据版本是否发生了变化。可以使用Watch API来实现这一点:

// 在循环中定期检查数据版本
for {
response, err := client.Get(context.Background(), “/config/alert_threshold”)

if err != nil {
// 处理错误
}

currentRevision := response.Header.Revision

if currentRevision > initialRevision {
// 数据版本发生了变化,触发报警逻辑
// 可以发送警报通知,执行操作等
// 更新初始数据版本以便下一次检查
initialRevision = currentRevision
}

// 休眠一段时间后再次检查
time.Sleep(time.Second * 10)
}

  1. 报警逻辑:在检测到数据版本变化后,可以触发报警逻辑,例如发送警报通知、记录事件、执行自动化操作等。

这个示例中,我们使用etcd的数据版本来监视特定键的变化,并在变化时触发报警。这可以用于监控配置更改、服务状态等。请注意,此示例是一个简化的示例,实际场景中可能需要更多的错误处理和安全性措施。此外,确保etcd服务器配置安全,以免未经授权的访问。

4.8 怎么使用Etcd做分布式锁?

参考1:基于Etcd的分布式锁实现原理及方案

4.8.1 分布式锁的基本原理

分布式环境下,多台机器上多个进程对同一个共享资源(数据、文件等)进行操作,如果不做互斥,就有可能出现“余额扣成负数”,或者“商品超卖”的情况。为了解决这个问题,需要分布式锁服务。首先,来看一下分布式锁应该具备哪些条件。

  1. 互斥性:在任意时刻,对于同一个锁,只有一个客户端能持有,从而保证一个共享资源同一时间只能被一个客户端操作;
  2. 安全性:即不会形成死锁,当一个客户端在持有锁的期间崩溃而没有主动解锁的情况下,其持有的锁也能够被正确释放,并保证后续其它客户端能加锁;
  3. 可用性:当提供锁服务的节点发生宕机等不可恢复性故障时,“热备” 节点能够接替故障的节点继续提供服务,并保证自身持有的数据与故障节点一致。
  4. 对称性:对于任意一个锁,其加锁和解锁必须是同一个客户端,即客户端A不能把客户端B加的锁给解了。
4.8.2 Etcd 实现分布式锁的基础

Etcd的高可用性、强一致性不必多说,前面章节中已经阐明,本节主要介绍Etcd支持的以下机制:Watch机制、Lease机制、Revision机制和Prefix机制,正是这些机制赋予了Etcd实现分布式锁的能力。

  1. Lease机制:即租约机制(TTL,TimeToLive),Etcd可以为存储的Key-Value对设置租约,当租约到期,Key-Value将失效删除;同时也支持续约,通过客户端可以在租约到期之前续约,以避免Key-Value对过期失效。Lease机制可以保证分布式锁的安全性,为锁对应的Key配置租约,即使锁的持有者因故障而不能主动释放锁,锁也会因租约到期而自动释放。
  2. Revision机制:每个Key带有一个Revision号,每进行一次事务便加一,因此它是全局唯一的,如初始值为0,进行一次put(key,value)KeyRevision变为1,同样的操作,再进行一次,Revision变为2;换成key1进行put(key1,value)操作,Revision将变为3;这种机制有一个作用:通过Revision的大小就可以知道写操作的顺序。在实现分布式锁时,多个客户端同时抢锁,根据Revision号大小依次获得锁,可以避免“羊群效应”(也称“惊群效应”),实现公平锁。
  3. Prefix机制:即前缀机制,也称目录机制,例如,一个名为/mylock的锁,两个争抢它的客户端进行写操作,实际写入的Key分别为:key1=“/mylock/UUID1”key2=“/mylock/UUID2”,其中,UUID表示全局唯一的ID,确保两个Key的唯一性。很显然,写操作都会成功,但返回的Revision不一样,那么,如何判断谁获得了锁呢?通过前缀“/mylock”查询,返回包含两个Key-Value对的Key-Value列表,同时也包含它们的Revision,通过Revision大小,客户端可以判断自己是否获得锁,如果抢锁失败,则等待锁释放(对应的Key被删除或者租约过期),然后再判断自己是否可以获得锁。
  4. Watch机制:即监听机制,Watch机制支持监听某个固定的Key,也支持监听一个范围(前缀机制),当被监听的Key或范围发生变化,客户端将收到通知;在实现分布式锁时,如果抢锁失败,可通过Prefix机制返回的Key-Value列表获得Revision比自己小且相差最小的Key(称为Pre-Key),对Pre-Key进行监听,因为只有它释放锁,自己才能获得锁,如果监听到Pre-KeyDELETE事件,则说明Pre-Key已经释放,自己已经持有锁。
4.8.3 Etcd 实现分布式锁
4.8.3.1 基于Etcd的分布式锁业务流程

下面描述了使用Etcd实现分布式锁的业务流程,假设对某个共享资源设置的锁名为:/anyrtc/mylock

  1. 准备:客户端连接Etcd,以/anyrtc/mylock为前缀创建全局唯一的Key,假设第一个客户端对应的Key=“/anyrtc/mylock/UUID1”,第二个为Key=“/anyrtc/mylock/UUID2”;客户端分别为自己的Key创建租约Lease,租约的长度根据业务耗时确定,假设为15s。
  2. 创建定时任务作为租约的“心跳”:在一个客户端持有锁期间,其它客户端只能等待,为了避免等待期间租约失效,客户端需创建一个定时任务作为“心跳”进行续约。此外,如果持有锁期间客户端崩溃,心跳停止,Key将因租约到期而被删除,从而锁释放,避免死锁。
  3. 客户端将自己全局唯一的Key写入Etcd:进行Put操作,将步骤1中创建的Key绑定租约写入Etcd,根据EtcdRevision机制,假设两个客户端Put操作返回的Revision分别为1、2,客户端需记录Revision用以接下来判断自己是否获得锁。
  4. 客户端判断是否获得锁:客户端以前缀/anyrtc/mylock读取Key-Value列表(Key-Value中带有Key对应的Revision),判断自己KeyRevision是否为当前列表中最小的,如果是则认为获得锁;否则监听列表中前一个Revision比自己小的Key的删除事件,一旦监听到删除事件或者因租约失效而删除的事件,则自己获得锁。
  5. 执行业务:获得锁后,操作共享资源,执行业务代码。
  6. 释放锁:完成业务流程后,删除对应的Key释放锁。
4.8.3.2 基于Etcd的分布式锁的原理图

根据业务流程,基于Etcd的分布式锁示意图如下:
在这里插入图片描述

4.9 Etcd常用的几个接口?

  1. gRPCAPIEtcd的主要接口是gRPC接口,它提供了对Etcd的完整功能访问。通过gRPC接口,可以执行诸如设置键值对、获取键值对、触发事务操作等操作。gRPC是一种高性能的远程过程调用(RPC)框架,可以用多种编程语言来使用EtcdAPI
  2. HTTP/gRPC代理接口Etcd还提供了HTTPgRPC代理接口,允许通过HTTPgRPC请求来访问Etcd。这使得使用EtcdRESTfulAPI变得更加方便,特别是在不支持gRPC的环境中。
  3. etcdctl命令行工具Etcd还附带了一个命令行工具称为etcdctl,它提供了一个命令行界面,允许与Etcd集群交互。可以使用etcdctl来设置、获取、删除键值对,以及执行其他管理和查询操作。
  4. WatchAPIEtcdWatchAPI允许监视键值对的更改。可以设置观察器以接收有关特定键的更改通知,这对于实时应用程序和配置管理非常有用。
  5. 事务APIEtcd支持事务操作,可以使用事务API执行一系列操作,要么全部成功,要么全部失败。这确保了多个操作的一致性。
  6. 健康检查接口Etcd提供了健康检查接口,允许检查Etcd集群的运行状况和健康状态。这对于监控和运维非常有用。
  7. 授权和认证接口Etcd支持基于TLS的安全通信,并提供了授权和认证接口,可以配置访问控制策略,以限制对Etcd数据的访问。

这些接口组合在一起,使Etcd成为一个强大的分布式数据存储系统,适用于多种用例,包括服务发现、配置管理、分布式锁、选举等。不同的接口提供了不同级别的灵活性和功能,以满足不同应用程序的需求。

※4.10 Etcd的过期策略是什么?

Etcd的过期策略基于TTLTime-To-Live,生存时间)机制,它允许为存储在Etcd中的键值对设置生存时间,当键值对的生存时间到期时,它将自动从Etcd中删除。这个机制是Etcd中处理过期的主要策略。

具体来说,当使用Etcdput操作来设置一个键值对时,可以为该键值对指定TTL值,如下所示:

etcdctl put key value --ttl 60

上述命令将keyvalue存储在Etcd中,并设置了60秒的TTL。当60秒钟过去后,如果不更新该键值对的TTL或删除该键值对,Etcd将自动将其删除。

过期策略的主要用途之一是在配置管理中,以确保配置数据在一定时间后自动失效,从而触发配置的刷新或重新加载。过期键值对的删除是Etcd自身的维护任务,不需要用户手动干预。

需要注意的是,EtcdTTL机制仅适用于具有TTL设置的键值对,如果未设置TTL,键值对将永久保存在Etcd中。此外,Etcd可以配置不同的自动清理策略来处理过期数据的清理,例如自动清理已过期的键值对以释放磁盘空间。

总结:
Etcd的过期策略基于TTL机制,允许设置键值对的生存时间,当生存时间到期时,Etcd将自动删除这些键值对。这对于自动数据管理和配置刷新非常有用。

4.11 Etcd中的数据格式是什么样的?

参考:etcdv3与etcdv2特性比较

etcd存储的数据是一个Key-Value格式的存储,etcdv2key是一个递归的文件目录结构,在v3版本中的键改成了扁平化的数据结构,更加简洁,v3中支持前缀查询,在etcdctl get key时可以加上前缀查询选项--prefix,从而达到v2的目录结构查询效果。

5 go-zero

5.1 go-zero框架的架构是什么样的?

在这里插入图片描述

5.2 go-zero的api层主要的作用是什么?

API里有grpc-gateway,可以将HTTP协议转为GRPC,同时还有鉴权,加解密,其他的和RPC端一致。

5.3 go-zero的负载均衡

参考1:负载均衡

负载均衡:
go-zero使用的负载均衡是P2C(Power of Two Choices)P2C(Power of Two Choices)算法是一种基于随机化的负载均衡算法,由Jeff DeanLuiz Andre Barroso在2001年提出。

P2C算法是一种改进的随机算法,它可以避免最劣选择和负载不均衡的情况。P2C算法的核心思想是:从所有可用节点中随机选择两个节点,然后根据这两个节点的负载情况选择一个负载较小的节点。这样做的好处在于,如果只随机选择一个节点,可能会选择到负载较高的节点,从而导致负载不均衡;而选择两个节点,则可以进行比较,从而避免最劣选择。

P2C算法的实现步骤如下:

  1. 将所有可用节点按照负载大小排序,从小到大排列。
  2. 随机选择两个节点。
  3. 选择两个节点中负载较小的节点,作为负载均衡器选择的节点。 P2C算法的优点在于,它可以在保证负载均衡的前提下,选择负载更小的节点,从而提高系统的性能和可靠性。此外,P2C算法的实现简单,不需要太多的计算和存储资源,因此在实际应用中被广泛采用。

其他常见负载均衡算法:

  1. 基于轮询​:基于轮询的负载均衡算法是一种简单的负载均衡算法,将请求依次分发给每个节点,循环重复这个过程。这种算法适用于系统中的节点处理能力相同的情况,但是在实际应用中,由于节点的处理能力可能不同,所以需要进行改进。
  2. 基于权重:基于权重的负载均衡算法是一种考虑节点处理能力的算法,将请求分配给具有更高权重的节点。权重可以根据节点的处理能力进行设置。例如,处理能力更强的节点可以设置更高的权重,从而处理更多的请求。
  3. 基于最少连接数:基于最少连接数的负载均衡算法是一种考虑节点负载情况的算法,将请求分配给连接数最少的节点。这种算法适用于连接时间较长的应用场景,因为连接时间较长的节点可能会影响其他节点的连接数。
  4. 基于IP(HASH)散列:基于IP散列的负载均衡算法是一种根据请求的源IP地址进行分配的算法。它将请求的源IP地址进行散列,然后根据散列结果将请求分配给相应的节点。这种算法可以确保来自同一IP地址的请求被分配到同一节点上,从而保持会话的一致性

5.4 go-zero的网关

参考1:网关
go-zero使用的网关是gRPC gatewaygo-zero中的gRPC网关是一个HTTP服务器,它将RESTful API转换为gRPC请求,然后将gRPC响应转换为RESTful API。大致流程如下:

  1. proto文件中解析出gRPC服务的定义。
  2. 从 配置文件中解析出gRPC服务的HTTP映射规则。
  3. 根据gRPC服务的定义和HTTP映射规则,生成gRPC服务的HTTP处理器。
  4. 启动HTTP服务器,处理 HTTP请求。
  5. HTTP请求转换为gRPC请求。
  6. gRPC响应转换为HTTP响应。
  7. 返回HTTP响应。

5.5 go-zero断路器

参考1:断路器

断路器又叫熔断器,是一种保护机制,用于保护服务调用链路中的服务不被过多的请求压垮。当服务调用链路中的某个服务出现异常时,断路器会将该服务的调用请求拒绝,从而保护服务调用链路中的其他服务不被压垮。

比较知名的熔断器算法有HystrixSentinel,它们都是通过统计服务调用的成功率和响应时间来判断服务是否可用,从而实现熔断的功能。

go-zero内置了熔断器组件breaker.Breaker(自研的),go-zero中采用滑动窗口来进行数据采集,目前是以10s为一个窗口,单个窗口有40个桶,然后将窗口内采集的数据使用google sre算法计算是否开启熔断,详情可参考https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101

brreaker.Breaker中,提供了DoDoWithAcceptableDoWithFallbackDoWithFallbackAcceptable四个方法,分别对应不同的场景。 开发者可以通过breaker直接调用这些方法,或者创建一个breaker实例来调用,两种方法均可,直接调用其实本质上也会以name作为唯一key去获取/创建一个breaker实例。

  1. Do方法:默认按照错误率来判断服务是否可用,不支持指标自定义,也不支持错误回调。
  2. DoWithAcceptable:支持自定义的采集指标,可以自主控制哪些情况是可以接受,哪些情况是需要加入熔断指标采集窗口的。
  3. DoWithFallback:默认采用错误率来判断服务是否可用,不支持指标自定义,但是支持熔断回调。
  4. DoWithFallbackAcceptable:支持采集指标自定义,也支持熔断回调。

以上方法都是通过name来获取/创建一个breaker实例,即熔断器名称相同的同属于一个熔断器控制,如果需要自定义breaker的配置,可以通过NewBreaker方法来创建一个breaker实例,通过实例可以精确控制具体情况是放过还是拒绝。

go-zero中,开发者不需要对请求单独进行熔断处理,该功能已经集成到了框架中,因此开发者无需关系。

  1. HTTP以请求方法+路由作为统计维度,用HTTP状态码500作为错误采集指标进行统计,详情可参考:breakerhandler.go
  2. gRPC客户端以RPC方法名作为统计维度,用gRPC的错误码为:codes.DeadlineExceededcodes.Internalcodes.Unavailablecodes.DataLosscodes.Unimplemented作为错误采集指标进行统计,详情可参考:breakerinterceptor.go
  3. gRPC服务端以RPC方法名称作为统计维度,用gRPC的错误作为错误采集指标进行统计,详情可参考:breakerinterceptor.go

5.6 go-zero限流

参考1:限流(并发控制)

限流器是一种服务治理能力,用于限制服务的并发调用量,以保护服务的稳定性。限流分为restgrpc

限流一般有单节点限流集群限流(将限流数值对集群节点数求平均值,其本质还是单节点限流)分布式限流

限流,倒不如说是并发控制更贴切。

*********************

5.7 在这个项目主要负责的内容?

这一块还需要再整理逻辑,细化具体做了哪些。

5.8 处理的数据量大吗?一般是多少?

– 208服务器每秒最多,目前出现在 2023-10-09 14:09:54,条数是1813

SELECT formatDateTime(capture_time,‘%Y-%m-%d %H:%M:%S’) dates,COUNT(id) nums FROM http_capture GROUP BY dates ORDER BY nums DESC;

5.9 数据量比较大的话该如何处理?

使用单个asynq来处理其实不对,如果使用asynq来进行数据的分发的话在数据量比较大的情况下,Redis会塞满数据,导致瘫痪。

应该利用kafka的消费者组的特点,这样可以充分利用kafka单个消费者组多个消费者并发处理的性能。

5.10 为什么使用kafka而不是RabbitMQ?

适用于实时大数据,并且可以快速扩展,并发处理(也就是增加多个分区,同时再增加多个消费者)。

6 分布式

6.1 分布式事务

参考1:go-zero学习 第六章 分布式事务dtm
参考2:DTM开源项目文档:官方文档

seata-golang的分布式事务。
参考3:seata-golang 接入指南
参考4:官网
参考5:【微服务架构】分布式事务

分布式事务是一种涉及多个独立组件或服务的事务操作,需要确保在不同节点上的操作要么全部成功,要么全部失败,以维护数据的一致性。

常见事务模式:

  • msg:二阶段消息,适合不需要回滚的全局事务。
  • saga:适合需要支持回滚的全局事务。
  • tcc:适合一致性要求较高的全局事务。
  • xa:适合性能要求不高,没有行锁争抢的全局事务。
6.1.1 二阶段消息事务

二阶段消息事务(Two-Phase Commit,2PC)是一种用于实现分布式事务的协议,它通常用于确保在不同节点上的数据库或服务上的事务操作的一致性。适合不需要回滚的全局事务。
二阶段消息事务通常包含以下两个阶段:

  1. 准备阶段(Prepare Phase): 在这个阶段,事务协调者(通常是分布式系统中的一个中心节点)向所有参与者(各个节点或服务)发送一个准备请求。每个参与者会执行以下操作:
  2. 检查自己是否能够成功完成该事务,包括检查事务操作是否合法和是否有足够的资源。
  3. 如果参与者准备好了,它会向协调者发送一个“同意”消息。
  4. 如果参与者不能准备好,它会向协调者发送一个“中止”消息。
  5. 提交阶段(Commit Phase): 如果所有参与者都在准备阶段发送了“同意”消息,那么协调者会向所有参与者发送一个提交请求。每个参与者在接收到提交请求后,会执行以下操作:
  6. 执行事务的实际操作,将数据持久化或执行其他必要的操作。
  7. 一旦操作成功,它会向协调者发送一个“已提交”消息,表示它已成功提交事务。
  8. 如果操作失败,参与者会向协调者发送一个“中止”消息,表示事务无法提交。
  9. 完成和恢复: 一旦协调者收到所有参与者的“已提交”消息,它会将事务标记为已提交,并通知客户端事务已成功完成。如果有任何一个参与者发送了“中止”消息或在规定时间内没有响应,协调者会将事务标记为已中止,并通知客户端事务失败。

2PC的关键特点是在准备阶段引入了一个同意/中止机制,以确保所有参与者都可以成功完成操作。然而,2PC也存在一些问题,如协调者单点故障、性能瓶颈和阻塞等问题。因此,一些分布式系统会选择使用三阶段提交(Three-Phase Commit,3PC)等更复杂但更可靠的分布式事务协议来解决这些问题。

6.1.2 saga事务

Saga是一种通过将大型事务拆分成多个小事务并使用补偿操作来维护数据一致性的方式。Saga模式的主要目标是最小化分布式事务的范围,以提高性能和可伸缩性,并降低事务中断的风险。适合需要支持回滚的全局事务。

Saga事务包含以下关键特点和概念:

  1. 拆分事务Saga将大型事务拆分为多个小事务,每个小事务涵盖了系统中的一个服务或组件。这些小事务可以在不同的节点上独立执行,从而减小了分布式事务的范围。
  2. 补偿操作:每个小事务都有一个关联的补偿操作,用于撤销或修复该事务的效果。补偿操作通常是与原事务操作相反的操作。如果某个小事务失败,Saga会逆序执行之前成功的小事务的补偿操作,以维护数据一致性。
  3. 事务协调Saga中的事务协调器(Transaction Coordinator)负责协调各个小事务的执行和补偿。它跟踪事务的状态,并在必要时触发补偿操作。
  4. 局部数据一致性Saga模式接受局部数据不一致性,即在某个小事务成功后,该事务的数据可能在稍后的补偿操作中被撤销或修改。这是Saga的一种权衡,旨在提高性能和可伸缩性。
  5. 长时间执行Saga事务可以在较长的时间范围内执行,因为它们可以在多个步骤之间等待用户或外部事件的响应。这使得Saga适用于需要长时间执行的业务流程。

Saga模式的一个重要优势是允许系统部分失败而不影响整体一致性。如果某个小事务失败,系统可以继续运行并通过执行补偿操作来纠正问题。这降低了系统中断的风险,尤其在大规模和复杂的分布式系统中。

需要注意的是,实施Saga模式可能会引入复杂性,例如管理补偿操作和确保系统可恢复性。因此,在选择使用Saga模式时,需要权衡其优势和复杂性,以确保它适合特定的应用场景和系统架构。

6.1.3 tcc事务

TCC(Try-Confirm-Cancel)是一种分布式事务模式,TCC事务将大事务拆分成三个阶段:尝试(Try)、确认(Confirm)和取消(Cancel)来实现分布式事务,以确保在不同节点上的操作要么全部成功,要么全部失败。特别适用于需要严格保证数据一致性的分布式系统。

以下是TCC事务模式的关键概念和步骤:

  1. Try阶段(尝试阶段): 在Try阶段,事务协调器(Transaction Coordinator)会向所有参与者(各个节点或服务)发送一个尝试请求,询问它们是否愿意执行事务。每个参与者会执行以下操作:
  • 尝试执行与该事务相关的操作,但不会将其结果持久化。这个阶段用于检查是否满足执行事务的前提条件,例如检查资源是否足够、锁定资源等。
  1. Confirm阶段(确认阶段): 如果所有参与者在Try阶段成功执行了相关操作并确认可以执行事务,那么事务协调器会向所有参与者发送确认请求。每个参与者会执行以下操作:
  • 确认之前Try阶段执行的操作,并将结果持久化。这个阶段将确保事务操作的执行。
  1. Cancel阶段(取消阶段): 如果在Try阶段有任何参与者失败或拒绝执行事务,或者在Confirm阶段的某些参与者失败,那么事务协调器会向所有参与者发送取消请求。每个参与者会执行以下操作:
  • 撤销或回滚之前Try阶段执行的操作,以确保事务操作的撤销。

TCC模式的关键特点包括:

  1. TCC模式将事务的操作拆分为TryConfirmCancel阶段,以确保在不同节点上的操作具有原子性。
  2. TCC要求参与者实现TryConfirmCancel操作,以处理不同阶段的事务状态。
  3. TCC允许在Cancel阶段回滚事务操作,从而确保数据一致性,即使在部分节点失败的情况下也能恢复到一致状态。

需要注意的是,TCC模式引入了额外的复杂性,包括事务协调、状态管理和异常处理等方面的问题。因此,TCC模式通常适用于需要强一致性和精确控制的分布式系统场景,但也需要谨慎考虑其实施和维护的复杂性。一些分布式事务管理框架(如Seata)提供了TCC模式的支持,以简化实施过程。

6.1.4 xa事务

适合性能要求不高,没有行锁争抢的全局事务。

XA(eXtended Architecture)事务是一种用于管理分布式事务的协议,它允许多个资源管理器(通常是数据库或消息队列)协同工作以确保分布式事务的原子性、一致性、隔离性和持久性(ACID属性)。XA协议提供了两阶段提交(Two-Phase Commit,2PC)协议的标准化实现,但它也可以扩展到支持三阶段提交(Three-Phase Commit,3PC)以提高可靠性。

以下是XA事务的主要概念和步骤:

  1. 事务管理器(Transaction Manager):事务的协调和管理是由事务管理器来完成的。它是分布式事务的核心组件,负责协调多个资源管理器和确保事务的一致性。
  2. 资源管理器(Resource Manager):资源管理器是与各种资源(如数据库、消息队列、文件系统等)交互的组件。每个资源管理器负责事务的处理和数据持久性。
  3. 全局事务(Global Transaction):全局事务是由多个资源管理器参与的分布式事务。它可以包括一个或多个分支事务。
  4. 分支事务(Branch Transaction):分支事务是全局事务中的一个局部操作,通常对应于一个资源管理器上的事务操作。

XA事务的典型流程如下:

  1. 准备阶段(Prepare Phase):在此阶段,全局事务协调器向每个资源管理器发送准备请求。资源管理器会将事务操作记录在一个准备日志中,但不会对其进行实际提交。如果所有资源管理器都成功地准备好了,协调器会进入下一阶段。
  2. 提交阶段(Commit Phase):在此阶段,全局事务协调器向每个资源管理器发送提交请求。资源管理器会根据准备阶段的日志来执行实际的提交操作。如果任何一个资源管理器在此阶段失败,协调器会发送回滚请求,以确保事务的原子性。
  3. 回滚阶段(Rollback Phase,可选):如果在提交阶段发生错误,全局事务协调器会向每个资源管理器发送回滚请求,以撤销之前的事务操作。

XA事务的主要优点是它提供了一种标准化的分布式事务管理方法,确保了ACID属性。然而,XA事务也有一些缺点,包括性能开销、对资源管理器的依赖性和可能的单点故障问题。因此,使用XA事务需要仔细考虑系统的需求和复杂性。在某些情况下,更轻量级的分布式事务模式(如TCCSaga)可能更适合。

6.1.5 分布式事务和消息中间件应用场景区别

分布式事务主要是订单、商城,需要立即结束订单、库存、支付等,否则业务无法继续,耦合度较高,其中一个环节失败,则整个业务失败。

消息中间件是解耦,各管各的。

6.2 分布式锁

参考1:微服务中的分布式锁方案
参考2:一文彻底弄清楚分布式锁
参考3:分布式锁有哪些解决方案

6.2.1 基于数据库实现分布式锁

基于数据库的分布式锁是通过在数据库中创建一个特殊的记录或行来表示锁的状态,从而确保在任何给定时刻只有一个客户端能够获得该锁,以执行特定的操作。这可以用于避免多个客户端同时修改相同的数据或执行相同的任务,从而确保数据的一致性和可靠性。

基于数据库的分布式锁的一般步骤和一些考虑因素:

  1. 选择数据库引擎: 选择一个适合应用程序的数据库引擎。常见的选择包括MySQLPostgreSQLRedis等。不同的数据库引擎可能提供不同的特性和性能。
  2. 创建锁记录: 在选定的数据库中创建一个专门用于存储锁状态的表格或键值对。这个表格或键值对应该包含以下信息:
  • 锁的名称或标识符
  • 锁的持有者(客户端标识)
  • 锁的到期时间(可选)
  1. 获取锁: 当客户端需要获取锁时,它会尝试在锁表中插入一条记录,或者更新一个已存在的记录,来表示它正在持有该锁。这可以使用数据库事务来保证原子性操作。
  2. 释放锁: 当客户端完成任务或需要释放锁时,它会从锁表中删除相关记录或将锁的持有者字段重置为空。同样,使用数据库事务来确保原子性操作。
  3. 设置锁的超时(可选): 有时候,为了避免死锁或客户端长时间持有锁而无响应,可以为锁设置一个超时时间。客户端需要在规定时间内完成任务并释放锁,否则锁将自动过期。
  4. 处理竞争和失败情况: 当多个客户端尝试获取同一个锁时,可能会出现竞争情况。需要编写逻辑来处理这些情况,通常使用数据库的事务隔离级别来确保数据的一致性。
  5. 监控和故障恢复: 实施监控来跟踪锁的使用情况,以及故障恢复策略,以处理可能的锁冲突或数据库故障。

注意:
基于数据库的分布式锁可以工作,但也需要谨慎考虑性能和可伸缩性问题。锁表可能成为瓶颈,尤其是在高并发环境中。在某些情况下,可能需要考虑使用更高级的分布式锁管理工具,例如ZooKeeperetcdRedis的分布式锁等,以减轻数据库的负载。

6.2.2 基于Redis实现分布式锁

基于Redis的分布式锁是利用Redis作为中心化的锁管理器。Redis是一个高性能的内存数据库,具有原子操作和持久性的特性,适用于实现分布式锁。以下是如何创建基于Redis的分布式锁的一般步骤:

  1. 选择一个唯一标识符:为了创建锁,需要选择一个唯一的标识符,通常是一个字符串,用于标识锁的名称。
  2. 尝试获取锁:客户端尝试在Redis中设置一个特定的键值对,其中键是锁的名称,值是客户端的标识符(通常是一个唯一的标识符,如UUID)。这个设置操作需要使用RedisSETNX(Set if Not eXists)命令,以确保只有一个客户端能够成功设置锁。如果客户端成功设置了锁,表示获取锁成功。

SET lock_name client_identifier NX PX lock_timeout

各命令含义:

  • lock_name: 锁的名称
  • client_identifier: 客户端的唯一标识符
  • NX: 表示仅在键不存在时设置锁
  • PX: 设置锁的超时时间(毫秒)
  1. 处理竞争和超时情况: 如果多个客户端同时尝试获取锁,只有一个客户端将成功,其他客户端将获取失败。可以使用轮询或等待一段时间后重新尝试获取锁,但要小心避免死锁。
  2. 释放锁: 当客户端完成任务或需要释放锁时,它可以使用Redis的DEL命令来删除锁的键,以释放锁。

DEL lock_name

  1. 设置锁的超时时间(可选): 可以为锁设置一个自动过期时间,以确保即使客户端崩溃或意外终止,锁也会在一定时间后自动释放。这可以使用EXPIRE命令或在设置锁时使用PX参数来实现。

EXPIRE lock_name lock_timeout

  1. 处理异常和故障情况: 考虑如何处理客户端崩溃、锁的超时以及其他异常情况。可以使用监控和定期检查锁是否过期来处理这些情况。

基于Redis的分布式锁是一种简单而有效的方式来实现分布式锁,但也需要小心处理竞争和故障情况。还需要仔细选择锁的名称以及设置超时时间,以适应应用程序需求。请注意,虽然Redis是一个快速的内存数据库,但要确保Redis服务器的高可用性和稳定性,以防止锁的单点故障。

6.2.3 基于zookeeper实现分布式锁

基于ZooKeeper的分布式锁是一种强大且高度可靠的分布式锁实现方法。ZooKeeper是一个分布式协调服务,提供了分布式锁所需的原语。以下是如何创建基于ZooKeeper的分布式锁的一般步骤:

  1. 创建ZooKeeper连接:客户端需要首先创建到ZooKeeper集群的连接,通常使用ZooKeeper客户端库来实现。这个连接将用于创建锁节点以及监听锁的释放。
  2. 创建锁节点:每个客户端尝试获取锁时,都在ZooKeeper中创建一个独立的有序临时节点。节点的路径通常包含锁的名称。
  3. 获取锁:客户端通过在ZooKeeper上创建一个有序的临时节点来表示它想要获取锁。然后,它检查自己创建的节点是否是当前锁路径下最小的节点。如果是,表示客户端获得了锁。
  4. 处理竞争和等待:如果多个客户端尝试获取同一个锁,只有一个客户端将成功,其他客户端将进入等待状态。等待的客户端可以监听前一个节点的删除事件,一旦前一个节点被删除,它们会再次检查是否是最小节点。这种方式确保了锁的公平性。
  5. 释放锁:当客户端完成任务或需要释放锁时,它只需删除自己创建的节点。这将触发ZooKeeper的事件通知,通知下一个等待的客户端可以尝试获取锁。
  6. 处理异常和故障情况ZooKeeper提供了强大的分布式协调功能,可以处理各种异常和故障情况,包括客户端崩溃、网络问题等。可以使用ZooKeeper的会话超时机制来检测客户端连接问题,并确保锁的可靠性。
  7. 设置锁的超时时间(可选):类似于基于Redis的分布式锁,可以为锁设置一个超时时间,以防止某个客户端长时间持有锁。

基于ZooKeeper的分布式锁是一种可靠和高度分布式的锁实现方法,适用于需要强一致性和高可用性的分布式系统。然而,使用ZooKeeper也需要更多的配置和维护工作,因此要确保ZooKeeper集群的稳定性和性能。此外,要考虑锁的公平性,以避免某个客户端长时间持有锁,阻塞其他客户端。

6.2.4 三种实现方式的区别
  1. 底层技术:
  • 基于数据库的分布式锁:使用关系型数据库作为底层存储来实现锁,通常使用事务来确保锁的原子性。
  • 基于Redis的分布式锁:使用Redis内存数据库作为底层存储,利用Redis的原子性操作来实现锁。
  • 基于ZooKeeper的分布式锁:使用ZooKeeper分布式协调服务作为底层存储,通过ZooKeeper的节点操作和监听来实现锁。
  1. 性能和延迟:
  • 基于数据库的分布式锁:数据库通常不如内存数据库RedisZooKeeper快速,可能会引入较大的延迟。
  • 基于Redis的分布式锁Redis是内存数据库,速度非常快,适合高性能的应用场景。
  • 基于ZooKeeper的分布式锁ZooKeeper通常比数据库慢,但比数据库快。
  1. 可用性和复杂性:
  • 基于数据库的分布式锁:可以通过数据库的复制和备份来提高可用性,但配置和管理数据库可能更复杂。
  • 基于Redis的分布式锁Redis通常具有高可用性和容错性,但需要定期备份和监控。
  • 基于ZooKeeper的分布式锁ZooKeeper专注于分布式协调,通常具有高可用性和一致性,并提供了复杂的ZooKeeper集群配置和管理。
  1. 锁的粒度和管理:
  • 基于数据库的分布式锁:可以实现细粒度的锁,例如行级锁或表级锁,但需要额外的逻辑来管理锁。
  • 基于Redis的分布式锁Redis锁通常是全局性的,只有一个锁的实例,因此适合用于全局资源的锁定。
  • 基于ZooKeeper的分布式锁ZooKeeper锁通常是全局性的,但可以更容易地管理多个锁。
  1. 故障恢复:
  • 基于数据库的分布式锁:需要额外的机制来处理数据库故障和恢复。
  • 基于Redis的分布式锁Redis具有持久性选项,可以配置为在故障后自动恢复锁状态。
  • 基于ZooKeeper的分布式锁ZooKeeper通常具有高可用性和故障恢复功能。

基于Redis和基于ZooKeeper的分布式锁通常更适合需要高性能和高可用性的场景,而基于数据库的分布式锁可能更适合需要简化的场景或已经在使用数据库的应用程序。

6.2.5 基于Etcd实现分布式锁

参考 4.8 怎么使用Etcd做分布式锁?

6.2.6 zookeeper和Redis分别实现分布式锁,它主要的区别在哪里?

ZooKeeper分布式锁:

  1. 一致性ZooKeeper提供了强一致性的特性,这是其设计的核心之一。在分布式锁的实现中,ZooKeeper会保证锁的获取和释放的顺序,从而确保分布式环境中的一致性。
  2. 临时有序节点ZooKeeper的分布式锁通常通过创建临时有序节点来实现。每个客户端在ZooKeeper上创建一个唯一的节点,并带有序号,最终形成一个有序的节点队列。锁的获取就是判断自己的节点是否是队列中最小的节点。
  3. 阻塞等待:如果某个客户端获取锁失败,它会监听前一个节点的变化,一旦前一个节点释放了锁,它就有机会再次尝试获取锁。

Redis分布式锁:

  1. 基于SETNX命令Redis分布式锁通常使用SETNX(SET if Not eXists)命令实现。一个客户端尝试通过SETNXRedis中设置一个键值对,如果设置成功,表示获取锁成功;否则,表示锁已经被其他客户端持有。
  2. 超时机制: 为了防止死锁,可以为锁设置一个过期时间,确保即使获取锁的客户端异常退出,锁也会在一定时间后自动释放。
  3. 非阻塞Redis分布式锁是非阻塞的,如果获取锁失败,客户端可以选择重试或者放弃。

区别和选择:

  1. 一致性ZooKeeper提供了强一致性,适合需要强一致性保证的场景。Redis分布式锁则是最终一致性,适合一些对一致性要求相对较低的场景。
  2. 性能Redis分布式锁通常比ZooKeeper实现更为简单,性能也可能更好。如果应用场景对性能要求较高,而且可以接受最终一致性,Redis分布式锁可能是一个更轻量级的选择。
  3. 功能特性ZooKeeper提供了更多的分布式协调服务,而不仅仅是分布式锁。如果项目中需要其他分布式协调服务,ZooKeeper可能更适合。

在选择分布式锁方案时,需要根据具体的场景和需求权衡一致性、性能和功能特性。

6.3 分布式缓存

参考:搞定分布式系列:分布式缓存

分布式缓存是一种用于提高应用程序性能和可伸缩性的技术,它将数据存储在多个节点上,以便快速访问和减轻后端存储的负载。分布式缓存通常位于应用程序和后端数据存储之间,可以大大减少对数据库或其他数据存储系统的访问频率,从而提高响应时间和降低系统负载。

分布式缓存的主要特点:

  1. 高性能:分布式缓存通常使用内存存储数据,因此能够提供快速的读取和写入操作。这使得它们非常适合存储频繁访问的数据,例如数据库查询结果、API响应等。
  2. 可伸缩性:分布式缓存可以水平扩展,通过添加更多的缓存节点来处理更大的负载。
  3. 数据一致性:一些分布式缓存提供强一致性,确保缓存中的数据与后端存储中的数据保持一致。其他分布式缓存可能提供最终一致性,允许一定程度的数据延迟。
  4. 缓存失效策略:分布式缓存通常支持设置数据的过期时间或失效策略,以确保缓存中的数据不会过时。

分布式缓存的常见用途:

  1. 减轻数据库负载:分布式缓存可以缓存频繁访问的数据库查询结果,从而减轻数据库服务器的负载,提高数据库的响应速度。
  2. 提高响应速度:将静态内容、模板、页面片段等缓存在分布式缓存中,可以显著提高网站或应用程序的响应速度。
  3. 缓存API响应:缓存API响应数据,以减少对外部服务的请求,降低响应时间,并减轻外部服务的负载。
  4. 分布式会话管理:在分布式系统中,可以使用缓存来存储会话数据,确保用户在多个服务器之间的状态共享。
  5. 热门数据存储:存储热门、频繁访问的数据,例如电子商务网站的产品列表或社交媒体的热门帖子,以减轻后端服务器的负担。

常见的分布式缓存系统包括:

  • Redis:一个高性能的内存缓存和数据存储系统,支持多种数据结构,包括字符串、列表、哈希等。它也可以用作消息队列和分布式锁。
  • Memcached:一个简单而高效的内存缓存系统,用于存储键值对数据。
  • Ehcache:一个Java开发

分布式缓存的具体实现:
实现分布式缓存涉及多个方面,包括选择合适的缓存系统、配置和管理缓存集群、缓存数据的存储和更新、缓存失效策略、数据一致性等。下面是一个具体的实现分布式缓存的步骤:

  1. 选择缓存系统:首先,选择适合应用程序需求的缓存系统。常见的分布式缓存系统包括RedisMemcachedEhcache等。每个系统都有自己的特点和适用场景。
  2. 设置缓存集群:部署和配置缓存服务器以构建缓存集群。确保服务器之间可以相互通信,并可以进行水平扩展以适应负载增加的情况。
  3. 缓存数据的读写:通过缓存客户端库连接到缓存集群。使用客户端库来读取和写入缓存数据。通常,缓存数据可以使用键值对的方式存储。
  4. 缓存失效策略:考虑缓存数据的失效策略。可以为缓存数据设置过期时间,以确保数据不会永久存在于缓存中。根据数据的访问频率和重要性来设置合适的失效策略。
  5. 缓存数据的加载:当缓存中没有需要的数据时,确保应用程序可以从后端数据存储(如数据库)中加载数据,并将其缓存在分布式缓存中。这通常需要编写逻辑来处理缓存未命中的情况。
  6. 缓存数据的更新和删除:当后端数据发生变化时,确保应用程序更新缓存中的相应数据,以保持缓存的一致性。此外,需要考虑如何处理缓存数据的删除,以避免脏数据存在。
  7. 数据一致性:如果应用程序需要强一致性,确保在数据更新时使用合适的同步机制,例如在写入数据库后更新缓存,或者使用分布式锁来确保并发写入的一致性。
  8. 监控和维护:设置监控和报警系统,以监视缓存的性能和健康状态。定期备份缓存数据,确保数据的可恢复性。另外,实施缓存清理策略,以确保缓存不会占用过多的内存或存储。
  9. 安全性:针对缓存系统实施安全措施,包括限制访问、数据加密以及身份验证。
  10. 故障恢复:实施故障恢复策略,确保在缓存服务器或集群出现问题时,应用程序仍然能够正常工作。
  11. 性能优化:根据应用程序的需求,可以采取性能优化措施,例如缓存预热、使用缓存穿透保护策略、使用LRU(最近最少使用)等缓存淘汰策略。

分布式缓存的具体实现取决于应用程序需求和所选择的缓存系统。以上步骤提供了一个通用的指导框架,但每个应用程序都可能需要特定的配置和定制。确保在实施分布式缓存时,根据应用程序需求仔细考虑各个方面,并进行适当的测试和性能优化。

6.4 分布式唯一ID

参考1:分布式唯一 ID 生成方案浅谈
参考2:讲分布式唯一id,这篇文章很实在
参考3:Leaf:美团分布式ID生成服务开源

主要有以下几种:

  • UUID
  • 数据库自增ID
  • Redis生成ID
  • Zookeeper生成ID
  • Snowflake算法

  1. UUID
    UUIDUniversally Unique Identifier,即通用唯一标识码)算法的目的是生成某种形式的全局唯一ID来标识系统中的任一元素,尤其是在分布式环境下,UUID可以不依赖中心认证即可自动生成全局唯一ID
    UUID的标准形式为32个十六进制数组成的字符串,且分割为五个部分,例如:467e8542-2275-4163-95d6-7adc205580a9。

基于使用场景的不同,会存在以下几个不同版本的UUID以供使用,如下所示:

  • 基于时间的UUID:主要依赖当前的时间戳和机器mac地址。优势是能基本保证全球唯一性,缺点是由于使用了mac地址,会暴露mac地址和生成时间;
  • 分布式安全的UUID:将基于时间的UUID算法中的时间戳前四位替换为POSIXUIDGID。优势是能保证全球唯一性,缺点是很少使用,常用库基本没有实现;
  • 基于随机数的UUID:基于随机数或伪随机数生成。优势是实现简单,缺点是重复几率可计算;
  • 基于名字空间的UUIDMD5版):基于指定的名字空间/名字生成MD5散列值得到。优势是不同名字空间/名字下的UUID是唯一的,缺点是MD5碰撞问题,只用于向后兼容;
  • 基于名字空间的UUID(SHA1版):将基于名字空间的UUIDMD5版)中国的散列算法修改为SHA1。优势是不同名字空间/名字下的UUID是唯一的,缺点是SHA1计算相对耗时。UUID的优势是性能非常高,由于是本地生成,没有网络消耗。

而其也存在一些缺陷,包括不易于存储,UUID太长,16字节128位,通常以36长度的字符串表示;信息不安全,基于时间的UUID可能会造成机器的mac地址泄露;ID作为DB主键时在特定的场景下会存在一些问题。
2. 数据库自增ID
数据库自增ID是最常见的一种生成ID方式。利用数据库本身来进行设置,在全数据库内保持唯一。优势是使用简单,满足基本业务需求,天然有序;缺点是强依赖ID,会由于数据库部署的一些特性而存在单点故障、数据一致性等问题。
3. Redis生成ID
主要使用Redis的原子操作INCRINCRBY来实现。优势是不依赖于数据库,使用灵活,性能也优于数据库;而缺点则是可能要引入新的组件Redis,如果Redis出现单点故障问题,则会影响序号服务的可用性。
4. Zookeeper生成ID
主要是利用Zookeeperznode数据版本来生成序列号,可以生成32位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。由于需要依赖Zookeeper,并且是多步调用API,如果在竞争较大的情况下,可能需要考虑使用分布式锁,故此种生成唯一ID的方法的性能在高并发的分布式环境下不甚理想。
5. Snowflake算法
Snowflake(雪花算法)是一个开源的分布式ID生成算法,结果是一个long型的ID。Snowflake算法将64bit划分为多段,分开来标识机器、时间等信息,具体组成结构如下图所示:
在这里插入图片描述
Snowflake算法的核心思想是使用41bit作为毫秒数,10bit作为机器的ID(比如其中5个bit可作为数据中心,5个bit作为机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。

Snowflake算法可以根据自身业务的需求进行一定的调整。比如估算未来的数据中心个数,每个数据中心内的机器数,以及统一毫秒内的并发数来调整在算法中所需要的bit数。

Snowflake算法的优势是稳定性高,不依赖于数据库等第三方系统;使用灵活方便,可以根据业务需求的特性来调整算法中的bit位;单机上ID单调自增,毫秒数在高位,自增序列在低位,整个ID是趋势递增的。而其也存在一定的缺陷,包括强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务处于不可用状态;ID可能不是全局递增,虽然ID在单机上是递增的,但是由于涉及到分布式环境下的每个机器节点上的时钟,可能会出现不是全局递增的场景。

由于雪花算法强依赖于机器时间,如果时间上的时钟发生回拨,则可能引起生成的ID冲突的问题。解决该问题的方案如下所示:

  • ID生成交给少量服务器,然后关闭这些服务器的时钟回拨能力;
  • 当遇到时钟回拨问题时直接报错,交给上层业务来处理;
  • 如果回拨时间较短,在耗时要求范围内,比如5ms,等待回拨时长后在生成ID返回给业务侧;
  • 如果回拨时间很长,无法等待,可以匀出少量位作为回拨位,一旦时间回拨,将回拨位加1,可得到不一样的ID,2位回拨可允许标记三次时钟较长时间的回拨,基本够使用。如果超过回拨次数,可以再选择报错或抛出异常。

6.5 分布式开发的时候有没有碰到什么问题?

  1. 网络通信和延迟:分布式系统中的组件通常分布在不同的地理位置或服务器上,因此需要处理网络通信和延迟。网络通信可能会引入失败、丢包和不稳定性,需要设计适当的协议和处理机制。
  2. 一致性和可用性:维护分布式系统的一致性和可用性是一个复杂的问题。在分布式环境中,实现强一致性可能需要付出性能代价,而宽松一致性可能导致数据不一致。
  3. 数据一致性:确保数据在不同组件之间的一致性是一个关键问题。分布式事务和复制协议可以用于解决这个问题。
  4. 容错和故障处理:分布式系统需要考虑容错机制,以处理组件故障和网络中断。这包括故障检测、故障恢复和负载均衡等。
  5. 分布式锁和并发控制:在多个组件之间协调共享资源访问时,需要处理分布式锁和并发控制,以避免竞争条件和数据不一致。
  6. 数据分布和分片:将数据分布在多个服务器上,以提高性能和可伸缩性。这涉及到数据分片和负载均衡的设计。
  7. 版本控制和升级:维护分布式系统的多个版本和组件,需要有效的版本控制和升级策略,以确保系统的兼容性和可维护性。
  8. 监控和调试:监视和调试分布式系统可以是具有挑战性的任务。需要使用适当的工具和日志记录来跟踪问题并分析性能瓶颈。
  9. 安全性:分布式系统需要维护数据的安全性和隐私,因此需要考虑身份验证、授权、加密和防护措施。
  10. 数据传输和序列化:数据在不同组件之间传输和序列化可能需要额外的注意,以确保数据的完整性和正确性。
  11. 资源管理和扩展性:在分布式系统中,有效地管理资源(如计算资源、存储和带宽)和实现可扩展性是至关重要的。
  12. 文档和通信:在分布式开发中,清晰的文档和有效的沟通是关键,以确保团队理解系统的设计和协作。

这些问题只是分布式开发中的一部分,而实际挑战可能因应用程序的特定需求和规模而有所不同。解决这些问题需要仔细的设计、合适的工具和技术,以及经验丰富的开发团队。分布式系统的复杂性和挑战性是一个长期的研究领域,也是计算机科学领域的重要主题之一。

6.6 分布式架构一般哪些场景会用到?

  1. 高可用性需求:当系统对高可用性有极高的要求时,分布式架构可以通过在多个节点上部署相同的服务或数据来保障系统的持续可用性。即使某个节点发生故障,其他节点依然可以提供服务。
  2. 负载均衡和性能扩展:分布式架构可以通过在多个节点之间分发负载,实现负载均衡。这有助于提高系统的性能和吞吐量。当系统的用户量增加时,可以通过横向扩展(添加更多的节点)来应对更大的负载。
  3. 大规模数据处理:大规模数据处理场景,如大数据处理、实时数据分析等,常常采用分布式架构。分布式存储和计算系统如Hadoop、Spark等能够有效处理大规模数据的存储和分析需求。
  4. 弹性和容错性:分布式架构可以提供弹性和容错性,使系统能够适应不断变化的负载和处理节点故障。这种弹性和容错性是通过在分布式系统中复制数据、任务和服务来实现的。
  5. 全球化和地理位置分布:在全球化的应用中,分布式架构允许将系统部署在多个地理位置,使得用户能够更快速、可靠地访问系统。这种架构有助于减少网络延迟,并提高用户体验。
  6. 微服务架构:微服务架构是一种分布式架构的实现方式,其中各个功能模块被拆分成独立的服务。这样的架构有助于提高系统的灵活性、可维护性和可扩展性。每个微服务可以独立开发、部署和扩展。
  7. 实时处理和消息队列:在需要实时处理和异步通信的场景中,分布式消息队列和流处理系统可以帮助系统处理大量实时数据,并提供高效的消息传递机制。
  8. 复杂业务逻辑和大型系统:大型企业应用或服务通常包含复杂的业务逻辑和功能。分布式架构能够更好地组织和管理这些复杂性,同时支持系统的模块化开发和维护。

总的来说,分布式架构在需要提高系统可用性、性能扩展、弹性、容错性以及处理大规模数据等方面都发挥着重要的作用。不同的应用场景需要不同的分布式架构设计和技术选择。

7 Protobuf

7.1 为什么在微服务里面会选中protobuf多一些

  1. 性能高效protobuf是一种二进制协议,相对于文本协议(如JSONXML)来说,它的序列化和反序列化速度更快,生成的数据体积更小。这对于在微服务之间传输大量数据时非常重要,可以提高性能和减少带宽消耗。
  2. 跨语言支持protobuf具有广泛的编程语言支持,包括JavaC++PythonGo等,可以使用不同编程语言编写的微服务之间进行通信,而无需担心语言之间的兼容性问题。
  3. 版本兼容性protobuf支持版本化,可以在不破坏现有代码的情况下添加新字段或删除旧字段。这对于微服务的演进和升级非常有用,因为它们可以独立地更新和部署。
  4. 语言无关性protobuf的消息定义是独立于编程语言的,可以在一个地方定义消息结构,然后在不同的语言中使用这些消息。这有助于确保消息的一致性和互操作性。
  5. 数据结构强类型protobuf使用强类型的数据结构,可以在编译时检测到数据结构的错误,而不是在运行时。这有助于提高代码的稳定性和可维护性。
  6. 可读性和可维护性:即使是二进制格式,protobuf的消息定义仍然相对容易阅读和维护。它提供了清晰的消息结构和字段描述,使开发人员能够理解消息的含义。
  7. 丰富的生态系统protobuf在微服务领域有一个丰富的生态系统,许多工具和框架都提供了对protobuf的支持,例如gRPCThrift等,它们可以更轻松地构建和部署微服务。

总结:
protobuf在微服务架构中因其高性能、跨语言支持、版本兼容性等特点而备受青睐。尽管它需要在微服务之间定义消息结构并生成代码,但这种额外的工作可以带来在通信效率和可维护性方面的巨大好处。根据需求和技术栈,选择protobuf可能是一个明智的选择。

7.2 Protobuf2和3的区别

Protocol Buffers(简称 Protobuf)是一种用于序列化结构化数据的方法,它可以用于数据交换、通信协议、数据存储等各种应用。Protobuf有两个主要版本,分别是Protobuf 2(也称为Proto2)和Protobuf 3(也称为Proto3),它们之间有一些重要的区别:

  1. 语法差异
  • Proto2Proto2使用的是非严格的可选字段(optional)、必填字段(required)和重复字段(repeated)等概念。字段默认是可选的,而且可以为NULL(或默认值)。
  • Proto3Proto3精简了语法,只支持单一的字段语法,即所有字段都是可选的,并且不存在requiredoptional的概念。这意味着在Proto3中,所有字段都可以为零值。
  1. 默认值
  • Proto2:在Proto2中,字段可以具有默认值,如果字段没有设置值,它将使用默认值。这可以导致某些情况下难以确定字段是否明确设置为默认值还是未设置。
  • Proto3Proto3中默认值的概念被移除,字段不再具有默认值,而是始终具有零值。这样可以更清晰地表示字段是否明确设置。
  1. Unknown字段
  • Proto2:如果在解析Proto2消息时遇到未知字段,它们将被忽略,而不会引发错误。这使得Proto2消息可以向前兼容。
  • Proto3:在Proto3中,未知字段将导致解析失败,因为Proto3旨在更严格地确保数据的一致性。
  1. 默认编码
  • Proto2Proto2使用了一种相对较为复杂的变长编码方案(VarintZigzag等)。
  • Proto3Proto3使用更简单的固定长度编码,这有助于提高解析性能。
  1. Enum的变化
  • Proto2Proto2中枚举的第一个值默认为0,如果不显式设置值,后续枚举值会依次自增。
  • Proto3Proto3中的枚举从0开始,但不再支持自增,枚举值必须显式设置。
  1. 小数类型
  • Proto2Proto2支持32位和64位浮点数。
  • Proto3Proto3支持32位和64位浮点数,但不再支持fixedrequired

需要注意的是,升级现有的Protobuf 2Protobuf 3可能需要进行一些修改,因为它们的语法和行为之间存在显著差异。因此,迁移时需要谨慎处理现有的Protobuf数据定义。选择使用哪个版本取决于项目需求,新项目通常可以考虑使用Proto3,因为它提供了更简单、清晰和严格的语法。

7.3 Protobuf和JSON对比

  1. 数据大小
  • ProtobufProtobuf通常比JSON更紧凑,序列化后的数据更小。这是因为Protobuf使用了二进制编码,不包含冗余的字段名和数据类型信息。
  • JSONJSON是文本格式,相对于二进制格式来说,通常更大,因为它包含了字段名和数据类型等描述性信息。
  1. 可读性
  • ProtobufProtobuf是二进制格式,对人类来说不太可读。它主要用于机器间的数据交换。
  • JSONJSON是文本格式,易于阅读和编辑。它通常用于配置文件、REST API和与人类交互的场景。
  1. 性能
  • ProtobufProtobuf通常比JSON快,因为它的编解码速度更快,生成的数据更小。这使得Protobuf特别适用于高性能和低延迟的应用程序。
  • JSONJSON的解析和生成速度相对较慢,尤其是在处理大量数据时。
  1. 可扩展性
  • ProtobufProtobuf支持数据结构的演化,可以向现有的Protobuf消息添加新字段而不会破坏现有的兼容性。这使得它适用于长期维护的系统。
  • JSONJSON不太适合数据结构的演化,因为更改JSON结构可能需要更新所有相关的代码。
  1. 跨语言支持
  • ProtobufProtobuf提供了多种编程语言的支持,可以通过不同语言的生成代码进行序列化和反序列化。
  • JSONJSON同样具有广泛的跨语言支持,因为几乎所有编程语言都有JSON解析和生成库。
  1. 可选性和默认值
  • ProtobufProtobuf支持可选字段和默认值,允许明确指定字段是否存在以及字段的默认值。
  • JSONJSON中的字段默认都是可选的,如果字段不存在,则通常假定为null或缺失。
  1. 枚举类型
  • ProtobufProtobuf提供原生的枚举类型支持,允许定义一组有限的可能值。
  • JSONJSON不具备原生的枚举类型,通常使用字符串或数字表示。

ProtobufJSON在不同的用例中都有优点和缺点。Protobuf适用于高性能、紧凑、二进制的数据交换,尤其在内部通信和大规模分布式系统中表现良好。JSON则适用于易读性和可编辑性要求高的场景,如配置文件和REST API。选择合适的格式取决于应用程序需求和用例。有时候,甚至可以在两者之间进行转换,以满足不同系统之间的需求。

7.4 Protobuf中每个字段后的序号作用

Protocol Buffers(Protobuf)中,每个字段后的序号(field number)是用来标识和识别消息中的字段的唯一标识符。这些序号的作用包括:

  1. 消息格式的演化:序号的主要作用之一是允许在未来修改消息的定义时保持向后兼容性。因为序号是唯一的标识符,Protobuf可以在解析数据时根据序号而不是字段名来识别字段。这意味着可以添加、删除或重新排列字段,而不会破坏与旧版本数据的兼容性。
  2. 字段的顺序:通过指定序号,可以控制消息中字段的顺序。这对于使消息结构更有组织性和可读性很有帮助。虽然Protobuf不要求字段按序号排序,但通常建议按顺序编写字段定义。
  3. 字段的唯一性:序号确保了字段的唯一性。不同字段不能拥有相同的序号。这有助于防止消息定义中的重复字段或冲突。
  4. 节省空间:序号以紧凑的整数形式编码,占用的空间非常小,这有助于减小序列化后数据的大小。
  5. 快速解析:由于序号是固定的整数,Protobuf解析器可以更快速地定位和解析消息中的字段,而无需查找字段名称。

注意:
一旦定义了字段的序号,就不应该再更改它们。因为序号与消息结构的演化和数据兼容性密切相关,更改序号可能导致不兼容的问题。因此,序号的选择需要谨慎考虑,并且通常在消息定义中保持稳定。

7.5 ProtoBuf序列化

Protocol Buffers(Protobuf)中,序列化是将消息对象转换为二进制数据的过程。以下是使用Go中的Protobuf库进行消息序列化的一般步骤:

  1. 定义Protobuf消息:首先,需要定义消息结构,使用Protobuf语法编写.proto文件来描述消息。例如:

syntax = “proto3”;

message Person {
int32 id = 1;
string name = 2;
string email = 3;
}

  1. 生成Go代码: 使用Protobuf编译器(通常称为protoc)来生成相应语言的代码。在Go中,可以使用protoc-gen-go插件生成Go代码。运行以下命令:

protoc --go_out=. your_protobuf_file.proto

  1. 创建消息对象: 在Go代码中,使用生成的消息结构创建消息对象。例如:

person := &Person{
Id: 1,
Name: “John Doe”,
Email: “johndoe@example.com”,
}

  1. 序列化消息: 使用Protobuf库中的序列化函数将消息对象转换为二进制数据。在Go中,可以使用proto.Marshal()函数来完成这个任务:

data, err := proto.Marshal(person)
if err != nil {
// 处理错误
}

现在,data变量包含了序列化后的二进制数据。
5. 发送/存储数据: 将序列化后的数据发送到网络、存储到文件或传递给其他系统,以便后续处理或存储。
6. 反序列化(可选): 如果接收方需要解析二进制数据并还原为消息对象,可以使用Protobuf库的反序列化函数。在Go中,可以使用proto.Unmarshal()函数来反序列化数据:

receivedPerson := &Person{}
err := proto.Unmarshal(data, receivedPerson)
if err != nil {
// 处理错误
}

将二进制数据还原为Person结构体。

7.6 Protobuf字段兼容需要注意的事项

Protocol Buffers(Protobuf)中,确保字段的向后和向前兼容性是非常重要的,以便在更新消息定义时不会破坏与现有数据的兼容性。以下是需要注意的一些事项,以确保字段兼容性:

  1. 不要更改现有字段的序号:序号是用来标识字段的唯一标识符,不要更改现有字段的序号,因为这会破坏与旧版本数据的兼容性。如果需要添加新字段,为新字段分配一个尚未使用的序号。
  2. 避免删除字段:删除现有字段会导致与旧版本数据的不兼容性。如果不再需要某个字段,可以考虑将其标记为已弃用(deprecated),而不是删除它。弃用字段将仍然保留在消息中,但不再使用,以便旧版本的解析器可以忽略它们。
  3. 不要更改字段的数据类型:不要更改字段的数据类型,例如,从整数变为字符串,因为这会破坏与旧版本数据的兼容性。如果需要更改数据类型,可以添加一个新字段,并根据旧字段的数据类型进行转换。
  4. 可选字段到必填字段的升级:如果将字段从可选字段(optional)更改为必填字段(required),则需要确保向旧版本的消息提供默认值,以便旧版本的解析器可以正确解析新版本的消息。这是为了防止旧版本的消息在解析时引发错误。
  5. 添加新字段:添加新字段通常是向前兼容的,但需要分配一个未使用的序号。在旧版本的解析器中,新字段将被忽略,因为它们在旧版本中未知。
  6. 弃用字段:如果某个字段不再需要,可以使用deprecated标记将其标记为弃用。这样做会向开发人员发出警告,表示不应再使用该字段,但它仍然存在于消息定义中,以确保与旧版本的兼容性。
  7. 文档和通知:在更新消息定义时,及时更新文档并通知所有相关开发人员,以确保他们了解变更并可以相应地升级其代码。

充分考虑这些因素,可以帮助确保在更新消息定义时保持与旧版本数据的兼容性,从而实现平滑的升级和迁移。 Protobuf的强大之处在于它提供了良好的向前和向后兼容性支持,但仍然需要谨慎处理消息定义的修改。

7.7 Protobuf字段序号重复能编译出来吗

不能,Protobuf编译器将会报告错误,并阻止代码生成。

Protocol Buffers(Protobuf)中,字段序号(field number)必须是唯一的,否则编译器不会生成有效的代码。如果试图在同一消息定义中使用相同的字段序号多次,Protobuf编译器将会报告错误,并阻止代码生成。

这是因为字段序号用于标识和识别消息中的字段,它们必须是唯一的,以确保消息的正确解析和序列化。如果多个字段具有相同的序号,编解码器将无法准确地识别字段,这会导致严重的数据一致性问题。

因此,如果需要添加新字段或更改现有字段的序号,请确保为每个字段分配唯一的序号,以避免编译错误和与旧版本数据的不兼容性。

7.8 Protobuf是怎样做压缩的

Protocol Buffers(Protobuf)在数据传输和存储时通常比文本格式(如JSON)更紧凑,这主要是因为Protobuf使用了一种紧凑的二进制编码格式。以下是Protobuf如何进行压缩的关键特性:

  1. 无需字段名:在JSON等文本格式中,每个字段都需要包含字段名,这会增加数据的大小。而在Protobuf中,字段名不包含在序列化的数据中,只有字段的唯一标识符(字段序号)以及字段的值。这消除了冗余的字段名信息,使数据更紧凑。
  2. 可变长度编码Protobuf使用一种称为可变长度编码(Variable Length Encoding)的方法来编码整数值,以及一种紧凑的方式来表示浮点数和其他数据类型。这种编码方式允许较小的数字占用较少的字节,而较大的数字占用较多的字节,从而减小了数据的大小。
  3. 不需要额外的分隔符:在文本格式中,通常需要使用逗号、冒号等分隔符来分隔字段和值,这增加了数据的大小。在Protobuf中,字段的序号和值之间使用一种紧凑的编码方式进行分隔,不需要额外的字符。
  4. 默认值省略:如果字段的值与其默认值相同,Protobuf不会在序列化时包括这些字段。这减少了数据的大小,因为默认值不需要重复传输。
  5. 字段压缩Protobuf采用字段压缩的方式,将多个字段的数据组合在一起,以减少字段标识符的重复出现。这特别适用于重复字段,如数组或列表。

总结:
Protobuf的紧凑二进制编码方式使其在数据传输和存储方面具有出色的性能,尤其适用于需要高效率和低带宽消耗的场景。然而,需要注意的是,虽然Protobuf在性能和大小方面表现出色,但由于其是二进制格式,不如JSON那样易于人类阅读和调试。因此,在选择数据序列化格式时,需要根据具体需求权衡各种因素。

8 Prometheus

参考:Prometheus看完这些,入门就够了

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
SON等文本格式中,每个字段都需要包含字段名,这会增加数据的大小。而在Protobuf中,字段名不包含在序列化的数据中,只有字段的唯一标识符(字段序号)以及字段的值。这消除了冗余的字段名信息,使数据更紧凑。 2. **可变长度编码**:Protobuf使用一种称为可变长度编码(Variable Length Encoding)的方法来编码整数值,以及一种紧凑的方式来表示浮点数和其他数据类型。这种编码方式允许较小的数字占用较少的字节,而较大的数字占用较多的字节,从而减小了数据的大小。 3. **不需要额外的分隔符**:在文本格式中,通常需要使用逗号、冒号等分隔符来分隔字段和值,这增加了数据的大小。在Protobuf中,字段的序号和值之间使用一种紧凑的编码方式进行分隔,不需要额外的字符。 4. **默认值省略**:如果字段的值与其默认值相同,Protobuf不会在序列化时包括这些字段。这减少了数据的大小,因为默认值不需要重复传输。 5. **字段压缩**:Protobuf`采用字段压缩的方式,将多个字段的数据组合在一起,以减少字段标识符的重复出现。这特别适用于重复字段,如数组或列表。

总结:
Protobuf的紧凑二进制编码方式使其在数据传输和存储方面具有出色的性能,尤其适用于需要高效率和低带宽消耗的场景。然而,需要注意的是,虽然Protobuf在性能和大小方面表现出色,但由于其是二进制格式,不如JSON那样易于人类阅读和调试。因此,在选择数据序列化格式时,需要根据具体需求权衡各种因素。

8 Prometheus

参考:Prometheus看完这些,入门就够了

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-YSPiH6ug-1713217060151)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/774980
推荐阅读
相关标签
  

闽ICP备14008679号