如何高效地阅读开源项目源代码

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 这篇文章是萌叔对阅读开源项目源代码的一般思路和方法的总结。 2. 带着问题去阅读? 首先需要搞清楚为什么你要阅读这个项目的源代码?你想从阅读这个开源项目获得什么? 是看项目的代码如何布局? 以RocketMQ为例,你是想了解为什么RocketMQ有强大的吞吐能力? 以RocketMQ为例,Clustering模式下,同组的消费者如何保证负载均衡? 以RocketMQ为例,你是想了解死信队列的作用? 总之需要在阅读之前多给自己提问题?在阅读的过程中也要给自己不断的提问题。 一个成熟的开源项目的源代码动辄就是上万行。根据二八法则,其实核心的代码顶多就是20%,剩下的大量都是参数解析,判断各种条件,处理错误、异常情况等防御性编码。在阅读时,要学会跳过这些代码,只关注自己感兴趣的代码,以节约时间。 3. 阅读官方文档并使用一段时间后,再去阅读源代码 “没有调查,没有发言权”–李德胜 建议先阅读官方文档,并针对开源项目对应的工具或者服务使用一段时间,再去看源码。官方文档中往往会有关于这个服务的架构说明,核心原理的讲解,有的还会给出最佳实践,这对于我们快速理解这个项目的有很大的帮助。 使用一段时间有助于加深对项目的理解,了解它的应用场景,优点和缺点,也可以在脑海中产生更多的问题。如果是服务,能够亲手搭建一次最好,特别是要关注配置文件中的参数说明,留意哪些参数可能对服务性能造成的影响。 4. 站在巨人的肩膀上 充分借鉴前人的智慧,搜搜已经公开的资料,看有没有诸如 “xxx源码解析” “xxx原理” “xxx总结” “xxx过程分析” 如果运气好,已经有人对你疑惑的问题做出了解答。如果运气再好点,说不定还会把核心代码涉及的文件、函数列得清清楚楚,这样通常我们只需要沿着文章的脉络把核心代码再看一遍即可。 5. 从哪里开始? 5.1 顺着接口往里看 对于有接口的服务,可以顺着接口往里看,从接收到请求开始,到最后输出响应结果 5.2 从数据库看起 对于有数据库的服务,还可以从数据库表结构看起,看都针对数据库表都有哪些操作 5.3 从Example和单元测试看起 example和单元测试往往都是侧重一个场景。从特定场景入手会比较好理解 6. 抓住核心脉络 6.1 数据流&控制流 绝大多工具或者服务都可以看做过滤器 所以我们只要关注数据在系统中的流动就可以了,还有的系统除了数据流之外,还有单独的控制流 6.2 状态机 一些系统内部有比较多的状态,比如订单系统,这种情况我们可以手绘一下状态机,多关注一下状态变换所需要触发的条件。 7. 举一反三,多做对比 比如我们再看RocketMQ源码的时候,我们就可以想想它和kafka在架构上有什么不同,各有什么优点缺点。 8. 多做分享 人们对一个知识的理解程度往往可以分为三个层次。 当你能够把看完的一个开源项目,并能够给其他人讲明白的时候,你才算是真的搞明白了。另外分享的过程也是查余补缺的过程,一些原先你没有关注的点,可以重新熟悉起来。在分享的过程中,与他人交流的过程,思想的碰撞往往带来新的火花。 萌叔认为 技术分享是利人利己的好事,大家可以多做。 总结 以上就是萌叔的阅读源代码的经验总结,希望对大家有所帮助。

October 21, 2022 · 1 min

介绍一个grpc记录和重放库 vearne/grpcreply-使用篇

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 gRPC协议应用的非常广泛,针对它流量的记录和重放是一个非常常见的需求。但是到目前为止社区中都没有找到合适的库来实现这个目标。 终于它来了。传送门: vearne/grpcreplay 2.介绍 vearne/grpcreplay是一个网络监控工具,可以记录您的 grpc流量(Unary RPC),并将其用于灰度测试、压测或者流量分析。它有2大特点,使用简单,并且可以直接解析Protobuf的请求,并以JSON形式输出。 在使用vearne/grpcreplay前,你必须要知道vearne/grpcreplay的限制条件: 支持解析Unary RPC,不支持streaming RPC Server端必须开启反射 参考GRPC Server Reflection Protocol 3. 编译&使用 确保server所在的主机上已经安装了libpcap 安装libpcap可以参考以下命令: Ubuntu apt-get install libpcap-dev Centos yum install libpcap-devel Mac brew install libpcap 编译 make build 示例 server package main import ( "context" "encoding/json" "github.com/grpc-ecosystem/go-grpc-middleware" pb "github.com/vearne/grpcreplay/example/search_proto" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/reflection" "google.golang.org/grpc/status" "log" "net" "runtime/debug" "time" ) const PORT = "35001" type SearchServer struct{} func (s SearchServer) Search(ctx context.Context, in *pb.SearchRequest) (*pb.SearchResponse, error) { return &pb.SearchResponse{StaffID: 100, StaffName: in.StaffName}, nil } func (s SearchServer) CurrentTime(ctx context.Context, request *pb.TimeRequest) (*pb.TimeResponse, error) { return &pb.TimeResponse{CurrentTime: time.Now().Format(time.RFC3339)}, nil } func main() { opts := []grpc.ServerOption{ //grpc.Creds(c), grpc_middleware.WithUnaryServerChain( RecoveryInterceptor, LoggingInterceptor, ), //grpc.HeaderTableSize(0), //grpc.WithDisableRetry(), } server := grpc.NewServer(opts...) pb.RegisterSearchServiceServer(server, &SearchServer{}) // 注册反射服务(非常重要) // Register reflection service on gRPC server. reflection.Register(server) lis, err := net.Listen("tcp", ":"+PORT) if err != nil { log.Fatalf("net.Listen err: %v", err) } server.Serve(lis) } func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { log.Printf("gRPC method: %s, %v", info.FullMethod, req) resp, err := handler(ctx, req) bt, _ := json.Marshal(req) log.Println("body", string(bt)) log.Printf("gRPC method: %s, %v", info.FullMethod, resp) return resp, err } func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { defer func() { if e := recover(); e != nil { debug.PrintStack() err = status.Errorf(codes.Internal, "Panic err: %v", e) } }() return handler(ctx, req) } client ...

October 17, 2022 · 2 min

手撸了一个Golang协程池

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 传送门: vearne/executor 0. 引言 本项目受到Java Executors的启发 Executors,并参考了ExecutorService的接口设计。 支持取消单个任务,或者取消整个集群的任务。 并提供了一个可以动态扩容和缩容的实现DynamicGPool。 欢迎提PR。下面是更详细的说明: 1. 特性 支持取消单个任务,也可以取消协程池上的所有任务 Future.Cancel() ExecutorService.Cancel() 你也可以使用context.Context取消task或者pool。 可以创建多种不同类型的协程池(SingleGPool|FixedGPool|DynamicGPool) 2. 多种类型的协程池 类别 说明 备注 SingleGPool 单个worker协程池 FixedGPool 固定数量worker协程池 DynamicGPool worker数量可以动态变化的协程池 min:最少协程数量 max:最大协程数量 2.1 SingleGPool NewSingleGPool(ctx context.Context, opts ...option) ExecutorService 2.2 FixedGPool NewFixedGPool(ctx context.Context, size int, opts ...option) ExecutorService 2.3 DynamicGPool NewDynamicGPool(ctx context.Context, min int, max int, opts ...dynamicOption) ExecutorService 2.3.1 扩容规则 提交任务时,如果任务队列已经满了,则尝试增加worker去执行任务。 2.3.2 缩容规则: 条件: 如果处于忙碌状态的worker少于worker总数的1/4,则认为满足条件 执行meetCondNum次连续检测,每次间隔detectInterval。如果每次都满足条件,触发缩容。 缩容动作尝试减少一半的worker 3. 注意 由于executor使用了channel作为作为任务队列,所以提交任务时,可能会发生阻塞。 ...

September 26, 2022 · 2 min

Golang 模糊测试 简明教程

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 Golang1.18 引入了一个模糊测试的功能。文本萌叔将会简单介绍下这个特性。 2.模糊测试 传送门 比如我们编写了一个工具包来做除法 package fuzz func Div(a, b int) int { return a / b } 由于疏忽忽略了某些边界条件。显然在上面的代码中,b不能等于0,否则会发生运行时错误。 普通的单元测试 func TestDiv(t *testing.T) { testcases := []struct { a, b, want int }{ {10, 2, 5}, {5, 3, 1}, {-6, 3, -2}, {-6, -3, 2}, } for _, tc := range testcases { result := Div(tc.a, tc.b) if Div(tc.a, tc.b) != tc.want { t.Errorf("Div: %q, want %q", result, tc.want) } } } 由于对分支条件和异常情况考虑的不周全,自测的单元测试能够正常通过。但是一旦b=0,程序将会崩溃。 ...

September 14, 2022 · 1 min

Golang embed(内嵌)

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 引言 Golang编译成单个bin文件,发布的时候相当方便了。但是某个配置文件没有办法直接打在bin文件中,比如SSL的密钥,html模板等等。 Golang 1.16 标准库引入了embed包,它可以在编译期间将外部文件嵌入到bin包中。 嵌入的文件都是只读的,因此是协程安全的。 另外由于编译期间嵌入的,因此如果文件发生变化,需要重新编译。 2. 示例 2.2 示例1 嵌入文件夹 传送门 . ├── html │ ├── a.html │ └── index.html └── main.go package main import ( "embed" "log" "net/http" ) //go:embed html var content embed.FS func main() { mutex := http.NewServeMux() mutex.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content)))) err := http.ListenAndServe(":8080", mutex) if err != nil { log.Fatal(err) } } 2.2 示例2 嵌入模板文件 传送门 . ├── main.go └── template ├── hello.tpl └── hello2.tpl package main import ( "bytes" "embed" "html/template" "log" ) //go:embed template/*.tpl var mytpl embed.FS func main() { check := func(err error) { if err != nil { log.Fatal(err) } } list, err := mytpl.ReadDir("template") check(err) for _, item := range list { log.Println("template/" + item.Name()) } data, err := mytpl.ReadFile("template/hello.tpl") log.Println("data:", string(data)) check(err) t, err := template.New("hello").Parse(string(data)) content := bytes.NewBufferString("") err = t.Execute(content, map[string]string{"name": "张三"}) check(err) log.Println(content.String()) } 2.3 示例3 嵌入字符串/字节数据 传送门 ...

September 2, 2022 · 1 min

go-resty/resty中Trace Info说明

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 今天萌叔翻看Golang的request库go-resty/resty,看了请求trace Info的相关的细节。 程序如下: package main import ( "fmt" "github.com/go-resty/resty/v2" ) func main() { target := "https://vearne.cc/" client := resty.New() resp, err := client.R(). EnableTrace(). Get(target) if err != nil { fmt.Println("error:", err) return } fmt.Println(len(resp.String()), resp.StatusCode()) // Explore trace info fmt.Println("Request Trace Info:") ti := resp.Request.TraceInfo() fmt.Println(" DNSLookup :", ti.DNSLookup) fmt.Println(" ConnTime :", ti.ConnTime) fmt.Println(" TCPConnTime :", ti.TCPConnTime) fmt.Println(" TLSHandshake :", ti.TLSHandshake) fmt.Println(" ServerTime :", ti.ServerTime) fmt.Println(" ResponseTime :", ti.ResponseTime) fmt.Println(" TotalTime :", ti.TotalTime) fmt.Println(" IsConnReused :", ti.IsConnReused) fmt.Println(" IsConnWasIdle :", ti.IsConnWasIdle) fmt.Println(" ConnIdleTime :", ti.ConnIdleTime) fmt.Println(" RequestAttempt:", ti.RequestAttempt) fmt.Println(" RemoteAddr :", ti.RemoteAddr.String()) } 输出: ...

July 22, 2022 · 1 min

从http.Transport看连接池的设计

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文基于Golang 1.17.6 1.前言 之前萌叔曾在文章 imroc/req 连接池使用须知 提及过Golang标准库net/http提供的连接池http.Transport,但是是浅尝辄止。 本文萌叔想从http.Transport出发来谈谈一个连接池设计应该考虑哪些问题? 2.连接池的功能特征 下图是针对Grpc和Thrift压测结果(见参考资料1)。我么可以看出,长连接相比与短连接相比,QPS大概提升了1倍多。这是因为长连接减少连接建立的所需的3次握手。 要对长连接进行管理,特别是对闲置的长连接进行管理,就不可避免的引入连接池。 特征 需要一个连接时,并不一定真的创建新连接,而是优先尝试从连接池选出空闲连接;如果连接池对应的连接为空,才创建新连接。 销毁并不是真的销毁,而是将使用完毕的连接放回连接池中(逻辑关闭)。 这里引出了几个问题。 Q1:获取连接阶段,我们有没有办法知道从连接池中取出的空闲连接(复用)是有效的,还是无效的? Q2:把使用完毕的连接放回连接池的阶段,空闲连接数量是否要做上限的约束。如果空闲连接数量有上限约束且空闲连接的数量已经达到上限。那么把连接放回连接池的过程,必然需要将之前的某个空闲连接进行关闭,那么按照什么规则选择这个需要关闭的连接。 Q3:放置在连接池中的连接,随着时间的流逝,它可能会变成无效连接(stale)。比如由于Server端定时清理空闲连接。那么为了确保连接池中连接的有效性,是否需要引入定时的检查逻辑? 3.net/http中连接池的实现 net/http中连接池的实现代码在 net/http/transport.go 中 获取连接 Transport.RoundTrip() -> Transport.getConn() 放回连接(逻辑关闭) Response.Body.Close() -> bodyEOFSignal.Close() -> Transport.tryPutIdleConn() 为了约束空闲连接的数量,连接池引入了几个变量: MaxIdleConns MaxIdleConns controls the maximum number of idle (keep-alive) connections across all hosts. 所有host总的最大空闲连接数量,默认值是100 MaxIdleConnsPerHostif non-zero, controls the maximum idle (keep-alive) connections to keep per-host。 针对每个Host能够保持的最大空闲连接数量。默认值是2 这个是一个比较有意思的变量,因为默认情况,所有的HTTP请求都使用同一个连接池,由于MaxIdleConns存在,如果针对某个Host的 连接占用了大量空间,那么针对其它Host的连接可能就没有存储空间了。 MaxConnsPerHost MaxConnsPerHost optionally limits the total number of connections per host, including connections in the dialing, active, and idle states. 默认值是0,表示不限制。 ...

June 20, 2022 · 2 min

REDIS-CLUSTER集群slot迁移过程分析

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.前言 在前面的文章中, 《REDIS-CLUSTER集群创建内部细节详解》 萌叔创建一个Redis集群。 这篇文章,我会为集群添加2个节点,并介绍slot的迁移过程。 2.集群扩容 添加主节点127.0.0.1:7006 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 添加从节点127.0.0.1:7007(并指定其Master节点) redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000 --cluster-slave --cluster-master-id 86f3cb72813a2d07711b56b3143ff727911f4e1e 新添加的节点上并没有slot分布,需要通过命令让slot重新分布 root@BJ-02:~/cluster-test/7007# redis-cli --cluster reshard 127.0.0.1:7000 >>> Performing Cluster Check (using node 127.0.0.1:7000) S: 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000 slots: (0 slots) slave replicates be905740b96469fc6f20339fc9898e153c06d497 M: 86f3cb72813a2d07711b56b3143ff727911f4e1e 127.0.0.1:7006 slots: (0 slots) master 1 additional replica(s) M: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: 9e29dd4b2a7318e0e29a48ae4b37a7bd5ea0a828 127.0.0.1:7007 slots: (0 slots) slave replicates 86f3cb72813a2d07711b56b3143ff727911f4e1e S: 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003 slots: (0 slots) slave replicates 784fa4b720213b0e2b51a4542469f5e318e8658b M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004 slots: (0 slots) slave replicates 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 0 How many slots do you want to move (from 1 to 16384)? 1 What is the receiving node ID? 86f3cb72813a2d07711b56b3143ff727911f4e1e Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1: all Ready to move 1 slots. Source nodes: M: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) Destination node: M: 86f3cb72813a2d07711b56b3143ff727911f4e1e 127.0.0.1:7006 slots: (0 slots) master 1 additional replica(s) Resharding plan: Moving slot 5461 from 784fa4b720213b0e2b51a4542469f5e318e8658b Do you want to proceed with the proposed reshard plan (yes/no)? yes Moving slot 5461 from 127.0.0.1:7001 to 127.0.0.1:7006: 3. Slot迁移过程 迁移过程在clusterManagerMoveSlot() 中 ...

June 4, 2022 · 3 min

Redis-Cluster集群创建内部细节详解

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文基于redis 6.2.7 1.引言 Redis-Cluster集群的搭建非常简单。搭建过程见参考资料1。 执行 redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \ 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ --cluster-replicas 1 root@BJ-02:~/cluster-test# redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \ > 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ > --cluster-replicas 1 >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 127.0.0.1:7004 to 127.0.0.1:7000 Adding replica 127.0.0.1:7005 to 127.0.0.1:7001 Adding replica 127.0.0.1:7003 to 127.0.0.1:7002 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000 slots:[0-5460] (5461 slots) master M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master S: 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003 replicates 784fa4b720213b0e2b51a4542469f5e318e8658b S: 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004 replicates 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 S: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 replicates 7eb7ceb4d886580c6d122e7fd92e436594cc105e Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join .. >>> Performing Cluster Check (using node 127.0.0.1:7000) M: 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004 slots: (0 slots) slave replicates 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 S: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots: (0 slots) slave replicates 7eb7ceb4d886580c6d122e7fd92e436594cc105e S: 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003 slots: (0 slots) slave replicates 784fa4b720213b0e2b51a4542469f5e318e8658b [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. 验证 root@BJ-02:~# redis-cli -h 127.0.0.1 -p 7000 127.0.0.1:7000> cluster nodes 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001@17001 master - 0 1652672999293 2 connected 5461-10922 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000@17000 myself,master - 0 1652672998000 1 connected 0-5460 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002@17002 master - 0 1652673000333 3 connected 10923-16383 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004@17004 slave 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 0 1652673000126 3 connected be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005@17005 slave 7eb7ceb4d886580c6d122e7fd92e436594cc105e 0 1652673001373 1 connected 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003@17003 slave 784fa4b720213b0e2b51a4542469f5e318e8658b 0 1652673000000 2 connected 一行命令就完成了集群的部署,redis-cli --cluster create到底做了什么。这篇文章萌叔将带你探寻其中的秘密。 ...

May 16, 2022 · 3 min

Redis-Cluster集群模式下Redis客户端如何获得slot的路由信息

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 我们都知道在Redis-Cluster集群模式下,集群中有18634个slot,slot分布在集群多个实例上,当执行一个Command时,Redis客户端会提取Command中的key 根据下面的算法得出key所属的slot slot=CRC16(key)&16383 在根据客户端中的路由表,找到slot所在的Redis实例 这里的路由表存储的就是 slot -> redis-instance 那么问题来了redis客户端是如何得到这个路由表的呢? 2. 分析 下面以go-redis/redis的代码为例,谈谈Redis客户端如何获取和维护slot路由信息。 2.1 存储结构 type clusterClient struct { opt *ClusterOptions nodes *clusterNodes state *clusterStateHolder // 在这里 cmdsInfoCache *cmdsInfoCache //nolint:structcheck } type clusterState struct { nodes *clusterNodes Masters []*clusterNode Slaves []*clusterNode slots []*clusterSlot // 路由信息存储在这里 generation uint32 createdAt time.Time } type clusterSlot struct { start, end int nodes []*clusterNode } 2.2 命令执行过程 1)通过key计算出对应slot 2)通过路由表查找到对应的node信息 3)向node发送CMD 其实第2步根据slot从clusterState中查询对应clusterNode ...

May 14, 2022 · 2 min