目标1: CAT链路监控简介与原理
目标2: CAT搭建配置与使用
目标3: Sentinel简介与原理
目标4: Sentinel配置与使用
从单体架构到微服务架构的演变, 一个业务请求往往会流转多个服务, 大型互联网产品服务架构尤为复杂,腾讯的抢红包服务, 阿里的交易支付服务, 可能就流转成百上千个服务节点, 面对众多服务, 如何监控管理? 服务请求一旦出现问题, 如何快速定位问题? 如何保障服务的高可用, 做到全面的监控与预警? 如何分析统计服务的运行状况? 看下链路监控产品如何解决这些问题。
应用类型报表
|报表名称|报表用途|
|------------|------------|
|Transaction实时报表|一段代码的运行时间/次数/分布、比如URL/Cache/SQL执行次数和响应时间|
|Event实时报表 |事件产生的次数/分布,比如出现一个异常|
|Problem实时报表 |根据Transaction/Event数据分析出来的系统出现的异常,包括访问较慢的程序等|
|Heartbeat实时报表 |JVM内部一些状态信息,Load/Memory/GC/Thread等|
|Metric实时报表 |业务指标采集监控报表|
|Matrix实时报表|一个请求调用分布统计(一次请求中调用多少次SQL/RPC/Cache等),可评估应用设计的合理性|
|…|…|
整体设计
简单即是最好原则设计, 主要分为三个模块cat-client,cat-consumer,cat-home。
客户端设计
客户端设计是CAT系统设计中最为核心的一个环节,客户端要求是做到API简单、高可靠性能、无论在任何场景下客户端都不能影响各业务服务的性能(监控只是公司核心业务流程一个旁路环节)。
当某个报表处理器处理来不及时候,比如Transaction报表处理比较慢,可以通过配置支持开启多个Transaction处理线程,并发消费消息。
环境要求
CAT启动配置
不建议在Windows下部署, 设计上对window支持不好, 容易出各种问题。
下载CAT源码, 如GIT方式过慢, 可用Download Zip 方式打包下载。
构建CAT服务war包
可以导入IDEA工程进行编译, 或者直接用MAVEN进行编译:
将MAVEN加入到系统PATH, 执行mvn命令:
mvn clean install -Dmaven.test.skip=true
创建数据库
先创建CAT数据库, 采用utf8mb4字符集, 再导入{CAT_SRC}/script/目录下的CatApplication.sql脚本。
配置修改
运行盘下的/data/appdatas/cat和/data/applogs/cat有读写权限, 如果程序是在E盘, 则需创建e:/data/appdatas/cat目录
配置/data/appdatas/cat/client.xml (客户端使用)
<config mode="client">
<servers>
<server ip="127.0.0.1" port="2280" http-port="8080"/>
servers>
config>
配置/data/appdatas/cat/datasources.xml (服务端使用)
TOMCAT配置
修改tomcat conf 目录下 server.xml, 检查好端口没有被其他程序占用。
如果内存不足, 需作调整(linux)
```sh
CATALINA_OPTS="-Xms1024m -Xmx2048m -Xss1024K -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m"
执行start.sh
打开控制台地址: http://10.10.20.10:8080/cat/s/config?op=routerConfigUpdate
默认用户名:admin 默认密码:admin
课程演示, 不作详细介绍, 具体可参考官方文档: CAT集群部署
设计四个服务:网关服务、订单服务、账户服务和库存服务, 三层调用关系监控,
Gateway->Order->Account、Stock
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>com.dianping.catgroupId>
<artifactId>cat-clientartifactId>
<version>3.0.0version>
dependency>
dependencies>
@Configuration
public class CatFilterConfigure {
@Bean
public FilterRegistrationBean catFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
CatServletFilter filter = new CatServletFilter();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setName("cat-filter");
registration.setOrder(1);
return registration;
}
}
CatServletFilter实现类:
package com.itcast.cat.demo.gateway.catutils;
import com.dianping.cat.Cat;
import com.dianping.cat.CatConstants;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class CatServletFilter implements Filter {
private String[] urlPatterns = new String[0];
/**
* 初始化配置
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String patterns = filterConfig.getInitParameter("CatHttpModuleUrlPatterns");
if (patterns != null) {
patterns = patterns.trim();
urlPatterns = patterns.split(",");
for (int i = 0; i < urlPatterns.length; i++) {
urlPatterns[i] = urlPatterns[i].trim();
}
}
}
/**
* 请求过滤处理
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String url = request.getRequestURL().toString();
for (String urlPattern : urlPatterns) {
if (url.startsWith(urlPattern)) {
url = urlPattern;
}
}
// cat 上下文信息设置
CatContext catContext = new CatContext();
catContext.addProperty(Cat.Context.ROOT, request.getHeader(CatHttpConstants.CAT_HTTP_HEADER_ROOT_MESSAGE_ID));
catContext.addProperty(Cat.Context.PARENT, request.getHeader(CatHttpConstants.CAT_HTTP_HEADER_PARENT_MESSAGE_ID));
catContext.addProperty(Cat.Context.CHILD, request.getHeader(CatHttpConstants.CAT_HTTP_HEADER_CHILD_MESSAGE_ID));
Cat.logRemoteCallServer(catContext);
Transaction t = Cat.newTransaction(CatConstants.TYPE_URL, url);
try {
// cat日志记录
Cat.logEvent("Service.method", request.getMethod(), Message.SUCCESS, request.getRequestURL().toString());
Cat.logEvent("Service.client", request.getRemoteHost());
filterChain.doFilter(servletRequest, servletResponse);
t.setStatus(Transaction.SUCCESS);
} catch (Exception ex) {
t.setStatus(ex);
Cat.logError(ex);
throw ex;
} finally {
t.complete();
}
}
}
RestTemplate请求拦截器
请求传递拦截器, 保存CAT调用链信息, 跟踪记录发送至外部服务的请求。
CatRestInterceptor
@Component
public class CatRestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
Transaction t = Cat.newTransaction(CatConstants.TYPE_CALL, request.getURI().toString());
try {
HttpHeaders headers = request.getHeaders();
// 保存和传递CAT调用链上下文
Context ctx = new CatContext();
Cat.logRemoteCallClient(ctx);
headers.add(CatHttpConstants.CAT_HTTP_HEADER_ROOT_MESSAGE_ID, ctx.getProperty(Context.ROOT));
headers.add(CatHttpConstants.CAT_HTTP_HEADER_PARENT_MESSAGE_ID, ctx.getProperty(Context.PARENT));
headers.add(CatHttpConstants.CAT_HTTP_HEADER_CHILD_MESSAGE_ID, ctx.getProperty(Context.CHILD));
// 继续执行请求
ClientHttpResponse response = execution.execute(request, body);
t.setStatus(Transaction.SUCCESS);
return response;
} catch (Exception e) {
Cat.getProducer().logError(e);
t.setStatus(e);
throw e;
} finally {
t.complete();
}
}
}
注意, RestTemplate的初始化配置:
@Bean
RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// 保存和传递调用链上下文
restTemplate.setInterceptors(Collections.singletonList(new CatRestInterceptor()));
return restTemplate;
}
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="config.xsd">
<servers>
<server ip="10.10.20.10" port="2280" http-port="8080" />
servers>
config>
服务最好要在Linux下运行, 源码没有正确识别windows盘符, 会有问题。
Nacos控制台
访问Gateway服务, 本示例是部署在Linux机器上, 地址: http://10.10.20.10:8081/gateway
gateway service ==> Calling order service[order success] ==> Calling Account Service [account success] ==> Calling Customer Service [stock success]
CAT的LOGVIEW按层级完整的记录了四个服务的请求信息, 1至4分别对应Gateway、Order、Account和Stock服务。
LOGVIEW主要包含请求时间, 服务地址, 请求客户端等主要信息, 也支持图形方式呈现:
CAT 还有很多指标统计与报表展示, 能有效帮助我们监控管理整体微服务调用链路状态。
微服务架构设计由众多微服务组成,为保障高可用,通常会采用集群方式部署。由于服务自身原因或网络等其他问题,并不能保证100%可用性, 若单个服务出现问题, 会导致进入该服务的线程阻塞, 如果大量请求, 服务可能瘫痪, 服务与服务之间的依赖性, 故障会传播, 产生雪崩效应, 为解决和规避此问题, 业界提出熔断器模型, 衍生出了Sentinel,Hystrix和Resilience4j等组件。
Sentinel地址
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
与Hystrix熔断理念一致, 主要控制调用链中的不稳定资源, 针对这些不同场景进行限制, 避免影响整体系统的稳定性, 防止出现穿透、雪崩等灾难性问题。
在熔断实现上, Sentinel与Hystrix存在较大差异:
框架设计
设计说明
在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:
扩展机制
Sentinel 将 SlotChainBuilder 作为 SPI 接口进行扩展,使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。
Sentinel 控制台包含功能:
从GitHub下载地址下载最新版本。
启动命令, 配置启动端口:
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -jar sentinel-dashboard-1.6.2.jar
访问地址:http://127.0.0.1:8090/#/dashboard
默认登陆账号与密码都是sentinel,也可以通过启动参数修改:
-Dsentinel.dashboard.auth.username=sentinel
-Dsentinel.dashboard.auth.password=123456
平均响应时间演示
当资源的平均响应时间超过阈值(DegradeRule 中的 count,以 ms 为单位)之后,资源进入准降级状态。如果接下来 1s 内持续进入 5 个请求(即 QPS >= 5),它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-transport-simple-httpartifactId>
dependency>
启动参数,指定Dashboard地址:
-Dcsp.sentinel.dashboard.server=127.0.0.1:8090
或代码中添加:
System.setProperty("csp.sentinel.dashboard.server", "127.0.0.1:8090");
规则代码:
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<DegradeRule>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// set threshold rt, 10 ms
rule.setCount(10);
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
异常比例演示
当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
ExceptionRatioDegradeDemo类
private static void initDegradeRule() {
List<DegradeRule> rules = new ArrayList<DegradeRule>();
DegradeRule rule = new DegradeRule();
rule.setResource(KEY);
// set limit exception ratio to 0.1
rule.setCount(0.1);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
rule.setTimeWindow(10);
// rule.setMinRequestAmount(20);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
private static void initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(KEY);
// set limit qps to 20
rule1.setCount(20);
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource("methodA");
// set limit concurrent thread for 'methodA' to 20
rule1.setCount(20);
rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
更多Wiki文档
掌握Dashboard控制台的安装使用, 应用服务的接入配置。
Sentinel的降级与限流功能配置使用, 理解相关控制策略。
readDemo类,限制并发线程访问数为20。
private static void initFlowRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource("methodA");
// set limit concurrent thread for 'methodA' to 20
rule1.setCount(20);
rule1.setGrade(RuleConstant.FLOW_GRADE_THREAD);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
}
输出结果:
[外链图片转存中…(img-uUpz715w-1649904273217)]
控制台监控:
[外链图片转存中…(img-bIKkFnBD-1649904273218)]
更多Wiki文档
掌握Dashboard控制台的安装使用, 应用服务的接入配置。
Sentinel的降级与限流功能配置使用, 理解相关控制策略。