跳到主要内容Spring Cloud 微服务架构:网关 Zuul、链路追踪 Sleuth 与 Admin 健康检查 | 极客日志JavaWeChatjava
Spring Cloud 微服务架构:网关 Zuul、链路追踪 Sleuth 与 Admin 健康检查
介绍 Spring Cloud 微服务架构中的三个核心组件。首先讲解网关 Zuul 的概念、配置及负载均衡功能;其次阐述链路追踪的必要性,并演示如何使用 Sleuth 结合 Zipkin 进行分布式跟踪;最后介绍 Spring Cloud Admin 的健康检查机制,包括服务端与客户端配置,以及邮件和钉钉通知的实现。内容涵盖依赖引入、代码示例及关键配置项。
晚风叙旧31 浏览 网关
概念
服务治理,服务注册发现,服务调用,熔断。已经学完。
微服务基本模块已经有了,也可以做微服务了。但完成一个复杂的业务,可能需要多个微服务合作来完成,比如下单,需要用户服务,支付服务,地图服务,订单服务。一般是我们对外服务的窗口,进行服务内外隔离。一般微服务都在内网,不做安全验证。
就好像:很多明星,可以独立开演唱会(独立提供服务)。也可以去春晚(微服务群提供服务)。但一台春晚就不能让 观众一个一个调用了。观众要调用,需要检票啥的,检票就类似于网关,进来之后,界面随便看,不会说你 看个小品,还需要再检票。
微服务没有网关,会有下面的问题:
- 客户端请求多个微服务,增加了客户端复杂性,每个微服务都要做用户认证,限流等,避免和多个微服务打交道的复杂性。
- 有跨域问题,不在同一个域。
- 认证复杂,每个服务都要独立认证,服务要求的权限不一致。
- 难以重构。因为微服务被客户端调用着,重构难以实施。
网关是介于客户端(外部调用方比如 app,h5)和微服务的中间层。
Zuul 是 Netflix 开源的微服务网关,核心是一系列过滤器。这些过滤器可以完成以下功能。
- 是所有微服务入口,进行分发。
- 身份认证与安全。识别合法的请求,拦截不合法的请求。
- 监控。在入口处监控,更全面。
- 动态路由。动态将请求分发到不同的后端集群。
- 压力测试。可以逐渐增加对后端服务的流量,进行测试。
- 负载均衡。也是用 ribbon。
- 限流。比如我每秒只要 1000 次,10001 次就不让访问了。
- 服务熔断
网关和服务的关系:演员和剧场检票人员的关系。
zuul 默认集成了:ribbon 和 hystrix。
启用网关
新建项目引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
配置文件
eureka.client.service-url.defaultZone=http://euk1.com:7001/eureka/
spring.application.name=zuulserver
server.port=80
启动类
@EnableZuulProxy
网关会将服务名转换成具体服务的 ip 和端口,实际进行访问
负载均衡
轮询访问上面地址,会看到返回结果中,端口一直轮询在变。说明负载均衡生效了,默认是轮询
consumer.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
路由端点
调试的时候,看网关请求的地址,以及 映射是否正确。网关请求有误时,可以通过此处排查错误。
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.endpoint.health.enabled=true
management.endpoint.routes.enabled=true
配置指定微服务的访问路径
zuul.routes.consumer=/xxoo/**
zuul.routes.xx.path=/xx/**
zuul.routes.xx.url=http://dyll.com
zuul.routes.xx.path=/xx/**
zuul.routes.xx.service-id=cuid
cuid.ribbon.listOfServers=localhost:82,localhost:83
ribbon.eureka.enabled=false
忽略微服务
zuul.ignored-services=user-provider
前缀
高可用
分布式计算八大误区
链路追踪
链路追踪的必要性
如果能跟踪每个请求,中间请求经过哪些微服务,请求耗时,网络延迟,业务逻辑耗时等。我们就能更好地分析系统瓶颈、解决系统问题。因此链路跟踪很重要。
我们自己思考解决方案:在调用前后加时间戳。捕获异常。
链路追踪目的:解决错综复杂的服务调用中链路的查看。排查慢服务。
市面上链路追踪产品,大部分基于 google 的 Dapper 论文。
zipkin,twitter 开源的。是严格按照谷歌的 Dapper 论文来的。 pinpoint 韩国的 Naver 公司的。 Cat 美团点评的 EagleEye 淘宝的
链路追踪要考虑的几个问题
- 探针的性能消耗。尽量不影响 服务本尊。
- 易用。开发可以很快接入,别浪费太多精力。
- 数据分析。要实时分析。维度足够。
Sleuth 简介
Sleuth 是 Spring cloud 的分布式跟踪解决方案。
- span(跨度),基本工作单元。一次链路调用,创建一个 span,span 用一个 64 位 id 唯一标识。包括:id,描述,时间戳事件,spanId,span 父 id。span 被启动和停止时,记录了时间信息,初始化 span 叫:root span,它的 span id 和 trace id 相等。
- trace(跟踪),一组共享'root span'的 span 组成的树状结构 称为 trace,trace 也有一个 64 位 ID,trace 中所有 span 共享一个 trace id。类似于一颗 span 树。
- annotation(标签),annotation 用来记录事件的存在,其中,核心 annotation 用来定义请求的开始和结束。
- CS(Client Send 客户端发起请求)。客户端发起请求描述了 span 开始。
- SR(Server Received 服务端接到请求)。服务端获得请求并准备处理它。SR-CS=网络延迟。
- SS(Server Send 服务器端处理完成,并将结果发送给客户端)。表示服务器完成请求处理,响应客户端时。SS-SR=服务器处理请求的时间。
- CR(Client Received 客户端接受服务端信息)。span 结束的标识。客户端接收到服务器的响应。CR-CS=客户端发出请求到服务器响应的总时间。
其实数据结构是一颗树,从 root span 开始。
使用
Sleuth 单独
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
- 启动 eureka 7900,service-sms 8002,api-driver 9002.
- 访问一次。看日志结果。
[api-driver,1a409c98e7a3cdbf,1a409c98e7a3cdbf,true][服务名称,traceId(一条请求调用链中 唯一 ID),spanID(基本的工作单元,获取数据等),是否让 zipkin 收集和展示此信息] 看下游 [service-sms,1a409c98e7a3cdbf,b3d93470b5cf8434,true] traceId, 是一样的。 服务名必须得写。
Zipkin
zipkin 是 twitter 开源的分布式跟踪系统。
原理收集系统的时序数据,从而追踪微服务架构中系统延时等问题。还有一个友好的界面。
Collector、Storage、Restful API、Web UI 组成
sleuth 收集跟踪信息通过 http 请求发送给 zipkin server,zipkin 将跟踪信息存储,以及提供 RESTful API 接口,zipkin ui 通过调用 api 进行数据展示。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
spring:
zipkin:
base-url: http://localhost:9411/
sleuth:
sampler:
rate: 1
jar 包下载:curl -sSL https://zipkin.io/quickstart.sh | bash -s
java -jar zipkin.jar
docker run -d -p 9411:9411 openzipkin/zipkin
SpringCloud Admin 健康检查
Admin 服务器端
引入依赖
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-ui</artifactId>
</dependency>
package com.example.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
@SpringBootApplication
@EnableAdminServer
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
微服务端
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
spring.boot.admin.client.url=http://localhost:8080
邮件通知
spring:
application:
name: cloud-admin
security:
user:
name: root
password: root
mail:
host: smtp.qq.com
username: 单纯 QQ 号
password: xxxxxxx授权码
properties:
mail:
smpt:
auth: true
starttls:
enable: true
required: true
spring.boot.admin.notify.mail.to: 2634982208@qq.com
spring.boot.admin.notify.mail.from: [email protected]
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
钉钉群通知
启动类
package com.example.admin;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import de.codecentric.boot.admin.server.config.EnableAdminServer;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
@SpringBootApplication
@EnableAdminServer
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
@Bean
public DingDingNotifier dingDingNotifier(InstanceRepository repository) {
return new DingDingNotifier(repository);
}
}
通知类
package com.example.admin;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.notify.AbstractStatusChangeNotifier;
import reactor.core.publisher.Mono;
public class DingDingNotifier extends AbstractStatusChangeNotifier {
public DingDingNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event, Instance instance) {
String serviceName = instance.getRegistration().getName();
String serviceUrl = instance.getRegistration().getServiceUrl();
String status = instance.getStatusInfo().getStatus();
Map<String, Object> details = instance.getStatusInfo().getDetails();
StringBuilder str = new StringBuilder();
str.append("服务预警 : 【" + serviceName + "】");
str.append("【服务地址】" + serviceUrl);
str.append("【状态】" + status);
str.append("【详情】" + JSONObject.toJSONString(details));
return Mono.fromRunnable(() -> {
DingDingMessageUtil.sendTextMessage(str.toString());
});
}
}
发送工具类
package com.example.admin;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import com.alibaba.fastjson.JSONObject;
public class DingDingMessageUtil {
public static String access_token = "your_access_token";
public static void sendTextMessage(String msg) {
try {
Message message = new Message();
message.setMsgtype("text");
message.setText(new MessageInfo(msg));
URL url = new URL("https://oapi.dingtalk.com/robot/send?access_token=" + access_token);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Type", "application/Json; charset=UTF-8");
conn.connect();
OutputStream out = conn.getOutputStream();
String textMessage = JSONObject.toJSONString(message);
byte[] data = textMessage.getBytes();
out.write(data);
out.flush();
out.close();
InputStream in = conn.getInputStream();
byte[] data1 = new byte[in.available()];
in.read(data1);
System.out.println(new String(data1));
} catch (Exception e) {
e.printStackTrace();
}
}
}
消息类
package com.example.admin;
public class Message {
private String msgtype;
private MessageInfo text;
public String getMsgtype() {
return msgtype;
}
public void setMsgtype(String msgtype) {
this.msgtype = msgtype;
}
public MessageInfo getText() {
return text;
}
public void setText(MessageInfo text) {
this.text = text;
}
}
public class MessageInfo {
private String content;
public MessageInfo(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
微信通知
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online