问题
- 服务如何定义
- 服务如何发布和订阅
- 服务如何监控
- 服务如何治理(熔断机制等)
- 故障如何定位
基本组件
-
服务描述:
- RESTful API, 性能差
- XML, java平台, 一般内部使用
-
IDL, interface description language, 用作跨语言平台的服务之间的调用, 修改或者删除PB字段不能前向兼容
- gRPC(google)
- Thrift(facebook)
-
注册中心
- 服务提供者在启动时进行注册
- 服务消费者在启动时进行订阅
- 注册中心返回提供者的地址列表给消费者
- 当提供者发生变化, 注册中心通知消费者
-
服务框架
- 服务通信采用什么协议?
- 数据传输采用什么方式?
- 数据压缩采用什么格式?
-
服务监控
- 指标收集
- 数据处理
- 数据展示
-
服务追踪
- 消费者调用生成一个requestId
- 提供者接到请求后记录requestId, 如果需要调用其他服务, 再生成一个requestId, 两个id一起往下传
-
服务治理
- 单机故障
- 单IDC故障
- 依赖服务不可用
注册中心
-
注册中心需要提供哪些接口
- 服务注册接口
- 服务注销接口
- 心跳汇报接口
- 服务订阅接口
- 服务变更查询接口
- 服务查询接口
- 服务修改接口
- 集群部署zookeeper
- 目录存储, 区分版本号
- 服务健康状态监测
- 服务状态变更通知(zookeeper的watcher机制)
- 白名单机制
RPC
-
客户端和服务端如何建立网络连接
- HTTP
-
Socket
- 链路存活检测
- 断连重试
-
服务端如何处理请求: 使用成熟开源方案, 如Netty、MINA等
- 同步阻塞方式(BIO)
- 同步非阻塞方式(NIO)
- 异步非阻塞方式(AIO)
-
数据传输采用什么协议
- HTTP
- Dubbo
-
数据改如何序列化和反序列化
- 支持数据结构类型的丰富度
- 跨语言支持
- 性能
监控
-
监控内容
- 用户端监控: 业务直接对用户提供的功能的监控
- 接口监控: 业务提供的功能锁依赖的具体RPC接口的监控
- 资源监控: 某个接口依赖的资源的监控
- 基础监控: 对服务器本身的健康状况的监控
-
监控指标
-
请求量
- 实时请求量, QPS
- 统计请求量
- 响应时间
- 错误率
-
监控维度
- 全局维度
- 分机房维度
- 单机维度
- 时间维度
- 核心维度: 核心业务和非核心业务
-
监控系统原理
-
数据采集
- 服务主动上报
- 代理收集
-
数据传输
- UDP传输
- Kafka传输
-
数据处理
- 接口维度聚合
- 机器维度聚合
- 数据展示
追踪微服务调用
-
作用
- 优化系统瓶颈
- 优化链路调用
- 生成网络拓扑
- 透明传输数据
-
原理
- 核心就是调用链: 通过一个全局唯一的ID将请求串起来
- traceId: 用于标识某一次具体的请求ID, 在第一层生成一个全局唯一的id, 随着调用往后传递.
- spanId: 用于标识调用在分布式请求中的位置. 第一层是0, 进入下一层则是0.1, 0.2, 再下一层是0.1.1、0.1.2, 以此类推. 可以清晰查看上下游依赖.
- annotation, 用于业务自定义埋点数据, 可以是业务感兴趣的想上传到后端的数据, 比如一次请求的UID.
-
服务追踪系统
微服务治理
-
本地调用, 变成A远程调用B之后, 可能遇到的问题
-
服务提供者问题
- B有节点宕机
- B有节点慢
- B不稳定
-
网络问题
- A和注册中心断连
- B和注册中心断连
- A和B断连
-
其他问题
- 注册中心宕机
-
节点管理手段
- 注册中心摘除机制: 如果有注册中心网络有问题会造成瘫痪
- 服务消费者摘除机制
-
负载均衡
- 随机算法
- 轮询算法
- 最少活跃调用算法
- 一致性Hash算法: 当有节点故障, 请求会平摊到其他节点上, 实现复杂度高.
-
服务路由
-
制定路由规则的原因
- 业务存在灰度发布的需求
- 多机房就近访问的需求
-
配置方式
- 静态配置: 放在服务消费者本地
- 动态配置: 放在注册中心
-
服务容错
-
FailOver: 失败自动切换.
- 调用失败或超时会切换节点重试. 要求操作幂等, 适用于读请求.
-
FailBack: 失败通知.
-
FailCache: 失败缓存.
-
FailFast: 快速失败.
Dubbo框架里的微服务组件
-
Dubbo框架主要使用XML配置方式实现服务发布与注册、发现与引用
-
服务提供方
- xml格式
1. `dubbo:application`标签, 声明name等提供方应用信息, 用于计算依赖关系
2. `dubbo:registry`标签, 声明使用multicast广播注册中心暴露服务地址
3. `dubbo:protocol`标签, 声明暴露的端口以及协议头(如dubbo)
4. `dubbo:service`标签, 声明需要暴露的服务接口
5. bean标签, 和本地bean一样实现服务
2. 服务发布
- 根据service和protocol标签, 解析出一个dubbo协议头的url, 调用DubboProtocol的export()方法, 把服务暴露在相应端口
3. 服务注册
- 根据registry标签, 解析出一个registry协议头的url, 调用RegistryProtocol的export()方法, 将提供者URL注册到注册中心
2. 服务消费者
1. xml格式
1. `dubbo:application`标签, 声明name等消防费应用名, 用于计算依赖关系, 不要与提供方一致
2. `dubbo:registry`标签, 声明使用multicast广播注册中心暴露发现服务地址
3. `dubbo:reference`标签, 生成远程服务代理, 可以和本地bean一样使用远程接口.
2. 服务引用
- 根据reference标签, 解析出一个dubbo协议头的url, 用DubboProtocol的refer()方法, 得到服务的引用.
3. 服务发现
- 根据registry标签, 解析出一个registry协议头的url, 调用RegistryProtocol的refer()方法, 查询服务demoService的地址.
-
服务调用
- Dubbo支持多种通信框架, 如netty4, 需要在服务端的XML的protocol标签中, 定义server属性为框架名, 在客户端SML的consumer中, 定义client属性为框架名.
- 服务端采用NIO方式处理客户端的请求.
- 传输协议不仅支持私有的Dubbo协议, 还支持其他的如Hessian、RMI、HTTP、Web Service、Thrift等.
- 支持多种序列化格式, 如Dubbo、Hession2.0、JSON、Java、Kryo以及FST等, 可以在protocol标签的serialization属性定义.
-
服务监控
- 无论提供者还是消费者, 在调用时都会经过Filter调用链拦截, 完成一些特定功能, 包括数据埋点.
-
服务治理
注册中心如何落地
-
注册中心如何存储服务信息
-
内容
- 分组
- 服务名
-
节点信息
- 节点地址
- 节点其他信息
- 一般按照"服务-分组-节点信息"三层结构存储
-
工作包括四个工作流程
-
服务提供者注册流程
- 查看注册的节点是否在白名单内, 不在就抛异常, 在就继续
- 注册的Cluster(服务的接口名)是否存在, 不在就抛错, 在就继续
- Service(服务的分组)是否存在, 不在就抛错, 在就继续
- 将节点信息添加到Service和Cluster下面
-
服务提供者反注册流程
- Service(服务的分组)是否存在, 不在就抛错, 在就继续
- Cluster(服务的接口名)是否存在, 不在就抛错, 在就继续
- 删除对应节点信息
- 更新Cluster的sign值
-
服务消费者查询流程
- 先从本机内存(local cache)中查找. 因为服务节点信息不总是时刻变化的.
- 接着从本地快照(snapshot)中查找, 这是持久化到本地的一个文件, 重启后连不上注册中心也可读.
-
服务消费者订阅变更流程
- 服务消费者从注册中心获取了服务信息后, 就订阅了服务的变化, 会在本地保留Cluster的sign值.
- 服务消费者每隔一段时间就获取注册中心sign值, 如果变了久就重新拉一份到本地.
-
注册与发现的几个问题
- 多注册中心: 对于服务消费者来说, 要能够同时从多个注册中心订阅服务; 对于服务提供者来说, 要能够同时向多个注册中心注册.
- 并行订阅服务: 避免某些服务初始化慢, 阻塞其他服务加载.
- 批量反注册服务.
- 服务变更信息增量更新: 防止网络频繁抖动出现问题, 可以只返回变更的接口.
开源服务注册中心如何选型
-
应用内注册
- 定义: 注册中心提供服务端和客户端SDK, 业务应用通过SDK与注册中心交互.
-
典型应用: Netflix开源的Eureka
-
组成
- Eureka Server
- 服务端的Eureka Client
- 客户端的Eureka Client
-
特点
- AP型注册中心, 先保证可用性, 可能会牺牲一致性.
- AP型更适合微服务场景.
- 多种语言时, 需要使用Sidercar解决方案.
-
应用外注册
- 定义: 业务应用不通过SDK而是其他方式与注册中心打交道, 间接完成服务注册于发现.
-
典型应用: Consul
-
组成
- Consul: 注册中心的服务端.
- Registrator: 一个开源的第三方服务管理器项目, 通过监听服务部署的Docker实例是否存活, 来负责服务提供者的注册和销毁.
- Consul Template: 定时从注册中心服务端获取最新的服务提供者节点列表, 并刷新LB配置(如Nginx的upstream).
-
特点
- CP型注册中心, 先保证一致性, 会牺牲可用性.
- 适合业务语言体系复杂的场景, 支持多语言接入.
- 更适合云原生应用.
-
CAP理论
-
意义
- C, Consistency, 一致性
- A, Availability, 可用性
- P, Partion Tolerance, 分区容错性
- 数据节点越多, 分区容错性越高, 数据一致性越难保证; 而为了保证数据一致性, 又会带来可用性的问题.
开源RPC框架选型
-
选择
-
跟语言平台绑定
- Dubbo: 阿里2011年开源, 仅支持Java.
- Spring Cloud: Pivotal公司2014年开源, 仅支持Java.
- Motan: 微博2016年开源, 仅支持Java.
- Tars: 腾讯2017年开源, 进支持C++.
-
跨语言
- gRPC: google公司2015年开源.
- Thrift: Facebook公司2007年贡献给Apache基金会, 成为开源项目.
-
明细
- Dubbo
1. 组成
1. Consumer
2. Provider
3. Registry
4. Monitor, 监控系统
2. SDK方式提供
3. 默认采用Netty作为通信框架.
4. 通信协议: Dubbo、RMI、Hession、HTTP、Thrift
5. 序列化格式: Dubbo、Hession、JSON、Kryo、FST
2. Motan
1. 组成
1. register: 服务端和客户端用来和注册中心交互
2. protocol: 用来进行RPC服务的描述和RPC服务的配置管理, 这层可添加filter做统计和并发限制
3. serialize: 序列化与反序列化, 默认使用Hessian2.
4. transport: 用来进行远程通信, 默认使用Netty NIO的TCP长连接方式.
5. cluster: Client短使用的模块, 是一组可用的Server在逻辑上的封装.
2. SDK方式提供
3. Spring Cloud
1. 基于Spring Boot开发, 整合了开源行业中优秀的组件, 提供一整套解决方案.
2. 工作流程
1. 请求统一通过API网关Zuul来访问内部服务, 先经过Token进行安全认证.
2. 通过安全认证后, Zuul从注册中心Eureka获取可用服务结点列表.
3. 从可用服务结点列表中选取一个可用节点, 然后把请求分发到这个节点.
4. Hystrix组件负责处理服务超时熔断
5. Turbine组件负责监控服务间的调用和熔断相关指标
6. Sleuth组件负责调用链监控
7. ELK负责日志分析
3. 通信只能采用HTTP协议
4. gRPC
1. 原理是通过IDL(Interface Definition Language)文件定义服务接口, 通过代码生成程序生成具体实现代码.
2. 通信协议采用HTTP/2, 因其提供了连接复用、双向流、服务器推送、请求有限级、首部压缩等机制, 节省带宽、降低TCP连接次数, 节省CPU, 对移动端来说延长电池寿命.
3. IDL使用ProtoBuf, 由google开发的一种数据序列化协议, 压缩和传输效率极高, 语法简单.
4. 多语言支持.
5. Thrify
1. 有一套自己的IDL
2. 支持多种序列化格式: Binary、Compact、JSON、Multiplexed
3. 支持多种通信方式: Socket、Framed、File、Memory、zlib
4. 服务端支持多种处理方式: Simple、Thread Pool、Non-Blocking
- 未来语言不会成为RPC框架的约束, Dubbo、Motan和Spring Cloud纷纷引入Sidecar组件来支持多语言.
如何搭建一个可靠的监控系统
-
以ELK为代表的集中式日志解决方案
-
结构
-
Elasticsearch
- 负责search和analyze, 具有可伸缩、高可靠和易管理等特点, 基于Apache Lucene构建, 能对大容量的数据进行接近实时的存储、搜索和分析, 通常被用作基础搜索引擎.
-
Logstash
- 负责collect和transform, 支持动态地从各种数据源收集数据, 并对数据进行过滤、分析、格式化等, 然后存储到指定的位置.
-
Kibana
-
需要在各个服务器部署Logstash, 消耗CPU和内存, 所以又引入了Beats作为数据收集器, 后者占用的CPU和内存忽略不计, 可以从成百上千或成千上万台机器向Logstash或者直接向Elasticsearch发送数据. Beats支持多种数据源:
- Packetbeat, 用来收集网络流量数据.
- Topbeat, 用来收集系统、进程的CPU和内存使用情况等数据.
- Filebeat, 用来收集文件数据.
- Winlogbeat, 用来收集Windows事件日志数据.
-
以Graphiete、TICK和Prometheus等为代表的时序数据库解决方案.
-
Graphite
-
组成
-
Carbon
- 主要作用是接收被监控节点的连接, 收集各个指标的数据, 将这些数据写入carbon-cache并最终持久化到Whisper存储文件中去.
- 对写入格式有一定要求: key+空格分隔符+value+时间戳
- Whisper: 一个简单的时序数据库, 主要作用是存储时间序列数据, 可以按照不同的时间粒度来存储数据, 比如1分钟1个点、5分钟1个点、15分钟1个点三个精度来存储监控数据.
-
Graphite-Web
http://graphite.example.com/render?target=servers.www01.cpuUsage&width=500&height=300&from=-24h
target=sumSeries(products.*.salesPerMinute)
- 自身不包含数据采集组件, 但可以接入StatsD等开源数据采集组件来采集数据, 再传送给Carbon.
-
TICK
-
组成
<measurement>[,<tag-key>=<tag-value>...] <field-key>=<field-value>[,<field2-key>=<field2-value>...] [unix-nano-timestamp]
-
Prometheus
-
组成
- Prometheus Server: 用于拉取metrics信息并将数据存储在时序数据库
- Jobs/Exporters: 用于暴露已有的第三方服务的metrics给Prometheus Server, 比如StatsD、Graphite等, 负责数据收集.
- Pushgateway: 主要用于短期的jobs, 可能在Prometheus Server来拉取之前就消失了, 所有提供了push机制.
- Alertmanager: 用于数据报警.
- Prometheus web UI: 负责数据展示
-
工作流程
<metric name>{<label name>=<label value>, …}
-
选型比对
- ELK的技术栈比较成熟, 应用范围也比较广, 除了可用作监控系统外, 还可以用作日志查询和分析. 缺点是实时性不如时序数据库, 不能保证10s内的延迟.
- Graphite需要搭配数据采集系统如StatsD, 基于时序数据库, 提供了强大的各种聚合函数, 对外提供API可以接入Grafana, 比原生的美观.
- TICK的核心在于时序数据库InfluxDB存储功能强大, 支持类似SQL语言的复杂数据处理操作, 也建议用Grafana替换掉原生的Chronograf.
- Prometheus的独特之处在于采用了拉数据的方式, 对业务影响较小, 也采用了时序数据库, 支持独有的PromQL查询语言. 比较适合Docker封装好的云原生应用.
如何搭建一套适合你的服务追踪系统
-
服务追踪系统
- 埋点数据收集, 负责在服务端进行埋点, 来收集服务调用的上下文数据.
- 实时数据处理, 负责对收集到的链路信息, 按照traceId和spanId进行串联和存储.
- 数据链路展示, 把处理后的服务调用数据, 按照调用链的形式展示出来
-
有名的服务追踪系统
- 阿里的鹰眼, 为开源, 数据量大, 相对定制化, 不一定适合中小规模业务团队.
- Twitter开源的OpenZipkin
- 还有Naver开源的Pinpoint
-
OpenZipkin
-
4个核心部分
- Collector: 负责收集探针Reporter埋点采集的数据, 经过验证处理并建立索引.
- Storage: 存储服务调用的链路数据, 默认使用的是Cassandra, 也可以换成ElasticSearch或者MySQL
- API: 将格式化和建立索引的链路数据以API的方式对外提供服务
- UI: 以图形化的方式展示服务调用的链路信息
- 流程是通过在业务的HTTP Client前后引入服务追踪代码, 在HTTP方法调用前, 生成trace信息, 返回结果后, 记录耗时, 再把这些信息异步上传给ZipKin Collector.
-
Pinpoint
- 深度支持Java
-
4个核心部分
-
Pinpoint Agent: 通过Java字节码注入的方式, 收集JVM中的调用数据, 通过UDP协议传递给Collector, 数据采用Thrift协议进行编码.
- 即JVM在加载class二进制文件时, 动态地修改加载的class文件, 在前后执行拦截器的before和after方法.
- 只需要在JVM启动时添加启动参数即可.
- Pinpoint Collector: 收集Agent传过来的数据, 然后写到HBase Storage.
- HBase Storage
- Pinpoint Web UI: 通过Web UI展示服务调用的详细链路信息
-
选型对比
-
埋点探针支持平台的广泛性
- OpenZipkin提供了不同语言的Library, 使用范围广, 开源社区更活跃, 生命力更强.
-
系统集成难易度
- OpenZipkin必须在配置里面添加相应配置文件并且增加trace业务代码
- Pinpoint通过字节码注入实现拦截服务调用, 不需要代码改动.
-
调用链路数据的精确度
- OpenZipkin数据只到接口级别, 进一步的信息就没有了.
- Pinpoint还能深入到调用所关联的数据库信息
如何识别服务节点存活
- Zookeeper的机制是注册中心摘除机制. 当网络频繁抖动时会有问题.
-
动态注册中心解决方案
-
心跳开关保护机制
- 给注册中心设置一个开关, 打开后即便网络频繁抖动, 也不全量通知, 而是部分通知.
- 好处是将请求量降下来.
- 坏处是拉取最新节点信息的延迟更高.
-
服务节点摘除保护机制
- 设定一个阈值比例, 比如20%(根据业务冗余度确定), 注册中心不能摘除超过这个阈值比例的节点, 以免引起存活实例被压垮引发雪崩.
-
静态注册中心解决方案
- 移除不可用节点放在消费者一端. 如果连续失败超过一定次数, 就可以将其标为不可用, 并且每隔一段时间做一次探活.
- 当业务上线或者运维人员人工增加或者删除服务节点这种预先感知的情况下, 还是有必要去修改注册中心的服务节点信息.
负载均衡算法
-
常见
- 随机
- 轮询
- 加权轮询
- 最少活跃连接数
- 一致性hash算法: 可以保证相同来源的请求到同一个节点, 适合服务端节点处理不同客户端请求差异较大的场景, 比如缓存用户数据的情况.
-
自适应最优选择算法
- 在客户端本地维护一份同一个服务节点的性能统计快照, 并且定时更新.
- 根据二八原则分成两部分, 将最慢的20%的节点降权.
路由
-
应用场景
- 分组调用: 异地多活
- 灰度发布
- 流量切换
- 读写分离
-
两种规则
-
条件路由
- 排除某个服务节点
- 黑白名单
- 机房隔离
- 读写分离
-
脚本路由
- js
- groovy
- JRuby
-
服务路由的获取方式
- 本地配置
- 配置中心管理
- 动态下发: 运维或者开发人员, 通过服务治理平台修改路由规则, 平台调用配置中心接口, 把规则持久化到配置中心.
服务端出现故障
-
集群故障
-
原因
- 代码bug, 比如不断分配大对象, 没有及时回收导致OOM
- 突发的流量冲击
-
应对思路
- 限流: 超过阈值的请求丢弃.
-
降级: 通过停止系统中的某些功能, 来保证系统整体的可用性.
- 通过开关来实现, 如果开关打开, 则跳过某些功能和逻辑. 正常情况下, 开关是关闭的.
-
用在两个地方
- 新增的业务逻辑
- 依赖的服务或资源
-
单IDC故障
- 基于DNS解析的流量切换(联通/电信)
- 基于RPC分组的流量切换(多个IDC之间的切换)
-
单机故障
调用失败时如何应对
-
超时
- 设置一个P999或者P9999的值, 也就是以99.9%或者99.99%的调用都在多少毫秒内返回为准.
-
重试
- 如果是偶然原因造成的调用失败, 那假如一次调用失败的概率是1%, 连续两次失败的概率就是万分之一, 是原来失败率的1%.
- 保持接口幂等性.
-
双发
- 鲁莽的双发: 每个请求直接发起两次调用, 并以先返回的结果为准, 还能降低平均响应时间. 这会给服务端2倍的压力.
-
聪明的双发: 在给定的时间内如果没有返回, 就再发起一次.
- 这个时间比超时时间短很多, 可以取到P99或者P90.
- 设置一个最大重试比例, 避免服务端出现问题时, 大家都发两次. 可以设置成15%.
- 注意接口的幂等性.
-
熔断
- Closed状态: 正常情况下, 断路器是处于关闭状态的, 偶发的调用失败也不影响.
- Open状态: 当服务调用失败次数达到一定阈值时, 断路器就会处于开启状态, 后续的服务就直接返回, 不会向服务提供者发起请求.
- Half Open状态: 当断路器开启后, 每隔一段时间, 就会进入半打开状态, 这时候会向服务提供者发起探测调用, 以确定服务提供者是否恢复正常. 如果调用成功, 断路器就关闭, 如果没有成功, 断路器就继续保持开启, 并等待下一个周期重新进入半打开状态.
- 可以参照Hystrix.
https://segmentfault.com/a/1190000020314356