架构原理
1 - 概念
1.1 - 命名空间
命名空间
命名空间提供了一种在相同注册中心下资源的逻辑隔离的机制,同一命名空间下的资源命名必须唯一,但是跨命名空间允许存在同名的资源。
命名空间常用于多个不同环境的资源的隔离,比如开发测试环境和生产环境之间的资源隔离。
PolarisMesh默认存在2个命名空间:
- Polaris:Polaris是系统命名空间,存放的是PolarisMesh的系统服务,PolarisMesh自身的集群发现及管理需要依赖Polaris命名空间下的服务。
- default:默认命名空间,用户如果没有多命名空间的需求,可以直接使用default命名空间。
1.2 - 服务
服务
服务是一种资源对外暴露的抽象方式,资源本身通过端口监听的方式提供网络访问,并通过提供一系列预定义的服务接口,给主调端进行调用。
服务通过服务名进行标识,每个服务都属于某一个命名空间,同一命名空间下的服务名需唯一。
服务别名
服务别名是针对某个服务的别名,一个服务可以有多个服务别名,通过服务别名可访问到所指向的服务下所有的所有实例和分组。
服务实例
服务实例对应的是暴露一个或多个API接口供主调应用进行网络调用的节点,通过IP:PORT的方式进行唯一标识。
- 在虚拟机上,一个服务实例对应的是一个进行端口监听的进程。
- 在容器环境中,一个服务实例对应的是一个POD。
服务实例分组
实例分组是由一个或多个具有相同标签属性的实例组成,这些实例往往具备相同的特征,比如属于同一版本、属于同一个地域、属于同一环境。
对实例进行分组,可以方便后续按照分组进行服务治理,比如灰度发布时,按不同版本进行进行流量调度、在查看监控时,可以看到灰度分组的流量情况,方便验证版本灰度是否完成。
实例分组属于服务,具备一个或多个标签过滤条件组成,符合过滤条件的实例属于该分组。
属性名 | 类型 | 说明 |
---|---|---|
name | string | 分组名 |
service | string | 分组所属的服务名 |
namespace | string | 分组所属的命名空间 |
labels | map<string, MatchString> | 标签匹配条件,支持多种匹配模式(全匹配、部分匹配、正则匹配等) |
1.3 - 配置
配置分组
配置分组是配置文件集的所属分组,是配置组织的维度之一,每个配置文件都属于某个一个命名空间以及配置分组,相同命名空间和配置分组下的配置文件具有唯一的名称。
推荐使用一个有意义的字符串来分组配置集,用于标识该分组下的配置集合,比如和微服务名称对应–UserConfig,代表用户服务下的配置集合。
配置文件
配置文件指的是业务具体的配置,配置文件名可采用树形结构的层级进行标识,采用/符号来区分层次,比如cluster1/dev/config。
不同层级可用于体现服务的配置在不同环境、集群、set的差异。例如:第一级按环境分为DEV(开发)、TEST(测试环境)、PROD(生产环境),PROD下第二级按城市分为SH、SZ。
2 - 部署架构
2.1 - 单机部署
架构模型
北极星支持单机版的部署架构,适用于用户在开发测试阶段,通过本机快速拉起北极星服务进行验证。
组件列表
北极星单机版安装包包含北极星服务端的所有功能组件,组件列表如下:
- polaris: 控制面服务端
- polaris-console: 可视化控制台
- polaris-limiter: 分布式限流服务端,提供全局配额统计的功能
- prometheus: 监控数据存储组件
如何部署
可参考单机版部署指南
2.2 - 集群部署
架构模型
北极星支持高可用的集群部署架构模型,支持多级的容灾部署架构,适用于用户在生产环境上使用北极星。
基础功能
基础功能架构提供注册发现、健康检查、动态路由、熔断降级、配置管理等功能。
北极星控制面无状态化,通过DB以及Redis存储资源信息以及相关状态数据。
可选功能
可观测性
北极星提供可观测性功能,支持服务监控大盘,服务治理的监控曲线展示。
需要额外部署prometheus服务,所有的监控数据(包括控制面以及应用服务)都上报并汇总到prometheus,控制台从prometheus拉取监控数据进行展示。
分布式限流
北极星支持分布式限流能力,可在基础功能架构或者可观测性架构基础上,额外部署分布式限流服务(polaris-limiter),用于全局限流配额的统计和下发。
如何部署
可参考集群版部署指南
3 - 服务管理
3.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完成服务注册的操作。
3.2 - 服务发现
定义
服务发现指的主调方是根据服务名标识,拉取服务实例列表,以供后续进行服务调用的操作。
服务发现的方式
北极星支持以下4种方式进行服务发现:
通过SDK进行服务发现
北极星提供了多语言SDK,SDK通过ConsumerAPI提供3个接口进行服务发现:
- getAllInstances:获取服务下全量的服务实例列表,不做任何过滤。
- getHealthyInstances:获取服务下健康的服务实例列表,只包含健康实例,不包含被熔断、不健康、隔离、权重为0的实例。
- getOneInstances:针对健康的服务实例列表,进行动态路由和负载均衡,返回单个可用的服务实例。
使用参考:SDK服务发现使用指南
通过服务框架进行服务发现
服务框架会提供通用的服务发现接口,应用在RPC之前,会自动进行服务发现,获取到可用的实例进行RPC调用。
北极星对主流的服务框架(SpringCloud,Dubbo,gRPC)做了适配,用户无需修改业务逻辑代码,只需引入北极星的框架扩展库,即可实现自动发现。
使用参考:框架服务发现使用指南
使用DNS进行服务发现
北极星通过polaris-sidecar提供DNS功能,用户程序可以通过DNS域名访问的方式,实现无侵入的服务发现。
使用参考:DNS服务发现使用指南
使用OpenAPI服务发现
北极星控制面提供基于Rest标准的OpenAPI,用户可通过OpenAPI完成服务发现的操作。
使用参考:OpenAPI服务发现指南
3.3 - 健康检查
定义
健康检查提供了一种机制,使得控制面可以在一定时间段内,感知服务实例出现异常,从而将异常节点剔除,并通知给所有的消费者。健康检查有以下2种实现形式:
心跳上报
服务实例持续上报心跳给控制面,并与控制面约定TTL的时间段,控制面检查服务实例的心跳上报时间点,当发现当前时间相比实例最后一次上报时间已经超过3*TTL
,就将实例标记为不健康,并通知给该服务的消费者。
主动探测
服务实例与控制面约定探测的方式(探测协议、接口、超时等)。控制面会定时探测服务实例的接口,当发现探测失败次数超过n
,就将实例标记为不健康,并通知给该服务的消费者。
4 - 流量管理
4.1 - 动态路由
动态路由功能定义
通常一个服务包含多个实例。在简单场景下,每个实例是对等的,通过负载均衡组件访问任意一个即可。
但是,在绝大部分场景下,每个实例具有逻辑属性和物理属性:
- 逻辑属性:版本、协议、业务Set、特性环境等。北极星允许用户为每个实例设置自定义标签
- 物理属性:地理位置。比如实例所属的地域-城市-园区的位置信息。
对于某个服务的全部实例,可以根据逻辑和物理属性将其划分成为多个分组或者集群,如下图所示:
服务主调方/消费者发送请求,动态路由组件根据请求和主调方节点属性,将不同节点的不同请求路由到不同实例分组或者集群。
串联式路由插件
北极星动态路由组件采用插件化、可配置的方式实现。北极星默认内置以下路由插件:
- 前置路由插件:默认在插件链的最前面执行,用于剔除隔离和权重为0的实例。
- 规则路由插件:按照控制台配置的路由规则,根据用户请求参数执行路由规则进行服务实例的过滤。
- 元数据路由插件:直接根据传入的实例元数据对服务实例进行过滤。
- 就近路由插件:根据应用自身所属的地域信息,与实例的地域信息进行匹配,筛选出就近的服务实例。
- 后置路由插件:剔除健康状态异常和故障熔断的实例。如果被剔除的节点数超过一定比例,返回前一个插件的筛选结果,防止因网络分区原因导致的误剔除。
当调用GetOneInstance接口时,会调用路由插件,执行动态路由进行实例筛选,执行流程如下图所示:
插件链中路由插件的执行顺序可以由用户自行编排,同时用户也可以定制自己的路由插件。
4.2 - 负载均衡
定义
从满足本次转发要求的服务实例集中, 通过一定的均衡策略,选取一个实例返回给主调方,供主调方进行服务请求发送。
分类
负载均衡策略一般分为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算法。
4.3 - 访问限流
什么是访问限流
限流能力是高并发系统中,对于服务提供方的一种保护手段。通过限流功能,我们可以通过控制QPS的方式,以避免被瞬时的流量高峰冲垮,从而保障系统的高可用性。
访问限流主要有如下两个应用场景:
- 过载保护:保护业务不被突发流量打垮
- 业务防刷:防止恶意用户发送过多流量影响其他正常用户
北极星为被调端服务提供2种类型的访问限流能力:
单机限流:针对单个被调实例的级别的限流,流量限额只针对当前被调实例生效,不共享。
分布式限流:针对服务下所有实例级别的限流,多个服务实例共享同一个全局流量限额。
两种限流模式如何选择?
- 单机限流:一般适用于保护服务自身不被打垮,按照每个服务集群单机的容量来计算配额。
- 分布式限流:一般适用于保护第三方服务或者公共服务(比如保护数据库);或者是在网关层进行限流,对通过网关接入的后端服务进行保护。
4.4 - 访问鉴权
定义
访问鉴权包含以下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实现原理
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。
5 - 熔断降级
5.1 - 故障熔断
定义
故障熔断,指的是当下游因过载或者BUG等原因,出现请求错误后,为了防止故障级联扩散导致整个链路出现异常,从而对请求进行拒绝或者重试的一种机制。
熔断模型
熔断模型的设计遵循业界标准的熔断器模型设计。熔断器有3类状态:
- 关闭:所有请求皆可访问下游资源,无任何限制。
- 打开:限制访问下游资源的请求,不允许任何请求的访问。
- 半开:限制访问下游资源的请求,只允许部分请求达到下游。
熔断场景
熔断一般会发生在以下场景下:
硬件环境出现故障
服务在运营过程中,因为一些不可抗力的因素,可能会出现机器故障、机器重启、机房断电、网络中断等问题。通过熔断机制,对服务实例或者机房分组的快速熔断,可以避免业务请求持续失败。
版本上线引入BUG
版本新特性开发上线后,因为漏测等原因,某些分支触发了BUG,导致部分的业务逻辑出现故障。常见的是部分方法在遇到某些入参的时候,会出现进程报错或者高负载的问题,影响其他方法的请求处理。通过熔断机制,将故障方法进行隔离,可以避免其他业务请求受到影响。
服务出现过载
因为路由不均或者峰值流量的到来,导致被调服务出现了高负载,导致请求的时延增大,成功率降低。通过熔断机制,合理的拒绝一部分请求,可以降低服务负载,恢复正常的运行状态。
熔断级别
服务级熔断
按照服务粒度进行熔断统计,服务一般对应的是注册中心上注册的服务,实际对应到业务可以是应用或者模块。一旦服务被熔断后,访问该服务的请求都会返回失败走降级逻辑
方法级熔断
按照服务下提供的方法进行熔断统计,方法被熔断后会对请求进行拒绝或者走降级逻辑。
分组级熔断
按照服务实例分组进行熔断统计,实例分组被熔断后,请求往往会重路由到其他实例分组。
实例级熔断
按照具体的服务实例进行熔断统计,实例被熔断后,请求往往会重路由到其他实例。
组合粒度
按照组合的粒度进行熔断统计,常见的组合是方法+实例的组合,对应的场景是,一个实例提供了多个方法,其中一个方法在新版本上线时候出现了BUG,需要对该实例下对应的方法进行熔断,其他方法没有改动,可以正常提供服务。
触发熔断条件
连续错误数熔断
请求调用时,统计周期内,出现连续异常数目超过阈值之后,资源进入熔断状态。
错误率熔断
熔断器按照滑窗对请求总数及成功数进行统计,并汇总时间段内的总错误率,一旦超过阈值,资源进入熔断状态。
错误数熔断
熔断器按照滑窗对请求总数及成功数进行统计,并汇总时间段内的总错误数,一旦超过阈值,资源进入熔断状态。
慢调用率熔断
熔断器按照滑窗对请求总数及慢调用数进行统计,并汇总时间段内的总慢请求率,一旦超过阈值,资源进入熔断状态。
熔断策略
快速失败熔断
定义
一旦触发熔断,则接下来的熔断周期内对资源的访问会自动地被拒绝或重路由。
适用于硬件环境出现故障,以及版本上线出现BUG的快速熔断场景。
代表的算法有Hystrix。
算法实现
每个请求进入后,会判断熔断器状态,存在3种分支:
- 熔断器打开后,请求直接返回,走降级逻辑。
- 熔断器半开,则只需要一定量的请求访问下游,其他请求返回。
- 熔断器打开,所有请求都可以访问下游。
在请求调用后,通过框架采集请求的调用结果,汇聚熔断器。
熔断器会通过滑窗的方式,统计每个请求的成功数及总请求数,根据成功失败率进行状态转换:
- 熔断器从关闭到打开:一旦请求达到阈值,则熔断器打开。
- 熔断器从打开到半开:熔断器打开经过一段时间,或者通过了故障探测,则进入半开状态。
- 熔断器从半开到打开:熔断器半开后,走递增放量的方式,放量期间出现了失败,则熔断器重新打开。
- 熔断器从半开到关闭:熔断器半开后,走递增放量的方式,放量期间没有失败,最终放量100%后,熔断器关闭。
自适应熔断
定义
出现错误时,会按照错误率的比例,渐进式的丢弃一部分的请求,直到丢弃的请求数无限接近100%(完全熔断)。
适用于过载保护的场景,可以将流量调整到能正常处理业务请求的范围。
代表的算法有谷歌SRE。
算法实现
熔断器会通过滑窗的方式 ,统计请求的总数,以及成功数。
每个请求进入后,熔断器会通过以下公式进行计算,本次请求是否允许通过:
当结果为0,则全放通。否则,则按照计算结果百分比,通过区间算法,对请求进行限制(比如k=2,则错误率超过50%时,触发熔断),最终会无限趋向于1,熔断器进入打开状态。
熔断器打开后,随着少量请求的进入,通过统计请求的成功率,逐步放开放量阈值,直到熔断器关闭,请求全放通。
6 - 熔断降级
6.1 - 配置推送
配置中心架构设计
流程设计
客户端视角
应用启动时,同步从服务端拉取一次配置,获取最新的配置内容。
把第一步拉取到的所有的配置文件生成 List
当收到配置文件的推送消息时,向服务端拉取最新的配置文件。
配置服务端视角
先检查客户端 List
如果客户端配置文件版本号都是最新的,则在内存里维护 File -> List
发布推送配置简化流程
用户在界面点击发布按钮,服务端更新数据库里配置发布表的数据。配置发布表的核心字段:file, version, content, mtime
每个北极星服务端实例,都会定时1s扫描配置发布表,根据 mtime 捞出最近 1s 内变更过的数据
北极星服务端实例扫描到最新变更的数据之后
- 重新加载内存缓存
- 向内存里的消息发布管道里写入一条消息
推送协程从消息发布管道里获取到消息,并消费消息。通过 File -> List