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