启动nacos
$ docker run --name nacos01 -d \
-p 8848:8848 \
--privileged=true \
--restart=always \
-e JVM_XMS=512m \
-e JVM_XMX=2048m \
-e MODE=standalone \
-e PREFER_HOST_MODE=hostname \
nacos/nacos-server:1.1.4
引入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
编写配置文件将服务注册到nacos中
spring:
# nacos
cloud:
nacos:
discovery:
server-addr: 192.168.1.10:8848
# 不指定名字无法注册进nacos
application:
name: gulimall-coupon
在启动类上加上这个注解开启服务发现的功能
@SpringBootApplication
@EnableDiscoveryClient
public class GulimallCouponApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}
}
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
首先,修改 pom.xml 文件,引入 Nacos Config Starter。
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
在应用的 /src/main/resources/bootstrap.properties 配置文件中配置 Nacos Config 元数据
spring.application.name=nacos-config-example
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
完成上述两步后,应用会从 Nacos Config 中获取相应的配置,并添加在 Spring Environment 的 PropertySources 中。这里我们使用 @Value 注解来将对应的配置注入到 SampleController 的 userName 和 age 字段,并添加 @RefreshScope 打开动态刷新功能。配置的加载:如果配置中心和当前应用的配置文件中都配置了相同的项,优先使用配置中心的配置。
@RefreshScope
class SampleController {
@Value("${user.name}")
String userName;
@Value("${user.age}")
int age;
}
# 这么配置默认找的是 public->DEFAULT->appName.properties 文件(命名空间、组、文件名)
spring.application.name=appName
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=192.168.1.10:8848
spring.cloud.nacos.config.namespace=b4e817d4-8bdd-4e13-a917-14c8abef6296
# 这个是默认配置文件的加载,默认的DEFAULT ,加载的是组里面的 ${spring.application.name}.properties 文件
spring.cloud.nacos.config.group=prod
# 扩展配置
# 读取 b4e817d4-8bdd-4e13-a917-14c8abef6296->prood->datasource.yml
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=prod
spring.cloud.nacos.config.ext-config[0].refresh=true
# 读取 b4e817d4-8bdd-4e13-a917-14c8abef6296->prood->mybatis.yml
spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=prod
spring.cloud.nacos.config.ext-config[1].refresh=true
# 读取 b4e817d4-8bdd-4e13-a917-14c8abef6296->prood->other.yml
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=prod
spring.cloud.nacos.config.ext-config[2].refresh=true
2、细节
* 1)、命名空间:配置隔离;
* 默认:public(保留空间);默认新增的所有配置都在public空间。
* 1、开发,测试,生产:利用命名空间来做环境隔离。
* 注意:在bootstrap.properties;配置上,需要使用哪个命名空间下的配置,
* spring.cloud.nacos.config.namespace=9de62e44-cd2a-4a82-bf5c-95878bd5e871
* 2、每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
*
* 2)、配置集:所有的配置的集合,说白了就是我们在nacos里面写个每一个配置文件。
*
* 3)、配置集ID:类似文件名。
* Data ID:类似文件名
*
* 4)、配置分组:
* 默认所有的配置集都属于:DEFAULT_GROUP;
* 1111,618,1212
*
* 项目中的使用:每个微服务创建自己的命名空间,使用配置分组区分环境,dev,test,prod
*
* 3、同时加载多个配置集
* 1)、微服务任何配置信息,任何配置文件都可以放在配置中心中
* 2)、只需要在bootstrap.properties说明加载配置中心中哪些配置文件即可
* 3)、@Value,@ConfigurationProperties。。。
* 以前SpringBoot任何方法从配置文件中获取值,都能使用。
* 配置中心有的优先使用配置中心中的,
什么是熔断:A服务调用B服务的某个功能,由于网络不稳定问题,或者B服务卡机,导致功能时间超长。如果这样子的次数太多。我们就可以直接将B断路了(A不再请求B接口),凡是调用B的直接返回降级数据,不必等待B的超长执行。这样 B的故障问题,就不会级联影响到A。
什么是降级:整个网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级【停止服务,所有的调用直接返回降级数据】。以此缓解服务器资源的的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应。
相同点:
- 为了保证集群大部分服务的可用性和可靠性,防止崩溃,牺牲小我
- 用户最终都是体验到某个功能不可用
不同点:
- 熔断是被调用方故障,触发的系统主动规则。(一个远程服务多次出现问题,下一次调用这个服务时就直接触发熔断方法而不是去调用服务)
- 降级是基于全局考虑,停止一些正常服务,释放资源(每次请求都会到达服务提供方,但是降级之后就不进入业务代码而是直接最新降级的方法)
什么是限流:对打入服务的请求流量进行控制,使服务能够承担不超过自己能力的流量压力
好好理解这些话
熔断是被调用方的故障,A去调用B B由于自己的问题A等不及了A觉得他太慢了。A以后就有经验了,再次调用B的这个服务的时候就直接返回,A系统的主动规则。
降级:有可能是人工做的,人工关闭服务。
熔断一般都写在消费端,降级一般卸载服务提供方。
官方文档: https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
项目地址: https://githab.com/alibaba/Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel具有以下特征
Sentinel分为两个部分:
Sentinel基本概念:
整合sentinel
导入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
下载sentinel控制台:主要是可视化监控
配置sentinel控制台地址信息:这样子我们的数据就会被dashboard监控到
# sentinel dashboard
spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.transport.port=8719
在控制台调整参数。【默认所有的流控设置保存在应用的内存中(就是微服务的内存中),重启失效】
上面的规则配置,都是存在内存中的。即如果应用重启,这个规则就会失效。因此我们提供了开放的接口,您可以通过实现 DataSource
接口的方式,来自定义规则的存储数据源。通常我们的建议有:
更多详情请参考 动态规则配置。
每个微服务都导入actuator 模块;并配置management.endpoints.web.exposure.include=*
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
自定义sentinel 流控返回信息
@Configuration
public class SeckillSentinelConfig {
public SeckillSentinelConfig() {
WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
R error = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(error));
}
});
}
}
使用sentinel来保护feing远程调用:
feign.sentinel.enabled=true
网址
通过try-catch
// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
// 被保护的业务逻辑
// do something here...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 在此处进行相应的处理操作
}
通过注解方式。
// 原本的业务方法.
@SentinelResource(blockHandler = "blockHandlerForGetUser")
public User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
// blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
public User blockHandlerForGetUser(String id, BlockException ex) {
return new User("admin");
}
无论是1、2方式一定要配置被限流后的默认返回,否则会直接抛出异常。Url 资源可以在微服务中定义同一的返回数据。我们自定义资源需要自己写方法返回熔断数据。
@Configuration
public class SeckillSentinelConfig {
public SeckillSentinelConfig() {
WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
R error = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(error));
}
});
}
}
网址
版本要和导入的spring-cloud-alibaba一致
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-sentinel-gatewayartifactId>
<version>2.1.0.RELEASEversion>
dependency>
自定义详细信息
您可以在 GatewayCallbackManager 注册回调进行定制:
setBlockHandler:注册函数用于实现自定义的逻辑处理被限流的请求,对应接口为 BlockRequestHandler。默认实现为 DefaultBlockRequestHandler,当被限流时会返回类似于下面的错误信息:Blocked by Sentinel: FlowException。
// web flux 响应式编程
@Configuration
public class SentinelGatewayConfig {
public SentinelGatewayConfig() {
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
// 网关限流了请求,就会调用此回调 Mono Flux
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
R error = R.error(BizCodeEnume.TO_MANY_REQUEST.getCode(), BizCodeEnume.TO_MANY_REQUEST.getMsg());
String json = JSON.toJSONString(error);
Mono<ServerResponse> body = ServerResponse.ok().body(Mono.just(json), String.class);
return body;
}
});
}
}
sentinel 是通过spring boot的统计信息从而实现实时监控。springboot的统计信息可以暴露接口供对外使用。需要导入spring-boot-starter-actuator
参考文档https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
### Endpoint 支持
在使用 Endpoint 特性之前需要在 Maven 中添加 `spring-boot-starter-actuator` 依赖,并在配置中允许 Endpoints 的访问。
- Spring Boot 1.x 中添加配置 `management.security.enabled=false`。暴露的 endpoint 路径为 `/sentinel`
- Spring Boot 2.x 中添加配置 `management.endpoints.web.exposure.include=*`。暴露的 endpoint 路径为 `/actuator/sentinel`
seata文档
seata:Simple Extensible Autonomous Transaction Architecture,seata使用用在低并发的系统中比如后台管理系统,因为seata的实现是通过上各种各样的锁实现的。
Seata有3个基本组件:
全局事务与分支事务:
a Distributed Transaction is a Global Transaction which is made up with a batch of Branch Transaction, and normally Branch Transaction is just Local Transaction.
Seata管理分布式事务的典型生命周期:
至此,seata的协议机制总体上看与 XA 是一致的。但是是有差别的:
架构图
XA 方案的 RM 实际上是在数据库层,RM 本质上就是数据库自身(通过提供支持 XA 的驱动程序来供应用使用)。
而 Fescar 的 RM 是以二方包的形式作为中间件层部署在应用程序这一侧的,不依赖于数据库本身对协议的支持,当然也不需要数据库支持 XA 协议。这点对于微服务化的架构来说是非常重要的:应用层不需要为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。
这个设计,剥离了分布式事务方案对数据库在 协议支持 上的要求。
使用seata 控制分布式事务
每一个微服务先必须创建undo_log 表(需要连接到seata服务器就必须有这个表,需要实现全局事务的业务就加入这个表,seata server会往里面注入阶段的日志)
DROP TABLE IF EXISTS `mq_message`;
CREATE TABLE `mq_message` (
`message_id` char(32) NOT NULL,
`content` text,
`to_exchane` varchar(255) DEFAULT NULL,
`routing_key` varchar(255) DEFAULT NULL,
`class_type` varchar(255) DEFAULT NULL,
`message_status` int(1) DEFAULT '0' COMMENT '0-新建 1-已发送 2-错误抵达 3-已抵达',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
安装事务协调器:seata-server https://github.com/seata/seata/releases
整合
导入依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
dependency>
启动seata-server
所有想要用到分布式事务的微服务使用 seata DataSourceProxy代理自己的数据源https://github.com/seata/seata-samples,查看jpa的配置数据源
public class WareConfig {
@Bean
public DataSource dataSource(DataSourceProperties dataSourceProperties) {
HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
if (StringUtils.hasText(dataSourceProperties.getName())) {
dataSource.setPoolName(dataSourceProperties.getName());
}
// 最后在使用seata代理一下我们的数据源
return new DataSourceProxy(dataSource);
}
}
每个微服务都必须导入file.conf 和 registry.conf (file.conf 里面的分组名字需要修改一下,这个名字就是作为注册到TC里面的,alibaba seata自动配置已经写好的分组名字、必须要与file.conf的一致,alibaba seata自动配置的名字是${spring.application.name}-fescar-service-group
)
启动微服务
给分布式大事务的入口标注@GlobalTransactional
每一个远程的小事务用@Transactional
出现的问题
// 使用seata控制分布式事务 不能使用批处理,只能一个个操作
List<OrderItemEntity> orderItems = order.getOrderItems();
// for (OrderItemEntity orderItem : orderItems) {
// orderItemService.save(orderItem);
// }
// exception Error updating database. Cause: io.seata.common.exception.NotSupportYetException
// io.seata.common.exception.NotSupportYetException: null
orderItemService.saveBatch(orderItems);
这里使用阿里云对象存储(OSS)
官方文档:https://help.aliyun.com/document_detail/64041.html?spm=5176.87240.400427.54.1bfd4614VN7fDp
我们之前采用sdk的方式,图片–>后台服务器–>阿里云。
这样后台服务器面临并发压力,既然是上传给阿里云,可不可以直接传给阿里云服务器。
上传成功后,只需要给我一个图片地址保存到数据库即可。
查看官方文档,发现提供了浏览器直接上传到阿里云的参考文档:
登录到个人阿里云控制台,并创建bucket。
找到基础设置
–>跨域设置
点击设置
–>创建规则
–>如下填写表单–>点击确定
选择用户添加权限
选择OSS所有权限
为了方便回显,需要把读写权限改为公共读
用户发送请求到应用服务端,服务端怎么返回policy和签名?官方文档再往下翻,有java示例:
点进去就有示例代码:
导入OSS的依赖
<dependency>
<groupId>com.aliyun.ossgroupId>
<artifactId>aliyun-sdk-ossartifactId>
<version>3.5.0version>
dependency>
编写Controller方法
@RequestMapping("pms/oss")
@RestController
public class PmsOssController {
String accessId = "LTAI4FuGwRrRAh1M8mRkndr6"; // 请填写您的AccessKeyId。
String accessKey = "LvIZkyPyKqoBGcVTY2wABYhv4QJmYT"; // 请填写您的AccessKeySecret。
String endpoint = "oss-cn-shanghai.aliyuncs.com"; // 请填写您的 endpoint。
String bucket = "ggmall"; // 请填写您的 bucketname 。
String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint
// callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//String callbackUrl = "http://88.88.88.88:8888";
// 图片目录,每天一个目录
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dir = sdf.format(new Date()); // 用户上传文件时指定的前缀。
@GetMapping("policy")
public ResponseVo<Object> policy() throws UnsupportedEncodingException {
OSSClient client = new OSSClient(endpoint, accessId, accessKey);
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = client.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = client.calculatePostSignature(postPolicy);
Map<String, String> respMap = new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));
// respMap.put("expire", formatISO8601Date(expiration));
return ResponseVo.ok(respMap);
}
}
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.1.10:8848
application:
name: gulimall-member
@FeignClient(name = "gulimall-coupon" )
public interface CouponFeignService {
@GetMapping("/coupon/coupon/member/list" )
public R memberCoupons();
}
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign" )
public class GulimallMemberApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}
定义一个接口,这个接口必须添加到IOC容器中。
使用@FeignClient 注解申明这是一个feign 接口
如果当前接口所在的包与主启动类同包或者子包下。就能被扫描然后加入IOC 容器中
但是为了省事,我们最好是在主启动类上特定指定一下feign接口的包路径@EnableFeignClients(basePackages = {"com.atguigu.gulimall.product.feign"})
feign 接口远程调用服务过程中,是将数据放到请求体中,所以服务的提供放获取参数的使用必须使用@RequestBody 注解,表示从请求体中获取数据。
流程:
Proxy.newProxyInstance()
)return dispatch.get(method).invoke(args);
return executeAndDecode(template, options);
Request request = targetRequest(template);}
1.
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
2.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
return dispatch.get(method).invoke(args);
}
3.
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template, options);
}
}
4.
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
Request request = targetRequest(template);}
5.
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}
6.
public Builder requestInterceptor(RequestInterceptor requestInterceptor) {
this.requestInterceptors.add(requestInterceptor);
return this;
}
7.
@Configuration
public class GuliFeignConfig {
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
System.out.println("RequestInterceptor...." + Thread.currentThread().getId());
//1、RequestContextHolder拿到同一线程里面的request(这是spring为了简化我们的操作提供的,当然你可以直接从handler方法里面获取HttpServletRequest)
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = requestAttributes.getRequest();// 老请求
// 同步请求头数据,Cookie
String cookie = request.getHeader("Cookie");
// 给请求同步老请求的cookie信息
template.header("Cookie", cookie);
}
}
};
}
}
网关的作用:鉴权、限流、日志输出、隐藏微服务
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/
ServerWebExchange
. This lets you match on anything from the HTTP request, such as headers or parameters.GatewayFilter
that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.断言的编写
filter的编写
gateway:route、predicate、filter
流程:请求到达网关,网关通过断言来判断我们的请求是否符合某个路由规则。如果符合了就按照路由规则路由到指定的地方。到指定地方的过程中我们可以进行过滤,请求的响应结果也会经过过滤。在过滤环节我们可以添加、修改请求体或者响应体的信息。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
spring:
cloud:
# 网关的配置
gateway:
routes:
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?>/?.*), /renren-fast/$\{segment}
微服务架构是一个分布式架构,它按业务划分服务单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难去定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有 哪些服务参与,参与的顺序又是怎样的,从而达到每个请求的步骤清晰可见,出了问题,很快定位。
链路追踪组件有Google的Dapper, Twitter 的Zipkin,以及阿里的Eagleeye (鹰眼) 等,它们都是非常优秀的链路追踪开源组件。
sleuth 官方
Span (跨度) :基本工作单元,发送一个远程调度任务就会产生一个Span, Span 是一个64位ID唯一标识的,Trace是用另一个 64位ID唯一标识的,Span 还有其他数据信息,比如摘要、时间戳事件、Span的ID、以及进度ID.
Trace (跟踪) :一系列Span组成的一个树状结构。请求一个微服务系统的API接口,这个API接口,需要调用多个微服务,调用每个微服务都会产生一个新的Span,所有由这个请求产生的Span组成了这个Trace.
Annotation (标注) :用来及时记录一个事件的,一些核心注解用来定义一个请求的开始和结束。这些往解包括以下:
官方文档
1、服务提供者与消费者导入依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-sleuthartifactId>
dependency>
2、打开debug日志
logging.level.org.springframework.cloud.openfeign=debug
logging.level.org.springframework.cloud.sleuth=debug
3、发起一次远程调用,观察控制台
# DEBUG[user-service,541450f08573ff5,541450f0857 3ff5,false]
user-service : 服务名
5414508573ff5:是Tranceld,一 条链路中,只有一个Tranceld
541450f08573fff5:是spanld,链路中的基本工作单元id
false:表示是否将数据输出到其他服务,true 则会把信息输出到其他可视化的服务上观察
通过Sleuth产生的调用链监控信息,可以得知微服务之间的调用链路,但监控信息只输出到控制台不方便查看。我们需要一个图形化的工具zipkin。Zipkin 是Twitter 开源的分布式跟踪系统,主要用来收集系统的时序数据,从而追踪系统的调用问题。zipkin 官网地址
# 使用docker 安装zipkin
$ docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
# zipkin服务的地址
spring.zipkin.base-url=http://192.168.1.10:9411/
# 关闭服务发现,否则Spring Cloud会把zipkin的url当做服务名称
spring.zipkin.discovery-client-enabled=false
# 设置使用http的方式传输数据
spring.zipkin.sender.type=web
# 设置抽样采集率为100%,默认为0.1,即10%
spring.sleuth.sampler.probability=1
Zipkin默认是将监控数据存储在内存的,如果Zipkin挂掉或重启的话,那么监控数据就会丢失。所以如果想要搭建生产可用的Zipkin,就需要实现监控数据的持久化。而想要实现数据持久化,自然就是得将数据存储至数据库。好在Zipkin支持将数据存储至:
- 内存(默认)
- MySQL
- Elasticsearch(国内推荐使用这个)
- Cassandra(国内很少人用)
Zipkin支持的这几种存储方式中,内存显然是不适用于生产的,这一点开始也说了。而使用MySQL的话,当数据量大时,查询较为缓慢,也不建议使用。Twitter官方使用的是Cassandra作为Zipkin的存储数据库,但国内大规模用Cassandra 的公司较少,而且Cassandra 相关文档也不多。
综上,故采用Elasticsearch是个比较好的选择,关于使用Elasticsearch 作为Zipkin 的存储数据库的官方文档如下:
zipkin-storage/elasticsearch
通过docker的方式
$ docker run -env STORAGE TYPE=elasticsearch -env ES_HOSTS=192.168.190.129:9200 openzipkin/zipkin-dependencies