前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >etcd源码分析 - 2.【打通核心流程】PUT键值对匹配处理函数

etcd源码分析 - 2.【打通核心流程】PUT键值对匹配处理函数

作者头像
junedayday
发布2022-12-02 19:11:59
5830
发布2022-12-02 19:11:59
举报
文章被收录于专栏:Go编程点滴Go编程点滴

在阅读了etcd server的启动流程后,我们对很多关键性函数的入口都有了初步印象。

那么,接下来我们一起看看对键值对的修改,在etcd server内部是怎么流转的。

PUT键值对的HTTP请求

etcdctl这个指令,我们可以快速地用命令etcdctl put key value发送PUT键值对的请求。

etcdctl对请求做了封装,我们要了解原始的HTTP请求格式,才能方便地阅读相关代码。相关的途径有很多,比如抓包、读源码等,这里为了可阅读性,我给出一个curl请求。

代码语言:javascript
复制
curl -L http://localhost:2379/v3/kv/put -X POST -d '{"key":"mykey","value":"myvalue"}'

主要关注如下三点:

  1. Method - POST
  2. URL - /v3/kv/put
  3. Body - {"key":"mykey","value":"myvalue"}

这个请求是v3版本的,而v2版本的差异比较大,暂不细谈。

Mux的路由匹配

背景知识介绍

为了更好地介绍下面的内容,我先介绍mux下的2个概念。

  • pattern指的是一种URL的匹配模式,最常见的如全量匹配、前缀匹配、正则匹配。当一个请求进来时,它会有自己的一个URL,去匹配mux中预先定义的多个pattern,找到一个最合适的。这是一种URL路由规则的实现
  • 当请求匹配到一个pattern后,就会执行它预定义的handler,也就是一个处理函数,返回结果。

所以, pattern负责匹配,而handler负责执行。在不同语境下,它们的专业术语有所差异,大家自行对应即可。

http mux的创建

我们要找HTTP1.X的路由匹配逻辑,就回到了上一节最后看到的代码中:

代码语言:javascript
复制
// 创建路由匹配规则
httpmux := sctx.createMux(gwmux, handler)

// 新建http server对象
srvhttp := &http.Server{
  Handler:  createAccessController(sctx.lg, s, httpmux),
  ErrorLog: logger, // do not log user error
}
// 这个cumx.HTTP1是检查协议是否满足HTTP1
httpl := m.Match(cmux.HTTP1())
// 运行server
go func() { errHandler(srvhttp.Serve(httpl)) }()

(*serveCtx)createMux

本函数不长,但很容易让读源码的同学陷入误区,我们一起来看看。这块代码主要分为三段:

代码语言:javascript
复制
func (sctx *serveCtx) createMux(gwmux *gw.ServeMux, handler http.Handler) *http.ServeMux {
 httpmux := http.NewServeMux()
  // 1.注册handler
 for path, h := range sctx.userHandlers {
  httpmux.Handle(path, h)
 }

  // 2.注册grpcGateway mux中的handler到/v3/路径下
 if gwmux != nil {
  httpmux.Handle(
   "/v3/",
   wsproxy.WebsocketProxy(
    gwmux,
    wsproxy.WithRequestMutator(
     // Default to the POST method for streams
     func(_ *http.Request, outgoing *http.Request) *http.Request {
      outgoing.Method = "POST"
      return outgoing
     },
    ),
    wsproxy.WithMaxRespBodyBufferSize(0x7fffffff),
   ),
  )
 }
  // 3.注册根路径下的handler
 if handler != nil {
  httpmux.Handle("/", handler)
 }
 return httpmux
}

第一点,可以通过简单的代码阅读,看到是对pprofdebug这些通用功能的URL功能注册,也是一些用户自定义的handler注册,这就很好地对应到sctx.userHandlers这个变量的命名了。

第三点很快就能被排除,它注册的是对根路径下的handler。我们阅读代码,找到handler最原始的生成处,就能看到它是对version、metrcis这类handler的注册。

所以,我们的重点就放在了gwmux这个对象上。阅读它的创建过程,就得跳转到上层函数。

(*serveCtx)registerGateway

在函数中,我们可以看到它注册了一个类型为registerHandlerFunc的handlers列表,包括如下内容:

代码语言:javascript
复制
handlers := []registerHandlerFunc{
  etcdservergw.RegisterKVHandler, // KV键值对的处理
  etcdservergw.RegisterWatchHandler, // Watch监听
  etcdservergw.RegisterLeaseHandler, // Lease租约
  etcdservergw.RegisterClusterHandler, // 集群
  etcdservergw.RegisterMaintenanceHandler, // 维护相关
  etcdservergw.RegisterAuthHandler, // 认证
  v3lockgw.RegisterLockHandler, // 锁
  v3electiongw.RegisterElectionHandler, // 选举
}
for _, h := range handlers {
  if err := h(ctx, gwmux, conn); err != nil {
    return nil, err
  }
}

我们聚焦到PUT请求的处理,它自然走的是etcdservergw.RegisterKVHandler这个入口。

RegisterKVHandler

本函数位于etcd/etcdserver/etcdserverpb/gw/rpc.pb.gw.go。它其实是用protobuf自动生成的,其中用到了grpc-gateway这个关键性技术,它的作用是将HTTP1的请求转换成gRPC,实现一个server可以同时支持HTTP1与gRPC,并且只写一份gRPC处理的代码即可。

有兴趣地可以去看看 https://github.com/grpc-ecosystem/grpc-gateway 项目。 大致调用链路为: HTTP1 -> gRPC -> 自己实现的handler

RegisterKVHandlerClient

该函数是由proto文件生成的,这里我忽略了关于context的处理,提取关键性的内容:

代码语言:javascript
复制
mux.Handle("POST", pattern_KV_Put_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
  // 反序列化请求和序列化响应
  inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
  // 执行PUT请求
  resp, md, err := request_KV_Put_0(rctx, inboundMarshaler, client, req, pathParams)
  // 返回结果
  forward_KV_Put_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})

序列化与反序列化存在多种选择,我们暂不深入,先来看看处理这部分的工作:

首先是如何匹配请求,也就是http://localhost:2379/v3/kv/put,对应如下:

代码语言:javascript
复制
var pattern_KV_Put_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v3", "kv", "put"}, ""))

而最核心的处理,也就是解析PUT请求的函数request_KV_Put_0与返回处理结果的函数forward_KV_Put_0,我们放到下一讲再来看。

小结

今天我们看了PUT请求在etcd server中通过mux的匹配逻辑,思路参考下图。

在阅读代码期间,我们接触到了grpc-gateway这个技术方案,有兴趣的朋友可以参考我的另一篇文章。

Github: https://github.com/Junedayday/code_reading Blog: http://junes.tech/ Bilibili: https://space.bilibili.com/293775192

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-07-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Go编程点滴 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • PUT键值对的HTTP请求
  • Mux的路由匹配
  • 背景知识介绍
    • http mux的创建
      • (*serveCtx)createMux
        • (*serveCtx)registerGateway
          • RegisterKVHandler
            • RegisterKVHandlerClient
            • 小结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档