美文网首页
Spring Cloud Netflix微服务开发(四) - S

Spring Cloud Netflix微服务开发(四) - S

作者: ElliotG | 来源:发表于2020-05-28 09:14 被阅读0次

0. 背景

传统的SOA架构体系中,系统调用层级不多,调用关系也不是很复杂,一旦出现问题,根据异常信息可以很快定位到问题模块并进行排查。
但是在微服务的世界里,实例数目成百上千,实例之间的调用关系呈网状结构,这个时候再靠人力去监控和排查问题几乎已经不太可能。
在这种情况下,一个完善的调用链路监控框架对于开发和运维来说,都是不可或缺的。

 

1. Spring Cloud Sleuth简介和原理

Spring Cloud Sleuth是Spring Cloud的分布式调用链解决方案,它从Google Dapper,Zipkin,HTrace中借鉴了很多思路。

Spring Cloud Sleuth常用术语:

  • Span(跨度)
    基本工作单元
    比如发送一次RPC请求就是一个新的Span。
    Span通过一个64位的ID标识,还包含有描述,事件时间戳,标签,调用它的Span的ID,处理器ID(一般为IP地址)。

  • Trace(跟踪)
    一系列Span组成的树状结构。
    简而言之,就是一次业务调用

  • Annotation(标注)
    用来描述事件的实时状态。

事件有如下几种状态:
   cs: Client Sent 客户端发起请求,它表示一个Span的开始。
   sr: Server Received 服务方接收到请求并开始处理,sr - cs的时间就是网络延迟。
   ss: Server Sent 它表示服务方请求处理完成,并将响应数据返回给客户端。 ss - sr的时间就是服务方处理时间。
   cr: Client Received 它表示客户端收到服务方的返回值,是当前Span结束的信号。cr - cs的时间就是一次请求的完整处理时间。

Sleuth原理介绍:

Sleuth通过Trace定义一次业务调用链。通过它的信息,我们能知道有多少个系统参与了该业务处理。
而系统间的调用顺序和时间戳信息,则是通过Span来记录的。
Trace和Span的信息经过整合,就能知道该业务的完整调用链。

 

2. 实战

下面我们通过一个简单的案例来演示Sleuth是如何使用的。

我们先新建一个pom项目simple-sleuth-demo
给它添加如下maven配置:

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>14</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.7.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <!-- 引入spring cloud的依赖 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
        </plugins>
    </build>

然后我们先创建一个子模块sleuth-consumer,为jar类型
再创建一个子模块sleuth-provider,为jar类型
项目结构如下:


项目结构图

接下去便构建consumer相关的代码

application.properties

server.port=8082
spring.application.name=sleuth-consumer

SleuthConsumerApplication.java

@SpringBootApplication
@EnableFeignClients
public class SleuthConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SleuthConsumerApplication.class, args);
    }
}

由于我们一会儿要用到Feign,因此添加了@EnableFeignClients注解。

接着,我们新建一个config包,在包下面新建一个配置文件类
ConsumerConfiguration.java

@Configuration
public class ConsumerConfiguration {
    @Autowired
    private BeanFactory beanFactory;

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public ExecutorService executorService() {
        // 简单起见, 我们注册固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        return new TraceableExecutorService(this.beanFactory, executorService);
    }
}

上面代码可以看到,我们私用了BeanFactory和一个固定大小的线程池。

接着,我们新建一个service包,并在该包下面定义Feign接口

@FeignClient(name = "sleuth-provider", url = "localhost:8083")
public interface HelloService {
    @RequestMapping("/sayHello")
    String sayHello(@RequestParam("name") String name);
}

然后,我们再新建一个controller包,并在该包下添加controller
ConsumerController.java

@RestController
public class ConsumerController {
    private static final Logger log = LoggerFactory.getLogger(ConsumerController.class);
    @Autowired
    private HelloService helloService;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ExecutorService executorService;

    @GetMapping("/helloByFeign")
    public String helloByFeign(String name) {
        log.info("client sent. Feign 方式, 参数: {}", name);

        String result = helloService.sayHello(name);

        log.info("client received. Feign 方式, 结果: {}", result);
        return result;
    }

    @GetMapping("/helloByRestTemplate")
    public String helloByRestTemplate(String name) {
        log.info("client sent. RestTemplate方式, 参数: {}", name);

        String url = "http://localhost:8083/sayHello?name=" + name;
        String result = restTemplate.getForObject(url, String.class);

        log.info("client received. RestTemplate方式, 结果: {}", result);
        return result;
    }

    @GetMapping("/helloByNewThread")
    public String hello(String name) throws ExecutionException, InterruptedException {
        log.info("client sent. 子线程方式, 参数: {}", name);

        Future future = executorService.submit(() -> {
            log.info("client sent. 进入子线程, 参数: {}", name);
            String result = helloService.sayHello(name);
            return result;
        });
        String result = (String) future.get();
        log.info("client received. 返回主线程, 结果: {}", result);
        return result;
    }
}

#########################################################
sleuth-provider项目代码

application.properties

server.port=8083
spring.application.name=sleuth-provider

SleuthProviderApplication.java

@SpringBootApplication
public class SleuthProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(SleuthProviderApplication.class, args);
    }
}

ProviderController.java

@RestController
public class ProviderController {
    private static final Logger log = LoggerFactory.getLogger(ProviderController.class);

    @GetMapping("/sayHello")
    public String hello(String name) {
        log.info("server received. 参数: {}", name);
        String result = "hello, " + name;
        log.info("server sent. 结果: {}", result);
        return result;
    }
}

#########################################################

  1. 启动sleuth-provider
  2. 启动sleuth-consumer
  3. 浏览器输入: http://localhost:8082/helloByFeign?name=kg

consumer端显示:


consumer log

provider端显示:


provider log

可以看到,引入了spring-cloud-sleuth之后,我们的日志组件可以自动打印Span信息。

好了,一个基本Spring Cloud Sleuth的实例就完成了。

相关文章

网友评论

      本文标题:Spring Cloud Netflix微服务开发(四) - S

      本文链接:https://www.haomeiwen.com/subject/pbcoahtx.html