在一个微服务系统架构中,一个完整的请求可能涉及到多个微服务的调用,这个调用形成一个链路。
比如,下单的请求,需要经过网关去调用业务服务,业务服务去调用订单服务,而订单服务同步调用商品服务和用户服务,用户服务又去调用积分服务:
业务要求整个下单的请求要在 1s 内完成,测试发现请求下单接口耗时超过 2s ,这时我们就需要去定位发现是调用链路上的哪个环节耗时异常,进而去解决问题。
Spring Cloud就有这样一个组件专门做链路追踪,那就是 Spring Cloud Sleuth ,有如下功能:
这里提到的另一个组件 Zipkin 是一个能够收集所有服务监控数据的跟踪系统。有了 Zipkin 我们可以直观的查看调用链路,并且可以方便的看出服务之间的调用关系以及调用耗时。
Spring Cloud Sleuth和 Zipkin 的使用非常简单,官网上有很详细的文档:
Sleuth: spring.io/projects/sp…
Zipkin: zipkin.io/pages/quick…
下面我们来实操一下。
我们以开篇举的例子来搭建这样一个环境:
还是以本 Spring Cloud Alibaba 系列文章的代码 SpringCloudAlibabaDemo 为例,目前已有 gatwway-service
, order-service
和 user-service
,我们再创建两个微服务项目 product-service
和 loyalty-service
,并形成一个调用链路。
完整代码仓库: github.com/ChenDapengJ… 。
为了展示,这里贴出了调用逻辑上的关键代码。
product-service
查询商品信息:
@RestController @RequestMapping("/product") public class ProductController { @GetMapping("/price/{id}") public BigDecimal getPrice(@PathVariable("id") Long id) { if (id == 1) { return new BigDecimal("5899"); } return new BigDecimal("5999"); } } 复制代码
loyalty-service
积分服务中获取用户积分和增加积分的 API :
@RestController @Slf4j public class LoyaltyController { /** * 获取用户当前积分 * @param id 用户id */ @GetMapping("/score/{id}") public Integer getScore(@PathVariable("id") Long id) { log.info("获取用户 id={} 当前积分", id); return 1800; } /** * 为当前用户增加积分 * @param id 用户id * @param lastScore 用户当前积分 * @param addScore 要增加的积分 */ @GetMapping("/addScore") public Integer addScore(@RequestParam(value = "id") Long id, @RequestParam(value = "lastScore") Integer lastScore, @RequestParam(value = "addScore") Integer addScore) { log.info("用户 id={} 增加 {} 积分", id, addScore); return lastScore + addScore; } } 复制代码
user-service
通过 OpenFeign 调用积分服务:
FeignClient 类:
@Service @FeignClient("loyalty-service") public interface LoyaltyService { @GetMapping("/score/{id}") Integer getScore(@PathVariable("id") Long id); @GetMapping("/addScore") Integer addScore(@RequestParam(value = "id") Long id, @RequestParam(value = "lastScore") Integer lastScore, @RequestParam(value = "addScore") Integer addScore); } 复制代码
Controller 调用:
@Slf4j @RestController @RequestMapping("/user") public class UserController { private LoyaltyService loyaltyService; @GetMapping("/score/{id}") public Integer getScore(@PathVariable("id") Long id) { return loyaltyService.getScore(id); } @GetMapping("/addScore") public Integer addScore(@RequestParam Long id, @RequestParam Integer lastScore, @RequestParam Integer addScore) { return loyaltyService.addScore(id, lastScore, addScore); } @Autowired public void setLoyaltyService(LoyaltyService loyaltyService) { this.loyaltyService = loyaltyService; } } 复制代码
order-service
订单服务通过 OpenFeign 调用 user-service
和 product-service
:
FeignClient 类 :
@Service @FeignClient("product-service") public interface ProductService { BigDecimal getPrice(@PathVariable("id") Long id); } 复制代码
@Service @FeignClient("user-service") public interface UserService { /** * 由于 user-service 使用了统一返回结果,所以此处的返回值是 ResponseResult * @param id 用户id * @return ResponseResult<Integer> */ @GetMapping("/user/score/{id}") ResponseResult<Integer> getScore(@PathVariable("id") Long id); /** * 由于 user-service 使用了统一返回结果,所以此处的返回值是 ResponseResult */ @GetMapping("/user/addScore") ResponseResult<Integer> addScore(@RequestParam(value = "id") Long id, @RequestParam(value = "lastScore") Integer lastScore, @RequestParam(value = "addScore") Integer addScore); } 复制代码
Controller 调用 :
@Slf4j @RestController @RequestMapping("/order") public class OrderController { private UserService userService; private ProductService productService; @GetMapping("/create") public String createOrder(@RequestParam("userId") Long userId, @RequestParam("productId") Long productId) { log.info("创建订单参数,userId={}, productId={}", userId, productId); // 商品服务-获取价格 BigDecimal price = productService.getPrice(productId); log.info("获得 price={}", price); // 用户服务-查询当前积分,增加积分 Integer currentScore = userService.getScore(userId).getData(); log.info("获得 currentScore={}", price); // 增加积分 Integer addScore = price.intValue(); Integer finalScore = userService.addScore(userId, currentScore, addScore).getData(); log.info("下单成功,用户 id={} 最终积分:{}", userId, finalScore); return "下单成功,用户 id=" + userId + " 最终积分:" + finalScore; } @Autowired public void setUserService(UserService userService) { this.userService = userService; } @Autowired public void setProductService(ProductService productService) { this.productService = productService; } } 复制代码
网关 gateway-service
配置 Nacos 注册中心和路由:
spring: cloud: nacos: discovery: server-addr: 192.168.242.112:81 gateway: routes: - id: order-service uri: lb://order-service predicates: - Path=/order/** 复制代码
启动网关以及其他四个服务,
然后可以在 Nacos 中看到注册进来的实例:
所有服务启动成功之后,通过网关调用下单 API :
整个调用链路没有问题。
要想使用 Sleuth ,只需简单几个操作即可。
除了 gateway-service
网关服务,其他四个服务均执行以下步骤:
1,导入 spring-cloud-starter-sleuth
依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> 复制代码
2, org.springframework.web.servlet.DispatcherServlet
日志级别调整为 DEBUG :
logging: level: org.springframework.web.servlet.DispatcherServlet: DEBUG 复制代码
然后重启这四个服务,再次通过网关访问下单 API ,看到每个服务都打印了这样的日志:
user-service :
product-service :
loyalty-service :
order-service :
这样形式的日志:
# [服务名,总链路ID,子链路ID] [order-service,5eda5d7bdcca0118,5eda5d7bdcca0118] 复制代码
就是整体的一个调用链路信息。
简单来说, Zipkin 是用来图形化展示 Sleuth 收集来的信息的。
Zipkin需要单独安装,它是一个 Java 编写的 Web 项目,我们使用 Docker Compose 进行部署安装 Zipkin 。
Tip:我已经非常体贴的把 Docker Compose 的使用分享了,详见: 用 docker-compose 部署服务真是好用,根本停不下来! 。
部署步骤:
1,创建 /usr/local/zipkin
目录,进入到该目录:
mkdir /usr/local/zipkin cd /usr/local/zipkin 复制代码
2,创建 docker-compose.yml
文件,文件内容如下:
version: "3" services: zipkin: image: openzipkin/zipkin restart: always container_name: zipkin ports: - 9411:9411 复制代码
这是简化版的 docker-compose.yml
配置文件,这样的配置就能启动 Zipkin 。更多的配置详见: github.com/openzipkin-… 。
3,使用 docker-compose up -d
命令( -d
表示后台启动)启动:
部署成功后,访问 Zipkin ,端口为 9411 ,访问地址: http://192.168.242.112:9411/zipkin/
这样,一个 Zipkin 服务就部署完成了。
首先,还是需要在微服务项目中导入 spring-cloud-sleuth-zipkin 的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-sleuth-zipkin</artifactId> </dependency> 复制代码
然后,增加一些配置,让 Sleuth 收集的信息发送到 Zipkin 服务上:
spring: cloud: nacos: discovery: server-addr: 192.168.242.112:81 sleuth: enabled: true sampler: # 设置 Sleuth 收集信息的百分比,一般情况下,10%就够用了,这里设置100%观察 rate: 100 zipkin: sender: type: web base-url: http://192.168.242.112:9411/ 复制代码
好了,再来启动每个服务,然后访问下单接口再看下 Zipkin 的面板。访问 http://192.168.242.112:9411/zipkin/
:
可以看到有一个请求出来了,点击 SHOW
查看详情:
可以清楚地看到调用链路上每一步的耗时。
Spring Cloud Sleuth结合 Zipkin 可以对每个微服务进行链路追踪,从而帮助我们分析服务间调用关系以及调用耗费的时间。
本文只简单介绍了通过 web 方式(配置项: spring.zipkin.sender.type=web
):
也就是通过 HTTP 的方式发送数据到 Zipkin ,如果请求量比较大,这种方式其实性能是比较低的,一般情况下我们都是通过消息中间件来发送,比如 RabbitMQ 。
如果日志数据量比较大,一般推荐拥有更高吞吐量的 Kafka 来进行日志推送。
这种方式就是让服务将 Sleuth 收集的日志推给 MQ ,让 Zipkin 去监控 MQ 的信息,通过 MQ 的队列获取到服务的信息。这样就提高了性能。
而日志的存储则可以采用 Elasticsearch 对数据进行持久化,这样可以保证 Zipkin 重启后,链路信息不会丢失。
下次有机会再分享一下如何通过 MQ 发送调用链数据信息以及使用 Elasticsearch 持久化数据,今天就到这里了。
文章浏览阅读2.9k次,点赞8次,收藏14次。测试主要做什么?这完全都体现在测试流程中,同时测试流程是面试问题中出现频率最高的,这不仅是因为测试流程很重要,而是在面试过程中这短短的半小时到一个小时的时间,通过测试流程就可以判断出应聘者是否合适,故在测试流程中包含了测试工作的核心内容,例如需求分析,测试用例的设计,测试执行,缺陷等重要的过程。..._测试过程管理中包含哪些过程
文章浏览阅读870次,点赞16次,收藏19次。1.背景介绍政府数字化政务是指政府利用数字技术、互联网、大数据、人工智能等新技术手段,对政府政务进行数字化改革,提高政府工作效率,提升政府服务质量的过程。随着人工智能(AI)和机器学习(ML)技术的快速发展,政府数字化政务中的人工智能与机器学习应用也逐渐成为政府改革的重要内容。政府数字化政务的人工智能与机器学习应用涉及多个领域,包括政策决策、政府服务、公共安全、社会治理等。在这些领域,人工...
文章浏览阅读219次,点赞2次,收藏4次。系统主要的用户为用户、管理员,他们的具体权限如下:用户:用户登录后可以对管理员上传的学习视频进行学习。用户可以选择题型进行练习。用户选择小程序提供的考研科目进行相关训练。用户可以进行水平测试,并且查看相关成绩用户可以进行错题集的整理管理员:管理员登录后可管理个人基本信息管理员登录后可管理个人基本信息管理员可以上传、发布考研的相关例题及其分析,并对题型进行管理管理员可以进行查看、搜索考研题目及错题情况。_mysql刷题软件
文章浏览阅读1.4k次。myelipse里有UML1和UML2两种方式,UML2功能更强大,但是两者生成过程差别不大1.建立Test工程,如下图,uml包存放uml类图package com.zz.domain;public class User {private int id;private String name;public int getId() {return id;}public void setId(int..._根据以下java代码画出类图
文章浏览阅读174次。需求:一个topic包含很多个表信息,需要自动根据json字符串中的字段来写入到hive不同的表对应的路径中。发送到Kafka中的数据原本最外层原本没有pkDay和project,只有data和name。因为担心data里面会空值,所以根同事商量,让他们在最外层添加了project和pkDay字段。pkDay字段用于表的自动分区,proejct和name合起来用于自动拼接hive表的名称为 ..._flume拦截器自定义开发 kafka
文章浏览阅读380次。原标题:Java Spring中同时访问多种不同数据库 多样的工作要求,可以使用不同的工作方法,只要能获得结果,就不会徒劳。开发企业应用时我们常常遇到要同时访问多种不同数据库的问题,有时是必须把数据归档到某种数据仓库中,有时是要把数据变更推送到第三方数据库中。使用Spring框架时,使用单一数据库是非常容易的,但如果要同时访问多个数据库的话事件就变得复杂多了。本文以在Spring框架下开发一个Sp..._根据输入的不同连接不同的数据库
文章浏览阅读3.6k次,点赞9次,收藏25次。本案例描述了晶振屏蔽以及开关电源变压器屏蔽对系统稳定工作的影响, 硬件设计时应考虑。_eft电路图
文章浏览阅读1.1k次。对于物料价格的更改,可以采取不同的手段:首先,我们来介绍MR21的方式。 需要说明的是,如果要对某一产品进行价格修改,必须满足的前提条件是: ■ 1、必须对价格生效的物料期间与对应会计期间进行开启; ■ 2、该产品在该物料期间未发生物料移动。执行MR21,例如更改物料1180051689的价格为20000元,系统提示“对于物料1180051689 存在一个当前或未来标准价格”,这是因为已经对该..._mr21 对于物料 zba89121 存在一个当前或未来标准价格
文章浏览阅读7.4k次,点赞3次,收藏13次。[文章导读]联想启天M420是一款商用台式电脑,预装的是win10系统,用户还是喜欢win7系统,该台式机采用的intel 8代i5 8500CPU,在安装安装win7时有很多问题,在安装win7时要在BIOS中“关闭安全启动”和“开启兼容模式”,并且安装过程中usb不能使用,要采用联想win7新机型安装,且默认采用的uefi+gpt模式,要改成legacy+mbr引导,那么联想启天M420台式电..._启天m420刷bios
文章浏览阅读2.7k次,点赞2次,收藏9次。一,为什么要冗余数据互联网数据量很大的业务场景,往往数据库需要进行水平切分来降低单库数据量。水平切分会有一个patition key,通过patition key的查询能..._保证冗余性
文章浏览阅读88次。是时候闭环Java应用了 原创 2016-08-16 张开涛 你曾经因为部署/上线而痛苦吗?你曾经因为要去运维那改配置而烦恼吗?在我接触过的一些部署/上线方式中,曾碰到过以下一些问题:1、程序代码和依赖都是人工上传到服务器,不是通过工具进行部署和发布;2、目录结构没有规范,jar启动时通过-classpath任意指定;3、fat jar,把程序代码、配置文件和依赖jar都打包到一个jar中,改配置..._那么需要把上面的defaultjavatyperesolver类打包到插件中
文章浏览阅读909次。1.得下载一个番茄插件,按alt+g才可以有函数跳转功能。2.不安装番茄插件,按F12也可以有跳转功能。3.进公司的VS工程是D:\sync\build\win路径,.sln才是打开工程的方式,一个是VS2005打开的,一个是VS2013打开的。4.公司库里的线程接口,在CmThreadManager.h 里,这个里面是我们的线程库,可以直接拿来用。CreateUserTaskThre..._番茄助手颜色