赞
踩
先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注go)
// 获取初始数据版本
response, err := client.Get(context.Background(), “/config/alert_threshold”)
if err != nil {
// 处理错误
}
initialRevision := response.Header.Revision
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)
}
这个示例中,我们使用etcd
的数据版本来监视特定键的变化,并在变化时触发报警。这可以用于监控配置更改、服务状态等。请注意,此示例是一个简化的示例,实际场景中可能需要更多的错误处理和安全性措施。此外,确保etcd
服务器配置安全,以免未经授权的访问。
分布式环境下,多台机器上多个进程对同一个共享资源(数据、文件等)进行操作,如果不做互斥,就有可能出现“余额扣成负数”,或者“商品超卖”的情况。为了解决这个问题,需要分布式锁服务。首先,来看一下分布式锁应该具备哪些条件。
A
不能把客户端B
加的锁给解了。Etcd
的高可用性、强一致性不必多说,前面章节中已经阐明,本节主要介绍Etcd
支持的以下机制:Watch
机制、Lease
机制、Revision
机制和Prefix
机制,正是这些机制赋予了Etcd
实现分布式锁的能力。
Etcd
可以为存储的Key-Value
对设置租约,当租约到期,Key-Value
将失效删除;同时也支持续约,通过客户端可以在租约到期之前续约,以避免Key-Value
对过期失效。Lease
机制可以保证分布式锁的安全性,为锁对应的Key
配置租约,即使锁的持有者因故障而不能主动释放锁,锁也会因租约到期而自动释放。Key
带有一个Revision
号,每进行一次事务便加一,因此它是全局唯一的,如初始值为0,进行一次put(key,value)
,Key
的Revision
变为1,同样的操作,再进行一次,Revision
变为2;换成key1
进行put(key1,value)
操作,Revision
将变为3;这种机制有一个作用:通过Revision
的大小就可以知道写操作的顺序。在实现分布式锁时,多个客户端同时抢锁,根据Revision
号大小依次获得锁,可以避免“羊群效应”(也称“惊群效应”),实现公平锁。/mylock
的锁,两个争抢它的客户端进行写操作,实际写入的Key
分别为:key1=“/mylock/UUID1”
,key2=“/mylock/UUID2”
,其中,UUID
表示全局唯一的ID
,确保两个Key
的唯一性。很显然,写操作都会成功,但返回的Revision
不一样,那么,如何判断谁获得了锁呢?通过前缀“/mylock”
查询,返回包含两个Key-Value
对的Key-Value
列表,同时也包含它们的Revision
,通过Revision
大小,客户端可以判断自己是否获得锁,如果抢锁失败,则等待锁释放(对应的Key
被删除或者租约过期),然后再判断自己是否可以获得锁。Watch
机制支持监听某个固定的Key
,也支持监听一个范围(前缀机制),当被监听的Key
或范围发生变化,客户端将收到通知;在实现分布式锁时,如果抢锁失败,可通过Prefix
机制返回的Key-Value
列表获得Revision
比自己小且相差最小的Key
(称为Pre-Key
),对Pre-Key
进行监听,因为只有它释放锁,自己才能获得锁,如果监听到Pre-Key
的DELETE
事件,则说明Pre-Key
已经释放,自己已经持有锁。下面描述了使用Etcd
实现分布式锁的业务流程,假设对某个共享资源设置的锁名为:/anyrtc/mylock
。
Etcd
,以/anyrtc/mylock为
前缀创建全局唯一的Key
,假设第一个客户端对应的Key=“/anyrtc/mylock/UUID1”
,第二个为Key=“/anyrtc/mylock/UUID2”
;客户端分别为自己的Key
创建租约Lease
,租约的长度根据业务耗时确定,假设为15s。Key
将因租约到期而被删除,从而锁释放,避免死锁。Put
操作,将步骤1中创建的Key
绑定租约写入Etcd
,根据Etcd
的Revision
机制,假设两个客户端Put
操作返回的Revision
分别为1、2,客户端需记录Revision
用以接下来判断自己是否获得锁。/anyrtc/mylock
读取Key-Value
列表(Key-Value
中带有Key
对应的Revision
),判断自己Key
的Revision
是否为当前列表中最小的,如果是则认为获得锁;否则监听列表中前一个Revision
比自己小的Key的删除事件,一旦监听到删除事件或者因租约失效而删除的事件,则自己获得锁。Key
释放锁。根据业务流程,基于Etcd
的分布式锁示意图如下:
Etcd
的主要接口是gRPC
接口,它提供了对Etcd
的完整功能访问。通过gRPC
接口,可以执行诸如设置键值对、获取键值对、触发事务操作等操作。gRPC
是一种高性能的远程过程调用(RPC
)框架,可以用多种编程语言来使用Etcd
的API
。Etcd
还提供了HTTP
和gRPC
代理接口,允许通过HTTP
或gRPC
请求来访问Etcd
。这使得使用Etcd
的RESTfulAPI
变得更加方便,特别是在不支持gRPC
的环境中。Etcd
还附带了一个命令行工具称为etcdctl
,它提供了一个命令行界面,允许与Etcd
集群交互。可以使用etcdctl
来设置、获取、删除键值对,以及执行其他管理和查询操作。Etcd
的WatchAPI
允许监视键值对的更改。可以设置观察器以接收有关特定键的更改通知,这对于实时应用程序和配置管理非常有用。Etcd
支持事务操作,可以使用事务API
执行一系列操作,要么全部成功,要么全部失败。这确保了多个操作的一致性。Etcd
提供了健康检查接口,允许检查Etcd
集群的运行状况和健康状态。这对于监控和运维非常有用。Etcd
支持基于TLS
的安全通信,并提供了授权和认证接口,可以配置访问控制策略,以限制对Etcd
数据的访问。这些接口组合在一起,使Etcd
成为一个强大的分布式数据存储系统,适用于多种用例,包括服务发现、配置管理、分布式锁、选举等。不同的接口提供了不同级别的灵活性和功能,以满足不同应用程序的需求。
Etcd
的过期策略基于TTL
(Time-To-Live
,生存时间)机制,它允许为存储在Etcd
中的键值对设置生存时间,当键值对的生存时间到期时,它将自动从Etcd
中删除。这个机制是Etcd
中处理过期的主要策略。
具体来说,当使用Etcd
的put
操作来设置一个键值对时,可以为该键值对指定TTL
值,如下所示:
etcdctl put key value --ttl 60
上述命令将key
和value
存储在Etcd
中,并设置了60
秒的TTL
。当60
秒钟过去后,如果不更新该键值对的TTL
或删除该键值对,Etcd
将自动将其删除。
过期策略的主要用途之一是在配置管理中,以确保配置数据在一定时间后自动失效,从而触发配置的刷新或重新加载。过期键值对的删除是Etcd
自身的维护任务,不需要用户手动干预。
需要注意的是,Etcd
的TTL
机制仅适用于具有TTL
设置的键值对,如果未设置TTL
,键值对将永久保存在Etcd
中。此外,Etcd
可以配置不同的自动清理策略来处理过期数据的清理,例如自动清理已过期的键值对以释放磁盘空间。
总结:
Etcd
的过期策略基于TTL
机制,允许设置键值对的生存时间,当生存时间到期时,Etcd
将自动删除这些键值对。这对于自动数据管理和配置刷新非常有用。
etcd
存储的数据是一个Key-Value
格式的存储,etcdv2
的key
是一个递归的文件目录结构,在v3
版本中的键改成了扁平化的数据结构,更加简洁,v3
中支持前缀查询,在etcdctl get key
时可以加上前缀查询选项--prefix
,从而达到v2
的目录结构查询效果。
API
里有grpc-gateway
,可以将HTTP
协议转为GRPC
,同时还有鉴权,加解密,其他的和RPC
端一致。
参考1:负载均衡
负载均衡:
go-zero
使用的负载均衡是P2C(Power of Two Choices)
。P2C(Power of Two Choices)
算法是一种基于随机化的负载均衡算法,由Jeff Dean
和Luiz Andre Barroso
在2001年提出。
P2C
算法是一种改进的随机算法,它可以避免最劣选择和负载不均衡的情况。P2C
算法的核心思想是:从所有可用节点中随机选择两个节点,然后根据这两个节点的负载情况选择一个负载较小的节点。这样做的好处在于,如果只随机选择一个节点,可能会选择到负载较高的节点,从而导致负载不均衡;而选择两个节点,则可以进行比较,从而避免最劣选择。
P2C
算法的实现步骤如下:
P2C
算法的优点在于,它可以在保证负载均衡的前提下,选择负载更小的节点,从而提高系统的性能和可靠性。此外,P2C
算法的实现简单,不需要太多的计算和存储资源,因此在实际应用中被广泛采用。其他常见负载均衡算法:
参考1:网关
go-zero
使用的网关是gRPC gateway
。go-zero
中的gRPC
网关是一个HTTP
服务器,它将RESTful API
转换为gRPC
请求,然后将gRPC
响应转换为RESTful API
。大致流程如下:
proto
文件中解析出gRPC
服务的定义。gRPC
服务的HTTP
映射规则。gRPC
服务的定义和HTTP
映射规则,生成gRPC
服务的HTTP
处理器。HTTP
服务器,处理 HTTP
请求。HTTP
请求转换为gRPC
请求。gRPC
响应转换为HTTP
响应。HTTP
响应。参考1:断路器
断路器又叫熔断器,是一种保护机制,用于保护服务调用链路中的服务不被过多的请求压垮。当服务调用链路中的某个服务出现异常时,断路器会将该服务的调用请求拒绝,从而保护服务调用链路中的其他服务不被压垮。
比较知名的熔断器算法有Hystrix
和Sentinel
,它们都是通过统计服务调用的成功率和响应时间来判断服务是否可用,从而实现熔断的功能。
go-zero
内置了熔断器组件breaker.Breaker(自研的),go-zero
中采用滑动窗口来进行数据采集,目前是以10s为一个窗口,单个窗口有40个桶,然后将窗口内采集的数据使用google sre算法计算是否开启熔断,详情可参考https://landing.google.com/sre/sre-book/chapters/handling-overload/#eq2101。
在brreaker.Breaker
中,提供了Do
、DoWithAcceptable
、DoWithFallback
、DoWithFallbackAcceptable
四个方法,分别对应不同的场景。 开发者可以通过breaker
直接调用这些方法,或者创建一个breaker
实例来调用,两种方法均可,直接调用其实本质上也会以name
作为唯一key
去获取/创建一个breaker
实例。
以上方法都是通过name
来获取/创建一个breaker
实例,即熔断器名称相同的同属于一个熔断器控制,如果需要自定义breaker
的配置,可以通过NewBreaker
方法来创建一个breaker
实例,通过实例可以精确控制具体情况是放过还是拒绝。
在go-zero
中,开发者不需要对请求单独进行熔断处理,该功能已经集成到了框架中,因此开发者无需关系。
HTTP
以请求方法+路由作为统计维度,用HTTP
状态码500
作为错误采集指标进行统计,详情可参考:breakerhandler.go
gRPC
客户端以RPC
方法名作为统计维度,用gRPC
的错误码为:codes.DeadlineExceeded
、codes.Internal
、codes.Unavailable
、codes.DataLoss
、codes.Unimplemented
作为错误采集指标进行统计,详情可参考:breakerinterceptor.go
。gRPC
服务端以RPC
方法名称作为统计维度,用gRPC
的错误作为错误采集指标进行统计,详情可参考:breakerinterceptor.go
。参考1:限流(并发控制)
限流器是一种服务治理能力,用于限制服务的并发调用量,以保护服务的稳定性。限流分为rest
和grpc
。
限流一般有单节点限流、集群限流(将限流数值对集群节点数求平均值,其本质还是单节点限流)、分布式限流。
限流,倒不如说是并发控制更贴切。
这一块还需要再整理逻辑,细化具体做了哪些。
– 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;
使用单个asynq
来处理其实不对,如果使用asynq
来进行数据的分发的话在数据量比较大的情况下,Redis
会塞满数据,导致瘫痪。
应该利用kafka
的消费者组的特点,这样可以充分利用kafka
单个消费者组多个消费者并发处理的性能。
适用于实时大数据,并且可以快速扩展,并发处理(也就是增加多个分区,同时再增加多个消费者)。
参考1:go-zero学习 第六章 分布式事务dtm
参考2:DTM开源项目文档:官方文档
seata-golang的分布式事务。
参考3:seata-golang 接入指南
参考4:官网
参考5:【微服务架构】分布式事务
分布式事务是一种涉及多个独立组件或服务的事务操作,需要确保在不同节点上的操作要么全部成功,要么全部失败,以维护数据的一致性。
常见事务模式:
二阶段消息事务(Two-Phase Commit,2PC)是一种用于实现分布式事务的协议,它通常用于确保在不同节点上的数据库或服务上的事务操作的一致性。适合不需要回滚的全局事务。
二阶段消息事务通常包含以下两个阶段:
2PC
的关键特点是在准备阶段引入了一个同意/中止机制,以确保所有参与者都可以成功完成操作。然而,2PC
也存在一些问题,如协调者单点故障、性能瓶颈和阻塞等问题。因此,一些分布式系统会选择使用三阶段提交(Three-Phase Commit,3PC)等更复杂但更可靠的分布式事务协议来解决这些问题。
Saga
是一种通过将大型事务拆分成多个小事务并使用补偿操作来维护数据一致性的方式。Saga
模式的主要目标是最小化分布式事务的范围,以提高性能和可伸缩性,并降低事务中断的风险。适合需要支持回滚的全局事务。
Saga
事务包含以下关键特点和概念:
Saga
将大型事务拆分为多个小事务,每个小事务涵盖了系统中的一个服务或组件。这些小事务可以在不同的节点上独立执行,从而减小了分布式事务的范围。Saga
会逆序执行之前成功的小事务的补偿操作,以维护数据一致性。Saga
中的事务协调器(Transaction Coordinator
)负责协调各个小事务的执行和补偿。它跟踪事务的状态,并在必要时触发补偿操作。Saga
模式接受局部数据不一致性,即在某个小事务成功后,该事务的数据可能在稍后的补偿操作中被撤销或修改。这是Saga
的一种权衡,旨在提高性能和可伸缩性。Saga
事务可以在较长的时间范围内执行,因为它们可以在多个步骤之间等待用户或外部事件的响应。这使得Saga
适用于需要长时间执行的业务流程。Saga
模式的一个重要优势是允许系统部分失败而不影响整体一致性。如果某个小事务失败,系统可以继续运行并通过执行补偿操作来纠正问题。这降低了系统中断的风险,尤其在大规模和复杂的分布式系统中。
需要注意的是,实施Saga
模式可能会引入复杂性,例如管理补偿操作和确保系统可恢复性。因此,在选择使用Saga
模式时,需要权衡其优势和复杂性,以确保它适合特定的应用场景和系统架构。
TCC(Try-Confirm-Cancel)
是一种分布式事务模式,TCC
事务将大事务拆分成三个阶段:尝试(Try
)、确认(Confirm
)和取消(Cancel
)来实现分布式事务,以确保在不同节点上的操作要么全部成功,要么全部失败。特别适用于需要严格保证数据一致性的分布式系统。
以下是TCC
事务模式的关键概念和步骤:
Try
阶段,事务协调器(Transaction Coordinator
)会向所有参与者(各个节点或服务)发送一个尝试请求,询问它们是否愿意执行事务。每个参与者会执行以下操作:Try
阶段成功执行了相关操作并确认可以执行事务,那么事务协调器会向所有参与者发送确认请求。每个参与者会执行以下操作:Try
阶段执行的操作,并将结果持久化。这个阶段将确保事务操作的执行。Try
阶段有任何参与者失败或拒绝执行事务,或者在Confirm
阶段的某些参与者失败,那么事务协调器会向所有参与者发送取消请求。每个参与者会执行以下操作:Try
阶段执行的操作,以确保事务操作的撤销。TCC
模式的关键特点包括:
TCC
模式将事务的操作拆分为Try
、Confirm
和Cancel
阶段,以确保在不同节点上的操作具有原子性。TCC
要求参与者实现Try
、Confirm
和Cancel
操作,以处理不同阶段的事务状态。TCC
允许在Cancel
阶段回滚事务操作,从而确保数据一致性,即使在部分节点失败的情况下也能恢复到一致状态。需要注意的是,TCC
模式引入了额外的复杂性,包括事务协调、状态管理和异常处理等方面的问题。因此,TCC
模式通常适用于需要强一致性和精确控制的分布式系统场景,但也需要谨慎考虑其实施和维护的复杂性。一些分布式事务管理框架(如Seata
)提供了TCC
模式的支持,以简化实施过程。
适合性能要求不高,没有行锁争抢的全局事务。
XA(eXtended Architecture)
事务是一种用于管理分布式事务的协议,它允许多个资源管理器(通常是数据库或消息队列)协同工作以确保分布式事务的原子性、一致性、隔离性和持久性(ACID
属性)。XA
协议提供了两阶段提交(Two-Phase Commit,2PC
)协议的标准化实现,但它也可以扩展到支持三阶段提交(Three-Phase Commit,3PC
)以提高可靠性。
以下是XA
事务的主要概念和步骤:
XA
事务的典型流程如下:
XA
事务的主要优点是它提供了一种标准化的分布式事务管理方法,确保了ACID
属性。然而,XA
事务也有一些缺点,包括性能开销、对资源管理器的依赖性和可能的单点故障问题。因此,使用XA
事务需要仔细考虑系统的需求和复杂性。在某些情况下,更轻量级的分布式事务模式(如TCC
或Saga
)可能更适合。
分布式事务主要是订单、商城,需要立即结束订单、库存、支付等,否则业务无法继续,耦合度较高,其中一个环节失败,则整个业务失败。
消息中间件是解耦,各管各的。
参考1:微服务中的分布式锁方案
参考2:一文彻底弄清楚分布式锁
参考3:分布式锁有哪些解决方案
基于数据库的分布式锁是通过在数据库中创建一个特殊的记录或行来表示锁的状态,从而确保在任何给定时刻只有一个客户端能够获得该锁,以执行特定的操作。这可以用于避免多个客户端同时修改相同的数据或执行相同的任务,从而确保数据的一致性和可靠性。
基于数据库的分布式锁的一般步骤和一些考虑因素:
MySQL
、PostgreSQL
、Redis
等。不同的数据库引擎可能提供不同的特性和性能。注意:
基于数据库的分布式锁可以工作,但也需要谨慎考虑性能和可伸缩性问题。锁表可能成为瓶颈,尤其是在高并发环境中。在某些情况下,可能需要考虑使用更高级的分布式锁管理工具,例如ZooKeeper
、etcd
、Redis
的分布式锁等,以减轻数据库的负载。
基于Redis
的分布式锁是利用Redis
作为中心化的锁管理器。Redis
是一个高性能的内存数据库,具有原子操作和持久性的特性,适用于实现分布式锁。以下是如何创建基于Redis
的分布式锁的一般步骤:
Redis
中设置一个特定的键值对,其中键是锁的名称,值是客户端的标识符(通常是一个唯一的标识符,如UUID
)。这个设置操作需要使用Redis
的SETNX(Set if Not eXists)
命令,以确保只有一个客户端能够成功设置锁。如果客户端成功设置了锁,表示获取锁成功。SET lock_name client_identifier NX PX lock_timeout
各命令含义:
Redis
的DEL命令来删除锁的键,以释放锁。DEL lock_name
EXPIRE
命令或在设置锁时使用PX
参数来实现。EXPIRE lock_name lock_timeout
基于Redis
的分布式锁是一种简单而有效的方式来实现分布式锁,但也需要小心处理竞争和故障情况。还需要仔细选择锁的名称以及设置超时时间,以适应应用程序需求。请注意,虽然Redis
是一个快速的内存数据库,但要确保Redis服务器的高可用性和稳定性,以防止锁的单点故障。
基于ZooKeeper
的分布式锁是一种强大且高度可靠的分布式锁实现方法。ZooKeeper
是一个分布式协调服务,提供了分布式锁所需的原语。以下是如何创建基于ZooKeeper
的分布式锁的一般步骤:
ZooKeeper
连接:客户端需要首先创建到ZooKeeper
集群的连接,通常使用ZooKeeper
客户端库来实现。这个连接将用于创建锁节点以及监听锁的释放。ZooKeeper
中创建一个独立的有序临时节点。节点的路径通常包含锁的名称。ZooKeeper
上创建一个有序的临时节点来表示它想要获取锁。然后,它检查自己创建的节点是否是当前锁路径下最小的节点。如果是,表示客户端获得了锁。ZooKeeper
的事件通知,通知下一个等待的客户端可以尝试获取锁。ZooKeeper
提供了强大的分布式协调功能,可以处理各种异常和故障情况,包括客户端崩溃、网络问题等。可以使用ZooKeeper
的会话超时机制来检测客户端连接问题,并确保锁的可靠性。Redis
的分布式锁,可以为锁设置一个超时时间,以防止某个客户端长时间持有锁。基于ZooKeeper
的分布式锁是一种可靠和高度分布式的锁实现方法,适用于需要强一致性和高可用性的分布式系统。然而,使用ZooKeeper
也需要更多的配置和维护工作,因此要确保ZooKeeper
集群的稳定性和性能。此外,要考虑锁的公平性,以避免某个客户端长时间持有锁,阻塞其他客户端。
Redis
的分布式锁:使用Redis
内存数据库作为底层存储,利用Redis
的原子性操作来实现锁。ZooKeeper
的分布式锁:使用ZooKeeper
分布式协调服务作为底层存储,通过ZooKeeper
的节点操作和监听来实现锁。Redis
和ZooKeeper
快速,可能会引入较大的延迟。Redis
的分布式锁:Redis
是内存数据库,速度非常快,适合高性能的应用场景。ZooKeeper
的分布式锁:ZooKeeper
通常比数据库慢,但比数据库快。Redis
的分布式锁:Redis
通常具有高可用性和容错性,但需要定期备份和监控。ZooKeeper
的分布式锁:ZooKeeper
专注于分布式协调,通常具有高可用性和一致性,并提供了复杂的ZooKeeper
集群配置和管理。Redis
的分布式锁:Redis
锁通常是全局性的,只有一个锁的实例,因此适合用于全局资源的锁定。ZooKeeper
的分布式锁:ZooKeeper
锁通常是全局性的,但可以更容易地管理多个锁。Redis
的分布式锁:Redis
具有持久性选项,可以配置为在故障后自动恢复锁状态。ZooKeeper
的分布式锁:ZooKeeper
通常具有高可用性和故障恢复功能。基于Redis
和基于ZooKeeper
的分布式锁通常更适合需要高性能和高可用性的场景,而基于数据库的分布式锁可能更适合需要简化的场景或已经在使用数据库的应用程序。
参考 4.8 怎么使用Etcd做分布式锁?
ZooKeeper分布式锁:
ZooKeeper
提供了强一致性的特性,这是其设计的核心之一。在分布式锁的实现中,ZooKeeper
会保证锁的获取和释放的顺序,从而确保分布式环境中的一致性。ZooKeeper
的分布式锁通常通过创建临时有序节点来实现。每个客户端在ZooKeeper
上创建一个唯一的节点,并带有序号,最终形成一个有序的节点队列。锁的获取就是判断自己的节点是否是队列中最小的节点。Redis分布式锁:
Redis
分布式锁通常使用SETNX(SET if Not eXists)
命令实现。一个客户端尝试通过SETNX
在Redis
中设置一个键值对,如果设置成功,表示获取锁成功;否则,表示锁已经被其他客户端持有。Redis
分布式锁是非阻塞的,如果获取锁失败,客户端可以选择重试或者放弃。区别和选择:
ZooKeeper
提供了强一致性,适合需要强一致性保证的场景。Redis
分布式锁则是最终一致性,适合一些对一致性要求相对较低的场景。Redis
分布式锁通常比ZooKeeper
实现更为简单,性能也可能更好。如果应用场景对性能要求较高,而且可以接受最终一致性,Redis
分布式锁可能是一个更轻量级的选择。ZooKeeper
提供了更多的分布式协调服务,而不仅仅是分布式锁。如果项目中需要其他分布式协调服务,ZooKeeper
可能更适合。在选择分布式锁方案时,需要根据具体的场景和需求权衡一致性、性能和功能特性。
分布式缓存是一种用于提高应用程序性能和可伸缩性的技术,它将数据存储在多个节点上,以便快速访问和减轻后端存储的负载。分布式缓存通常位于应用程序和后端数据存储之间,可以大大减少对数据库或其他数据存储系统的访问频率,从而提高响应时间和降低系统负载。
分布式缓存的主要特点:
API
响应等。分布式缓存的常见用途:
常见的分布式缓存系统包括:
分布式缓存的具体实现:
实现分布式缓存涉及多个方面,包括选择合适的缓存系统、配置和管理缓存集群、缓存数据的存储和更新、缓存失效策略、数据一致性等。下面是一个具体的实现分布式缓存的步骤:
Redis
、Memcached
、Ehcache
等。每个系统都有自己的特点和适用场景。LRU
(最近最少使用)等缓存淘汰策略。分布式缓存的具体实现取决于应用程序需求和所选择的缓存系统。以上步骤提供了一个通用的指导框架,但每个应用程序都可能需要特定的配置和定制。确保在实施分布式缓存时,根据应用程序需求仔细考虑各个方面,并进行适当的测试和性能优化。
参考1:分布式唯一 ID 生成方案浅谈
参考2:讲分布式唯一id,这篇文章很实在
参考3:Leaf:美团分布式ID生成服务开源
主要有以下几种:
UUID
(Universally Unique Identifier
,即通用唯一标识码)算法的目的是生成某种形式的全局唯一ID
来标识系统中的任一元素,尤其是在分布式环境下,UUID
可以不依赖中心认证即可自动生成全局唯一ID
。UUID
的标准形式为32个十六进制数组成的字符串,且分割为五个部分,例如:467e8542-2275-4163-95d6-7adc205580a9。基于使用场景的不同,会存在以下几个不同版本的UUID
以供使用,如下所示:
UUID
:主要依赖当前的时间戳和机器mac
地址。优势是能基本保证全球唯一性,缺点是由于使用了mac
地址,会暴露mac
地址和生成时间;UUID
:将基于时间的UUID
算法中的时间戳前四位替换为POSIX
的UID
或GID
。优势是能保证全球唯一性,缺点是很少使用,常用库基本没有实现;UUID
:基于随机数或伪随机数生成。优势是实现简单,缺点是重复几率可计算;UUID
(MD5
版):基于指定的名字空间/名字生成MD5
散列值得到。优势是不同名字空间/名字下的UUID
是唯一的,缺点是MD5
碰撞问题,只用于向后兼容;UUID
(SHA1版):将基于名字空间的UUID
(MD5
版)中国的散列算法修改为SHA1
。优势是不同名字空间/名字下的UUID
是唯一的,缺点是SHA1
计算相对耗时。UUID
的优势是性能非常高,由于是本地生成,没有网络消耗。而其也存在一些缺陷,包括不易于存储,UUID
太长,16字节128位,通常以36长度的字符串表示;信息不安全,基于时间的UUID
可能会造成机器的mac
地址泄露;ID
作为DB
主键时在特定的场景下会存在一些问题。
2. 数据库自增ID
数据库自增ID
是最常见的一种生成ID
方式。利用数据库本身来进行设置,在全数据库内保持唯一。优势是使用简单,满足基本业务需求,天然有序;缺点是强依赖ID
,会由于数据库部署的一些特性而存在单点故障、数据一致性等问题。
3. Redis生成ID
主要使用Redis
的原子操作INCR
和INCRBY
来实现。优势是不依赖于数据库,使用灵活,性能也优于数据库;而缺点则是可能要引入新的组件Redis
,如果Redis
出现单点故障问题,则会影响序号服务的可用性。
4. Zookeeper生成ID
主要是利用Zookeeper
的znode
数据版本来生成序列号,可以生成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
返回给业务侧;ID
,2位回拨可允许标记三次时钟较长时间的回拨,基本够使用。如果超过回拨次数,可以再选择报错或抛出异常。这些问题只是分布式开发中的一部分,而实际挑战可能因应用程序的特定需求和规模而有所不同。解决这些问题需要仔细的设计、合适的工具和技术,以及经验丰富的开发团队。分布式系统的复杂性和挑战性是一个长期的研究领域,也是计算机科学领域的重要主题之一。
总的来说,分布式架构在需要提高系统可用性、性能扩展、弹性、容错性以及处理大规模数据等方面都发挥着重要的作用。不同的应用场景需要不同的分布式架构设计和技术选择。
protobuf
是一种二进制协议,相对于文本协议(如JSON
和XML
)来说,它的序列化和反序列化速度更快,生成的数据体积更小。这对于在微服务之间传输大量数据时非常重要,可以提高性能和减少带宽消耗。protobuf
具有广泛的编程语言支持,包括Java
、C++
、Python
、Go
等,可以使用不同编程语言编写的微服务之间进行通信,而无需担心语言之间的兼容性问题。protobuf
支持版本化,可以在不破坏现有代码的情况下添加新字段或删除旧字段。这对于微服务的演进和升级非常有用,因为它们可以独立地更新和部署。protobuf
的消息定义是独立于编程语言的,可以在一个地方定义消息结构,然后在不同的语言中使用这些消息。这有助于确保消息的一致性和互操作性。protobuf
使用强类型的数据结构,可以在编译时检测到数据结构的错误,而不是在运行时。这有助于提高代码的稳定性和可维护性。protobuf
的消息定义仍然相对容易阅读和维护。它提供了清晰的消息结构和字段描述,使开发人员能够理解消息的含义。protobuf
在微服务领域有一个丰富的生态系统,许多工具和框架都提供了对protobuf
的支持,例如gRPC
、Thrift
等,它们可以更轻松地构建和部署微服务。总结:
protobuf
在微服务架构中因其高性能、跨语言支持、版本兼容性等特点而备受青睐。尽管它需要在微服务之间定义消息结构并生成代码,但这种额外的工作可以带来在通信效率和可维护性方面的巨大好处。根据需求和技术栈,选择protobuf
可能是一个明智的选择。
Protocol Buffers(简称 Protobuf)
是一种用于序列化结构化数据的方法,它可以用于数据交换、通信协议、数据存储等各种应用。Protobuf
有两个主要版本,分别是Protobuf 2
(也称为Proto2
)和Protobuf 3
(也称为Proto3
),它们之间有一些重要的区别:
Proto2
使用的是非严格的可选字段(optional
)、必填字段(required
)和重复字段(repeated
)等概念。字段默认是可选的,而且可以为NULL
(或默认值)。Proto3
精简了语法,只支持单一的字段语法,即所有字段都是可选的,并且不存在required
和optional
的概念。这意味着在Proto3
中,所有字段都可以为零值。Proto2
中,字段可以具有默认值,如果字段没有设置值,它将使用默认值。这可以导致某些情况下难以确定字段是否明确设置为默认值还是未设置。Proto3
中默认值的概念被移除,字段不再具有默认值,而是始终具有零值。这样可以更清晰地表示字段是否明确设置。Proto2
消息时遇到未知字段,它们将被忽略,而不会引发错误。这使得Proto2
消息可以向前兼容。Proto3
中,未知字段将导致解析失败,因为Proto3
旨在更严格地确保数据的一致性。Proto2
使用了一种相对较为复杂的变长编码方案(Varint
、Zigzag
等)。Proto3
使用更简单的固定长度编码,这有助于提高解析性能。Proto2
中枚举的第一个值默认为0,如果不显式设置值,后续枚举值会依次自增。Proto3
中的枚举从0开始,但不再支持自增,枚举值必须显式设置。Proto2
支持32
位和64
位浮点数。Proto3
支持32
位和64
位浮点数,但不再支持fixed
和required
。需要注意的是,升级现有的Protobuf 2
到Protobuf 3
可能需要进行一些修改,因为它们的语法和行为之间存在显著差异。因此,迁移时需要谨慎处理现有的Protobuf
数据定义。选择使用哪个版本取决于项目需求,新项目通常可以考虑使用Proto3
,因为它提供了更简单、清晰和严格的语法。
Protobuf
通常比JSON
更紧凑,序列化后的数据更小。这是因为Protobuf
使用了二进制编码,不包含冗余的字段名和数据类型信息。JSON
是文本格式,相对于二进制格式来说,通常更大,因为它包含了字段名和数据类型等描述性信息。Protobuf
是二进制格式,对人类来说不太可读。它主要用于机器间的数据交换。JSON
是文本格式,易于阅读和编辑。它通常用于配置文件、REST API
和与人类交互的场景。Protobuf
通常比JSON
快,因为它的编解码速度更快,生成的数据更小。这使得Protobuf
特别适用于高性能和低延迟的应用程序。JSON
的解析和生成速度相对较慢,尤其是在处理大量数据时。Protobuf
支持数据结构的演化,可以向现有的Protobuf
消息添加新字段而不会破坏现有的兼容性。这使得它适用于长期维护的系统。JSON
不太适合数据结构的演化,因为更改JSON
结构可能需要更新所有相关的代码。Protobuf
提供了多种编程语言的支持,可以通过不同语言的生成代码进行序列化和反序列化。JSON
同样具有广泛的跨语言支持,因为几乎所有编程语言都有JSON
解析和生成库。Protobuf
支持可选字段和默认值,允许明确指定字段是否存在以及字段的默认值。JSON
中的字段默认都是可选的,如果字段不存在,则通常假定为null
或缺失。Protobuf
提供原生的枚举类型支持,允许定义一组有限的可能值。JSON
不具备原生的枚举类型,通常使用字符串或数字表示。Protobuf
和JSON
在不同的用例中都有优点和缺点。Protobuf
适用于高性能、紧凑、二进制的数据交换,尤其在内部通信和大规模分布式系统中表现良好。JSON
则适用于易读性和可编辑性要求高的场景,如配置文件和REST API
。选择合适的格式取决于应用程序需求和用例。有时候,甚至可以在两者之间进行转换,以满足不同系统之间的需求。
在Protocol Buffers(Protobuf)
中,每个字段后的序号(field number
)是用来标识和识别消息中的字段的唯一标识符。这些序号的作用包括:
Protobuf
可以在解析数据时根据序号而不是字段名来识别字段。这意味着可以添加、删除或重新排列字段,而不会破坏与旧版本数据的兼容性。Protobuf
不要求字段按序号排序,但通常建议按顺序编写字段定义。Protobuf
解析器可以更快速地定位和解析消息中的字段,而无需查找字段名称。注意:
一旦定义了字段的序号,就不应该再更改它们。因为序号与消息结构的演化和数据兼容性密切相关,更改序号可能导致不兼容的问题。因此,序号的选择需要谨慎考虑,并且通常在消息定义中保持稳定。
在Protocol Buffers(Protobuf)
中,序列化是将消息对象转换为二进制数据的过程。以下是使用Go
中的Protobuf
库进行消息序列化的一般步骤:
Protobuf
语法编写.proto
文件来描述消息。例如:syntax = “proto3”;
message Person {
int32 id = 1;
string name = 2;
string email = 3;
}
Protobuf
编译器(通常称为protoc
)来生成相应语言的代码。在Go
中,可以使用protoc-gen-go
插件生成Go
代码。运行以下命令:protoc --go_out=. your_protobuf_file.proto
Go
代码中,使用生成的消息结构创建消息对象。例如:person := &Person{
Id: 1,
Name: “John Doe”,
Email: “johndoe@example.com”,
}
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
结构体。
在Protocol Buffers(Protobuf)
中,确保字段的向后和向前兼容性是非常重要的,以便在更新消息定义时不会破坏与现有数据的兼容性。以下是需要注意的一些事项,以确保字段兼容性:
deprecated
),而不是删除它。弃用字段将仍然保留在消息中,但不再使用,以便旧版本的解析器可以忽略它们。optional
)更改为必填字段(required
),则需要确保向旧版本的消息提供默认值,以便旧版本的解析器可以正确解析新版本的消息。这是为了防止旧版本的消息在解析时引发错误。deprecated
标记将其标记为弃用。这样做会向开发人员发出警告,表示不应再使用该字段,但它仍然存在于消息定义中,以确保与旧版本的兼容性。充分考虑这些因素,可以帮助确保在更新消息定义时保持与旧版本数据的兼容性,从而实现平滑的升级和迁移。 Protobuf
的强大之处在于它提供了良好的向前和向后兼容性支持,但仍然需要谨慎处理消息定义的修改。
不能,Protobuf
编译器将会报告错误,并阻止代码生成。
在Protocol Buffers(Protobuf)
中,字段序号(field number
)必须是唯一的,否则编译器不会生成有效的代码。如果试图在同一消息定义中使用相同的字段序号多次,Protobuf
编译器将会报告错误,并阻止代码生成。
这是因为字段序号用于标识和识别消息中的字段,它们必须是唯一的,以确保消息的正确解析和序列化。如果多个字段具有相同的序号,编解码器将无法准确地识别字段,这会导致严重的数据一致性问题。
因此,如果需要添加新字段或更改现有字段的序号,请确保为每个字段分配唯一的序号,以避免编译错误和与旧版本数据的不兼容性。
Protocol Buffers(Protobuf)
在数据传输和存储时通常比文本格式(如JSON
)更紧凑,这主要是因为Protobuf
使用了一种紧凑的二进制编码格式。以下是Protobuf
如何进行压缩的关键特性:
JSON
等文本格式中,每个字段都需要包含字段名,这会增加数据的大小。而在Protobuf
中,字段名不包含在序列化的数据中,只有字段的唯一标识符(字段序号)以及字段的值。这消除了冗余的字段名信息,使数据更紧凑。Protobuf
使用一种称为可变长度编码(Variable Length Encoding
)的方法来编码整数值,以及一种紧凑的方式来表示浮点数和其他数据类型。这种编码方式允许较小的数字占用较少的字节,而较大的数字占用较多的字节,从而减小了数据的大小。Protobuf
中,字段的序号和值之间使用一种紧凑的编码方式进行分隔,不需要额外的字符。Protobuf
不会在序列化时包括这些字段。这减少了数据的大小,因为默认值不需要重复传输。Protobuf
采用字段压缩的方式,将多个字段的数据组合在一起,以减少字段标识符的重复出现。这特别适用于重复字段,如数组或列表。总结:
Protobuf
的紧凑二进制编码方式使其在数据传输和存储方面具有出色的性能,尤其适用于需要高效率和低带宽消耗的场景。然而,需要注意的是,虽然Protobuf
在性能和大小方面表现出色,但由于其是二进制格式,不如JSON
那样易于人类阅读和调试。因此,在选择数据序列化格式时,需要根据具体需求权衡各种因素。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
SON等文本格式中,每个字段都需要包含字段名,这会增加数据的大小。而在
Protobuf中,字段名不包含在序列化的数据中,只有字段的唯一标识符(字段序号)以及字段的值。这消除了冗余的字段名信息,使数据更紧凑。 2. **可变长度编码**:
Protobuf使用一种称为可变长度编码(
Variable Length Encoding)的方法来编码整数值,以及一种紧凑的方式来表示浮点数和其他数据类型。这种编码方式允许较小的数字占用较少的字节,而较大的数字占用较多的字节,从而减小了数据的大小。 3. **不需要额外的分隔符**:在文本格式中,通常需要使用逗号、冒号等分隔符来分隔字段和值,这增加了数据的大小。在
Protobuf中,字段的序号和值之间使用一种紧凑的编码方式进行分隔,不需要额外的字符。 4. **默认值省略**:如果字段的值与其默认值相同,
Protobuf不会在序列化时包括这些字段。这减少了数据的大小,因为默认值不需要重复传输。 5. **字段压缩**:
Protobuf`采用字段压缩的方式,将多个字段的数据组合在一起,以减少字段标识符的重复出现。这特别适用于重复字段,如数组或列表。
总结:
Protobuf
的紧凑二进制编码方式使其在数据传输和存储方面具有出色的性能,尤其适用于需要高效率和低带宽消耗的场景。然而,需要注意的是,虽然Protobuf
在性能和大小方面表现出色,但由于其是二进制格式,不如JSON
那样易于人类阅读和调试。因此,在选择数据序列化格式时,需要根据具体需求权衡各种因素。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-YSPiH6ug-1713217060151)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。