cmux解读

在介绍gRPC proxy之前,我想先介绍一下cmux,其介绍是,在同一个端口,提供不同服务,包括http2,http1。

1.接口定义

先看一下接口:

// CMux is a multiplexer for network connections.
type CMux interface {
    // Match returns a net.Listener that sees (i.e., accepts) only
    // the connections matched by at least one of the matcher.
    //
    // The order used to call Match determines the priority of matchers.
    Match(...Matcher) net.Listener
    // Serve starts multiplexing the listener. Serve blocks and perhaps
    // should be invoked concurrently within a go routine.
    Serve() error
    // HandleError registers an error handler that handles listener errors.
    HandleError(ErrorHandler)
}

看一下其默认实现类的参数:

type cMux struct {
    //注册的服务端口
    root   net.Listener
    //默认的读取缓冲区大小
    bufLen int
    //错误函数
    errh   ErrorHandler
    //用于优雅的退出函数
    donec  chan struct{}
    //注册的复用listener 列表
    sls    []matchersListener
}

可以看出cmux主要提供了三个方法Match、Serve、HandleError。

  • Match 匹配一个matcher,返回一个能够匹配该规则的listenr
  • Serve 启动服务,一般用一个go routine来启动 go cmux.Serve()
  • HandleError 注册错误回调钩子函数。

2. 路由匹配

我们看一下cmux是如何去匹配路由的:

func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
    defer wg.Done()

    muc := newMuxConn(c)
    for _, sl := range m.sls {
        for _, s := range sl.ss {
            matched := s(muc.getSniffer())
            if matched {
                select {
                case sl.l.connc <- muc:
                case <-donec:
                    _ = c.Close()
                }
                return
            }
        }
    }

    _ = c.Close()
    err := ErrNotMatched{c: c}
    if !m.handleErr(err) {
        _ = m.root.Close()
    }
}

可以看出来主要遍历matchersListener列表,通过 getSniffer 生成了一个继承io.Reader的类,然后通过具体的Match方法的根据conn数据流中的特定字段,去匹配路由。
注意:如果有两个matcher路由可以同时匹配一个链接,那么这个链接会优先匹配最先加在的match路由。

3. 性能和限制

因为是通过匹配每个连接的起始的字节,如开启了keepalive的http1.1,grpc等http2服务的长链接的性能开销是可以忽略的。

限制:

  • TLS: 从源码可以看出cmux是基于net.Listener的扩展,所以其TLS是在底层中实现,无法在handlers里面使用。
  • 不支持同一客户端连接上的多协议复用:即,由于从链接的一开始就通过字段确定了复用的连接类型,所以无法复用多协议。

4. http路由匹配算法

其实说到上面,这个框架基本上就已经结束了,但是我还是想扩展一下,之前读过很多golang的路由实现,不知道有没有关注其内部的路由算法实现。

其实在 cmux 这个框架里面,有一个 patricia.go 文件。这文件中实现了一种叫 patricia Tree的数据结构,这个是trie前缀树,可以最大限度地减少无谓的字符串比较,故可以用于词频统计和大量字符串排序,一般都可用在路由的管理上,或者地址的分配。

还有一次,在读到 gorilla/mux 的源码时,看到该源码的路由匹配也是通过遍历 内置的array 来实现的。

扫描二维码关注公众号,回复: 1765847 查看本文章

如果你读过golang的http的源码,就可以看到其默认的匹配规则是通过hash去实现,这个虽然可能速度是最快的,但是有一点,它无法实现的是,路由的模糊匹配规则。

猜你喜欢

转载自www.cnblogs.com/songjingsong/p/9227563.html