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

先看一下接口:

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

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

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

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

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

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

  1. func (m *cMux) serve(c net.Conn, donec <-chan struct{}, wg *sync.WaitGroup) {
  2. defer wg.Done()
  3. muc := newMuxConn(c)
  4. for _, sl := range m.sls {
  5. for _, s := range sl.ss {
  6. matched := s(muc.getSniffer())
  7. if matched {
  8. select {
  9. case sl.l.connc <- muc:
  10. case <-donec:
  11. _ = c.Close()
  12. }
  13. return
  14. }
  15. }
  16. }
  17. _ = c.Close()
  18. err := ErrNotMatched{c: c}
  19. if !m.handleErr(err) {
  20. _ = m.root.Close()
  21. }
  22. }

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

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

限制:

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

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

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

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

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

版权声明:本文为songjingsong原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/songjingsong/p/9227563.html