1 - 服务管理
服务注册
服务注册指的是被调方按照服务模型将自身的服务数据注册到北极星,以供主调方进行服务发现。
服务数据主要包括以下部分:
- 服务名:服务的唯一标识,区分大小写。
- 服务元数据:服务的标签信息,KV格式,可对服务进行分类,可用于过滤。
- 服务实例:提供服务的节点列表,以IP:PORT的方式提供。
- 服务实例元数据:服务实例的标签信息,KV格式,通常用于描述节点的集群、版本等,用于后续流量治理等操作。
服务注册的方式
北极星支持以下4种服务注册方式:
通过SDK注册
北极星提供了多语言SDK,服务可以通过集成SDK,调用registerInstance接口完成服务注册。
通过服务框架注册
服务框架会提供通用的服务注册接口,供应用在拉起的时候,自动往注册中心注册。
北极星对主流的服务框架(SpringCloud,Dubbo,gRPC)做了适配,用户无需修改业务逻辑代码,只需引入北极星的框架扩展库,即可实现自动注册。
通过k8s同步的方式注册
用户通过k8s部署服务,并注册为k8s的service,北极星通过controller的机制,从k8s中将service和endpoint信息同步到北极星,完成服务注册。
通过OpenAPI注册
北极星控制面提供基于Rest标准的OpenAPI,用户可通过OpenAPI完成服务注册的操作。
服务发现
服务发现指的主调方是根据服务名标识,拉取服务实例列表,以供后续进行服务调用的操作。
服务发现的方式
北极星支持以下4种方式进行服务发现:
通过SDK进行服务发现
北极星提供了多语言SDK,SDK通过ConsumerAPI提供3个接口进行服务发现:
- getAllInstances:获取服务下全量的服务实例列表,不做任何过滤。
- getHealthyInstances:获取服务下健康的服务实例列表,只包含健康实例,不包含被熔断、不健康、隔离、权重为0的实例。
- getOneInstances:针对健康的服务实例列表,进行动态路由和负载均衡,返回单个可用的服务实例。
通过服务框架进行服务发现
服务框架会提供通用的服务发现接口,应用在RPC之前,会自动进行服务发现,获取到可用的实例进行RPC调用。
北极星对主流的服务框架(SpringCloud,Dubbo,gRPC)做了适配,用户无需修改业务逻辑代码,只需引入北极星的框架扩展库,即可实现自动发现。
使用DNS进行服务发现
北极星通过polaris-sidecar提供DNS功能,用户程序可以通过DNS域名访问的方式,实现无侵入的服务发现。
使用OpenAPI服务发现
北极星控制面提供基于Rest标准的OpenAPI,用户可通过OpenAPI完成服务发现的操作。
健康检查
健康检查提供了一种机制,使得控制面可以在一定时间段内,感知服务实例出现异常,从而将异常节点剔除,并通知给所有的消费者。健康检查支持以下实现形式:
心跳上报
服务实例持续上报心跳给控制面,并与控制面约定TTL的时间段,控制面检查服务实例的心跳上报时间点,当发现当前时间相比实例最后一次上报时间已经超过3*TTL
,就将实例标记为不健康,并通知给该服务的消费者。
2 - 流量管理
动态路由
通常一个服务包含多个实例。在简单场景下,每个实例是对等的,通过负载均衡组件访问任意一个即可。
但是,在绝大部分场景下,每个实例具有逻辑属性和物理属性:
- 逻辑属性:版本、协议、业务Set、特性环境等。北极星允许用户为每个实例设置自定义标签
- 物理属性:地理位置。比如实例所属的地域-城市-园区的位置信息。
对于某个服务的全部实例,可以根据逻辑和物理属性将其划分成为多个分组或者集群,如下图所示:
服务主调方/消费者发送请求,客户端根据请求和主调方节点属性,将不同节点的不同请求路由到不同实例分组或者集群。
串联式路由插件
北极星动态路由组件采用插件化、可配置的方式实现。北极星默认内置以下路由插件:
- 前置路由插件:默认在插件链的最前面执行,用于剔除隔离和权重为0的实例。
- 规则路由插件:按照控制台配置的路由规则,根据用户请求参数执行路由规则进行服务实例的过滤。
- 元数据路由插件:直接根据传入的实例元数据对服务实例进行过滤。
- 就近路由插件:根据应用自身所属的地域信息,与实例的地域信息进行匹配,筛选出就近的服务实例。
- 后置路由插件:剔除健康状态异常和故障熔断的实例。如果被剔除的节点数超过一定比例,返回前一个插件的筛选结果,防止因网络分区原因导致的误剔除。
当调用GetOneInstance接口时,会调用路由插件,执行动态路由进行实例筛选,执行流程如下图所示:
插件链中路由插件的执行顺序可以由用户自行编排,同时用户也可以定制自己的路由插件。
负载均衡
从满足本次转发要求的服务实例集中, 通过一定的均衡策略,选取一个实例返回给主调方,供主调方进行服务请求发送。
分类
负载均衡策略一般分为2类:
无状态负载均衡
无状态负载均衡策略,主要特点是每次负载均衡获取到的结果是由具体的负载均衡算法决定。目的是让负载均匀的分发到后端节点。
主要负载均衡策略包括:权重随机,权重轮询等
对于无状态的业务逻辑,为了保证后端节点能够均衡分配请求,此时应该选择权重随机负载均衡策略
有状态负载均衡
有状态负载均衡策略,除了要达到让负载均衡分散到节点的目标以外,还需要实现将同一对象的请求分发到同一个节点。例如业务场景需要将同一个用户的全部请求发送到后端同一个节点处理的情况。
主要负载均衡策略是一致性hash
算法
权重随机
权重随机负载均衡策略,利用区间算法,基于伪随机因子取模的方式选择对应服务实例。
对于有状态的业务逻辑(比如通过用户ID或者请求ID进行hash分区的),为了保证同key请求能够持续命中同一个物理节点,此时应该选择一致性hash负载均衡策略。
权重一致性hash(ringhash算法)
该负载均衡策略基于ketama环算法,每一个服务实例会按照权重分裂成若干个虚拟节点。虚拟节点通过取hash值的方式,映射到长度为2^32的hash环中。
发起查询时,北极星会基于用户传入的hashKey,计算出具体的hash值,然后到环中寻找hash值刚刚好大于传入数据hash值的虚拟节点,并返回其对应的服务实例
权重一致性hash(maglev算法)
该负载均衡策略基于maglev算法(https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/44824.pdf)进行节点分配。
首先为实例集创建一个长度为质数65537的向量表,算法根据节点的权重,将向量表进行填充,直到向量表全部被节点所占满。取节点时则根据用户传值的hashValue取摸的方式,返回对应下标的节点。
算法优点:由于是直接取模寻址,因此算法性能相比ketama环要高(官方数据是在256K个插槽的情况下,性能是5X到10X的差距)。
算法缺点:当节点下线后,导致迁移的节点数量相比ketama环要多(官方数据为迁移节点数量是ketama的两倍)
权重设计
北极星的每个服务实例,都可以设置权重,请求会根据权重,按比例进行分配。权重分为静态权重和动态权重两类:
静态权重
用户可以通过界面配置或者实例注册的方式,调整服务实例的权重,由控制面推送给所有的数据面生效。
动态权重
数据面在运行过程中,定时上报负载数据给控制面,目前支持capacity&used两个指标, 被调方定时(一般2s)上报两个指标的数值到控制面,控制面根据负载信息,调整权重数据。主调方定时拉取最新的权重数据,实时更新以前的静态权重字段,复用老的weightrandom算法。
访问限流
限流能力是高并发系统中,对于服务提供方的一种保护手段。通过限流功能,我们可以通过控制QPS的方式,以避免被瞬时的流量高峰冲垮,从而保障系统的高可用性。
访问限流主要有如下两个应用场景:
- 过载保护:保护业务不被突发流量打垮
- 业务防刷:防止恶意用户发送过多流量影响其他正常用户
北极星为被调端服务提供2种类型的访问限流能力:
单机限流:针对单个被调实例的级别的限流,流量限额只针对当前被调实例生效,不共享。
分布式限流:针对服务下所有实例级别的限流,多个服务实例共享同一个全局流量限额。
两种限流模式如何选择?
- 单机限流:一般适用于保护服务自身不被打垮,按照每个服务集群单机的容量来计算配额。
- 分布式限流:一般适用于保护第三方服务或者公共服务(比如保护数据库);或者是在网关层进行限流,对通过网关接入的后端服务进行保护。
访问鉴权
访问鉴权包含以下3部分功能:
- 认证:检验服务调用双方的身份真实性
- 加密:对服务调用通讯数据进行加密
- 鉴权:校验请求是否有访问服务接口的权限
访问鉴权可以有效地保护服务调用过程,防止中间人攻击、敏感数据泄露、数据越权访问等问题。
整体架构
Polaris支持使用mTLS来对服务调用进行认证与加密,整体架构如下所示:
- polaris-security是Polaris的安全组件,在mTLS场景下,它支持作为中间证书签发机构(Intermediate Certificate Authority)使用。
- Polaris Controller监测到用户服务启用mTLS功能后,会向Pod内自动注入所有启用mTLS功能需要的环境。
- Polaris xDS Server会给启用mTLS的服务对应的Envoy sidecar下发相应的额外配置。
- Polaris Sidecar中会额外启动mTLS agent组件,通过Unix Domain Socket通讯向Envoy提供SDS(Secret Discovery Service)能力。
- mTLS agent组件会自动生成服务使用的身份证书及私钥,并自动进行轮转(rotate)。
- 每当身份证书接近过期时,mTLS会向polaris-security发送CSR(证书签名请求)来进行更新。
证书签名及轮转原理
启动阶段
- 用户启用mTLS功能之前,需要先在k8s集群中部署两个secret
- polaris-security会读取polaris-security-secret,并为自身签发service certificate,用于在TLS握手中自证身份。
- mTLS agent会读取polaris-sidecar-secret,用于TLS握手中验证polaris-security的身份。
证书签名
- mTLS agent向polaris-security发起TLS保护的certificate signing request,polaris-security会提供自身的service certiface给mTLS agent验证。
- 成功后,polaris-security从请求的header中提取出mTLS agent的Service Account Token,并使用Token Review请求的方式发送给Kubernetes API Server进行验证。
- 验证成功后,正式进入证书签名流程,polaris-security会按CSR中的参数要求,使用CA私钥加密摘要,并将所有材料整合成已签名的证书,连同证书链一起返回给mTLS Agent。
- mTLS Agent可以使用返回的材料来提供SDS服务,已签名的证书可以用作证明workload的身份;证书链可以用作验证对端workload的身份。
证书轮转
- mTLS agent的Rotater类会负责证书轮转,它其实就是一个定时任务执行器,每隔一定时间间隔就会执行一次CSR发送任务,并根据返回结果更新SDS材料,请求失败则会自动重试。
- 轮转时间间隔默认值为30分钟,证书TTL默认值为1小时,自动重试间隔默认为1秒。
- polaris-security是一个无状态的组件,可以进行适当的水平扩展来保证高可用。
mTLS实现原理
Polaris提供三种不同的服务粒度模式供用户选择:
模式 | 解释 |
---|---|
Permissive | 宽容模式,服务接受纯文本/mTLS服务调用;发起服务调用时,根据对端接受状况自动选择发起mTLS或纯文本服务调用 |
Strict | 严格模式,服务仅接受/发起mTLS服务调用 |
None | 无加密模式(为默认选项),服务仅接受/发起纯文本服务调用 |
sidecar注入
- mTLS的开关是用户服务的
metadata
中的polarismesh.cn/tls-mode
键对应的label
。 - 服务注册回调时,Polaris Controller的injector发现上述label的值为strict或permissve时,就会渲染出额外的注入配置:挂载secret及uds路径、开启iptables入流量拦截、为polaris-bootstrap-writer设置特殊的环境变量等。
- polaris-bootstrap-writer检测到mTLS相关的环境变量被设置后,会使用一份mTLS专用的Envoy配置模版来进行渲染,这份模版中设置了sds的相关配置与特殊的node metadata,polaris控制面就是使用这个特殊的metadata来区分服务网格中的各个服务是否启用mTLS功能。
xDS
- polaris控制面在给各个Envoy sidecar下发配置时,会根据node metadata中的mTLS相关信息来决定下发哪种配置。
- 对于permissve模式,下发的listener会加入一个TLS Inspector filter配置,TLS Inspector能根据请求的头几字节自动判断这是纯文本请求或是mTLS请求,然后进行不同处理;下发的cluster会加入一个额外的Match条件,在对端endpoint拥有
acceptMTLS
metadata时,会使用tls transport socket,否则就使用默认的raw buffer transport socket。 - 对于strict模式,下发的listener仅接受mTLS服务调用;而下发的cluster仅会使用tls transport socket来连接对端endpoint。
3 - 熔断降级
熔断降级
故障熔断,指的是当下游因过载或者BUG等原因,出现请求错误后,为了防止故障级联扩散导致整个链路出现异常,从而对请求进行拒绝或者重试的一种机制。
熔断模型
熔断模型的设计遵循业界标准的熔断器模型设计。熔断器有3类状态:
- 关闭:所有请求皆可访问下游资源,无任何限制。
- 打开:限制访问下游资源的请求,不允许任何请求的访问。
- 半开:限制访问下游资源的请求,只允许部分请求达到下游。
熔断场景
熔断一般会发生在以下场景下:
硬件环境出现故障
服务在运营过程中,因为一些不可抗力的因素,可能会出现机器故障、机器重启、机房断电、网络中断等问题。通过熔断机制,对服务实例或者机房分组的快速熔断,可以避免业务请求持续失败。
版本上线引入BUG
版本新特性开发上线后,因为漏测等原因,某些分支触发了BUG,导致部分的业务逻辑出现故障。常见的是部分方法在遇到某些入参的时候,会出现进程报错或者高负载的问题,影响其他方法的请求处理。通过熔断机制,将故障方法进行屏蔽,可以避免其他业务请求受到影响。
服务出现过载
因为路由不均或者峰值流量的到来,导致被调服务出现了高负载,导致请求的时延增大,成功率降低。通过熔断机制,合理的拒绝一部分请求,可以降低服务负载,恢复正常的运行状态。
熔断级别
接口级熔断
应用与服务之间的调用都是针对接口进行调用,为避免调用故障接口导致业务整体时延较大,加剧后端的压力。用户可以设置熔断规则,按照整个服务或者服务下某个接口的粒度设置熔断阈值,并统计在调用过程中的错误率时延等数据,达到阈值后会进行熔断(熔断器打开)。熔断后,访问该服务或特定接口的请求都会返回失败或者走降级逻辑。
接口级熔断生效在接口调用前,主调服务访问接口前需要判断接口的熔断状态。
实例级熔断
一般用于远程服务调用(RPC)的场景,针对某个节点或者分组(具备相同标签的节点集)设置熔断阈值,实例级熔断往往按照具体的服务实例进行熔断统计,并统计在调用过程中的错误率时延等数据,达到阈值后会进行熔断。熔断后,该实例会被屏蔽,不会有请求路由进来,直到恢复。
接口级熔断生效在接口调用中,在负载均衡过程中完成对熔断状态实例的剔除。
触发熔断条件
连续错误数熔断
请求调用时,统计周期内,出现连续错误数目超过阈值之后,资源进入熔断状态。
错误率熔断
熔断器按照滑窗对请求总数及成功数进行统计,并汇总时间段内的总错误率,一旦超过阈值,资源进入熔断状态。
错误判断条件
系统需要通过错误请求的统计来判断是否需要触发熔断,请求的错误一般会表现出以下2个方面的特性:
返回的状态码
对于标准协议的请求,比如HTTP Response,常见的5XX等状态码,代表着后端出现异常(比如数据库异常)导致业务请求失败。
时延
对于交易系统等对时延比较敏感的系统,当出现后端数据库等负载过高的情况,导致部分请求可以正常处理,但是时延普遍过高,此时仍可认为这部分请求是失败请求,触发熔断处理。
熔断恢复
当资源的错误请求统计达到一定阈值后,资源会进入熔断状态,在接下来的一段时间内,该资源将会被屏蔽(不会有请求路由到该资源),渡过屏蔽期后,资源会进入半开状态,此时系统会放少部分业务请求给该资源,并记录请求的处理结果。假如请求全部处理成功,则资源恢复成功(熔断器关闭),取消屏蔽并正常处理业务请求。
但是,假如业务请求扔存在处理失败,则该资源会重新进入熔断状态,继续保持隔离。
如何使用
4 - 配置管理
配置中心
流程设计
客户端视角
应用启动时,同步从服务端拉取一次配置,获取最新的配置内容。
把第一步拉取到的所有的配置文件生成 List
当收到配置文件的推送消息时,向服务端拉取最新的配置文件。
配置服务端视角
先检查客户端 List
如果客户端配置文件版本号都是最新的,则在内存里维护 File -> List
发布推送配置简化流程
用户在界面点击发布按钮,服务端更新数据库里配置发布表的数据。配置发布表的核心字段:file, version, content, mtime
每个北极星服务端实例,都会定时1s扫描配置发布表,根据 mtime 捞出最近 1s 内变更过的数据
北极星服务端实例扫描到最新变更的数据之后
- 重新加载内存缓存
- 向内存里的消息发布管道里写入一条消息
推送协程从消息发布管道里获取到消息,并消费消息。通过 File -> List