MOSN 源码解析 - 路由

优质
小牛编辑
132浏览
2023-12-01

本文基于的内容是 MOSN v0.12.0。

MOSN 的路由能力目前仍然处于不断更新完善的阶段,详细的配置介绍可以参考 MOSN 配置文档中的路由部分。本文将主要从三个方面来介绍 MOSN 的路由功能:

  • 针对路由模块的配置解析
  • 路由模块是如何运行的
  • 动态路由的实现

配置解析逻辑

路由的配置要在 MOSN 中生效,包含两个部分:

  • proxy 的配置中,指定路由的名字router_config_name,说明 proxy 需要引用对应名字的路由。
  • 路由的配置,包含路由的名字以及其他路由配置。

其中路由的配置可以配置在两个地方,一个是通过connection_manager进行配置,这个方式是历史遗留配置,作为兼容性保留的内容,不推荐使用这种方式;另一个是通过routers进行配置。如果两处配置包含同名的路由配置,会以routers中的配置为准。代码如下:

func NewMosn(c *v2.MOSNConfig) *Mosn {
     ... // 省略
     for _, serverConfig := range c.Servers {
         ... // 省略
         for idx, _ := range serverConfig.Listeners {
             lc := configmanager.ParseListenerConfig(&serverConfig.Listeners[idx], inheritListeners)
             // deprecated: keep compatible for route config in listener's connection_manager
             deprecatedRouter, err := configmanager.ParseRouterConfiguration(&lc.FilterChains[0])
             if err != nil {
                 log.StartLogger.Fatalf("[mosn] [NewMosn] compatible router: %v", err)
             }
             if deprecatedRouter.RouterConfigName != "" {
                 m.routerManager.AddOrUpdateRouters(deprecatedRouter)
             }
             ... // 省略
         }
         // Add Router Config
         for _, routerConfig := range serverConfig.Routers {
             if routerConfig.RouterConfigName != "" {
                 m.routerManager.AddOrUpdateRouters(routerConfig)
             }
         }
     }
     ... // 省略
 }

根据代码,可以看到在解析配置时,会先解析 Listener 中的路由配置(已废弃字段,兼容逻辑),随后单独解析 Routers 配置。配置解析完成以后,都是通过AddOrUpdateRouters使配置生效,所以后解析的 Routers 配置如果与 Listener 中的配置同名,则会通过 Update 覆盖掉 Listener 中的配置。由此我们也可以看到不同的 Listener、不同的 Proxy,如果引用了相同名字的路由,则对应的是同一份路由规则。

路由模块运行

当前的路由能力,是与proxy进行绑定的。为了说明路由的逻辑,我们先来看一下在proxy 模式下的 MOSN,收到一个请求后,是如何处理的。

首先在 MOSN 的 Listener 新建一个连接的时候,通过OnNewConnection创建NetworkFilters,而proxy就是其中一个,因此一个连接对应一个proxy。在proxy创建的时候会基于配置获取对应的路由信息routersWrapper,所以在连接创建以后,在此连接上的请求会使用哪个路由配置就已经决定了。

func (al *activeListener) OnNewConnection(ctx context.Context, conn api.Connection) {
    filterManager := conn.FilterManager()
    for _, nfcf := range al.networkFiltersFactories {
        nfcf.CreateFilterChain(ctx, filterManager)
    }
    ... // 省略
}
func NewProxy(ctx context.Context, config *v2.Proxy) Proxy {
    ... // 省略
    if routersWrapper := router.GetRoutersMangerInstance().GetRouterWrapperByName(proxy.config.RouterConfigName); routersWrapper != nil {
        proxy.routersWrapper = routersWrapper
    }
    ... // 省略
}
func NewProxy(ctx context.Context, config *v2.Proxy) Proxy {
    ... // 省略
    if routersWrapper := router.GetRoutersMangerInstance().GetRouterWrapperByName(proxy.config.RouterConfigName); routersWrapper != nil {
        proxy.routersWrapper = routersWrapper
    }
    ... // 省略
}

在连接创建以后,开始处理请求。每次收到一个请求,经过协议解析以后,最终会走到proxydownStreamOnReceive方法。在OnReceive中,经过一系列处理,可能会走到路由匹配阶段matchRoute。MOSN 通过routersWrapper获取到当前运行时的routers,随后执行路由匹配逻辑,完成路由匹配。

func (s *downStream) matchRoute() {
    if s.proxy.routersWrapper == nil || s.proxy.routersWrapper.GetRouters() == nil {
        s.requestInfo.SetResponseFlag(api.NoRouteFound)
        s.sendHijackReply(types.RouterUnavailableCode, headers)
        return
    }
    routers := s.proxy.routersWrapper.GetRouters()
    ... // 省略
}

动态路由

MOSN 在路由模块中,设计了动态更新的接口,可以让 MOSN 在运行时动态更新路由的配置。经过前面的分析,我们可以看到 MOSN 配置解析时,也时通过动态更新的接口让路由配置生效的。除了配置解析以外,MOSN 默认可以通过 xDS 完成路由的动态更新,也可以通过扩展调用动态更新的接口完成动态路由配置更新。

路由动态更新主要提供了三个接口。

func (rm *routersManagerImpl) AddOrUpdateRouters(routerConfig *v2.RouterConfiguration) error {}
func (rm *routersManagerImpl) AddRoute(routerConfigName, domain string, route *v2.Router) error {}
func (rm *routersManagerImpl) RemoveAllRoutes(routerConfigName, domain string) error {}

默认情况下,都是使用AddOrUpdateRouters,使用一个完整的路由配置,对路由进行更新。AddRouteRemoveAllRoutes是在特殊情况下,用于特定的路由配置追加 / 删除路由匹配规则使用的。

动态路由更新还涉及到一个问题就是更新以后需要动态生效。从之前的路由运行逻辑分析中,我们知道 MOSN 在连接建立的时候,就通过GetRouterWrapperByName完成了路由选择,为了让路由配置更新对存量连接也可以生效,AddOrUpdateRouters的逻辑进行了针对性处理。

// 省略了日志部分
func (rm *routersManagerImpl) AddOrUpdateRouters(routerConfig *v2.RouterConfiguration) error {
    if routerConfig == nil {
        return ErrNilRouterConfig
    }
    if v, ok := rm.routersWrapperMap.Load(routerConfig.RouterConfigName); ok {
        rw, ok := v.(*RoutersWrapper)
        if !ok {
            return ErrUnexpected
        }
        routers, err := NewRouters(routerConfig)
        if err != nil {
            return err
        }
        rw.mux.Lock()
        rw.routers = routers
        rw.routersConfig = routerConfig
        rw.mux.Unlock()
    } else {
        routers, _ := NewRouters(routerConfig)
        rm.routersWrapperMap.Store(routerConfig.RouterConfigName, &RoutersWrapper{
            routers:       routers,
            routersConfig: routerConfig,
        })
    }
    // update admin stored config for admin api dump
    store.SetRouter(routerConfig.RouterConfigName, *routerConfig)
}
func (rm *routersManagerImpl) GetRouterWrapperByName(routerConfigName string) types.RouterWrapper {
    if v, ok := rm.routersWrapperMap.Load(routerConfigName); ok {
        rw, ok := v.(*RoutersWrapper)
        if !ok {
            return nil
        }
        return rw
    }
}

当一个路由配置是新增的时候,MOSN 会忽略掉NewRouters的错误,即允许路由配置解析失败,此时的目的是为了让GetRouterWrapperByName能返回一个RoutersWrapper。但是如果是路由配置更新,则不会忽略NewRouters的错误,必须是有效的配置才能完成更新。 在之后的运行过程中,如果存在路由配置更新,更新的是RoutersWrapper中的routers,存量连接中的RoutersWrapper也可以获取到最新的routers,实现路由规则的动态生效。

总结