1 前言
1.1 为什么要引入微服务网关
通常微服务网关所处的位置如下图所示处于 Nginx网关和微服务集群之间,起着承上启下的关键作用. 第一个重要作用是保障微服务集群的高扩展性. 微服务网关和微服务集群使用同一个服务注册中心,微服务网关可以感知到微服务集群中服务所对应的节点数量的变化,换句话说微服务集群在扩容或者缩容时对业务是无影响.第二个重要的作用是支持较高的定制化. 开发人员可以基于网关框架提供的扩展机制或者选取内置的扩展对经过微服务网关的请求进行定制化的处理.来满足更具体的业务需求.
1.2 为什么选择 Spring Cloud Gateway
由于需要对网关进行定制化开发,在微服务网关选型方面放在优先考虑的开发语言,其次是有大规模落地使用的实践. 基于上面的原因选择了 Spring Cloud Gateway.
1.3 Spring Cloud Gateway 的核心组成
一个请求从 Gateway Client 发出到发送给后端的被代理的服务依次经过了 Mapping 组件 Handler 组件以及众多内置可选过滤器还有自定义实现的过滤器.内核结构清晰轻量,丰富功能通过Filter进行扩展.
1.4 Spring Cloud Gateway 提供的功能
2 整体落地方案
2.1 为什么需要动态路由
路由功能是微服务网关的基础功能,常见的使用路由功能的方式是在配置文件中定义,下面是一个具体的例子.
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
通过配置文件对路由规则进行配置优点是简单清晰,但最大的问题是修改完路由规则后需要重新发布网关服务才能生效. 如果频繁重启微服务网关会对业务造成影响,如果要避免对业务的影响网关服务必须能够动态配置路由.
2.1 Gateway整合Apollo实现动态路由
2.1.1 在 apollo 中配置路由信息
apollo是携程开源的一套成熟的配置中心可以将路由配置信息配置到 apollo 中, 使用 apollo 提供的配置变更事件回调来通知Gateway.Gateway收到路由配置变更的消息后刷新路由规则实现动态路由的功能.apollo 中路由配置的例子如下图, 在创建routesConfig 配置项的时候要注意选择数据类型是 JSON 格式.
2.1.2 定义配置变化监听器
有了配置信息之后需要在Gateway中整合 apollo 客户端实现监听配置变化. apollo客户端提供了ConfigFileChangeListener接口用于让开发人员自定义收到配置变化后的处理逻辑.
这里要特别注意的是 onChange 方法. 当onChange被调用是说明路由配置发生了变更.这时从参数ConfigFileChangeEvent 中可以获取到最新的路由配置信息. 获取到的配置信息是一个 JSON 格式的文本,通过JSON 反序列化工具将配置信息反序列化 成 RouteDefinition 类型. RouteDefinition是Spring Cloud Gateway提供的用于封装路由信息的类. 2.1.3 配置变化时更新路由 得到路由配置定义列表后要做的就是通知 Spring Cloud Gateway 路由信息发生了变化. Spring Cloud Gateway 提供RouteDefinitionWriter对象来更新路由配置. RouteDefinitionWriter在Spring Cloud Gateway 项目启动的时候就通过 bean 的形式注入到了应用上下文中, 因此无需开发者创建具体代码如下
RouteDefinitionWriter提供了 save方法用于保存一个路由配置, delete方法删除一个路由配置. 调用 save 和 delete方法后此时路由并不会生效,还需要通过Spring框架的事件发布器 ApplicationEventPublisher发布一个路由变更事件
这个 RefreshRoutesEvent事件是 Spring Cloud Gateway 中定义的. Spring Cloud Gateway 会监听这个事件的处理实现路由刷新逻辑. 到这里整个动态路由的功能就实现了.
2.2 基于Nacos实现服务无损下线
在实际的生产环境下微服务网关后的微服务集群可能会进行扩容或者缩容. 也就是说注册到 nacos 上的服务实例增加或者减少. 在实例减少是会出现微服务网关把请求路由到正在下线的节点.
网关服务在转发请求的时候会根据从 nacos 中获取的服务与可用实例的映射关系. 当服务节点发生变化的时候 nacos会主动通知
微服务网关某个服务的可用实例发生了变化. 这样微服务网关就不在向不可用的节点发送请求. 但是由于Gateway 负载均衡组件会缓存服务和可用节点的映射配置,. 这就导致即便 Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求 由于缓存的原因,请求依然会被发送给已经下线的节点.
Gateway默认使用的负载均衡组件是loadbalancer. 缓存相关的配置如下
从上面的配置中ttl 所指定就是服务配置的缓存时间, 这里并不建议将 ttl 设置为 0 或者直接将缓存禁用. 那么即便是缩短 ttl 值也仅仅是减少了请求到下线节点的概率并不能从根本上解决问题. 这时就需要借助与 Nacos来解决这个问题.
Nacos 在服务的管理后台提供了下线服务节点的功能,这样可以在服务节点下线之前先在 Nacos 管理后台把节点手动下线. 这种方式虽然可以解决问题,但是依赖手工操作容易出错并且效率较低.Nacos 提供了注销实例的open-api 接口,可以通过发送注销请求
就可以下线一个可用实例.
基于K8S的PreStop Hook机制可以实现在pod停止前执行 curl 命令调用nacos 的注销实例接口. 这样先将服务注销没有请求发送到实例之后才会停止实例.