Loading...

📋 框架知识:SpringCloud

服务的注册与发现(Eureka)

SpringCloud 简介

以下简介来自SpringCloud官网(https://spring.io/projects/spring-cloud)

Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式环境中运行良好,包括开发人员自己的笔记本电脑,裸机数据中心,以及Cloud Foundry等托管平台。

quote from https://www.springcloud.cc/spring-cloud-dalston.html

Spring Cloud provides tools for developers to quickly build some of the common patterns in distributed systems (e.g. configuration management, service discovery, circuit breakers, intelligent routing, micro-proxy, control bus, one-time tokens, global locks, leadership election, distributed sessions, cluster state). Coordination of distributed systems leads to boiler plate patterns, and using Spring Cloud developers can quickly stand up services and applications that implement those patterns. They will work well in any distributed environment, including the developer’s own laptop, bare metal data centres, and managed platforms such as Cloud Foundry.

quote from https://spring.io/projects/spring-cloud

Eureka Server

ps. 学习完 Eureka 之后,可以再了解一下 Consul。

  1. 创建 maven 主工程

    你可以选择从https://start.spring.io/下载初始化项目。

    也可以new >> project >> Spring Initializr进行初始化项目。

  2. 创建 Eureka Server 注册服务中心

    创建module并选择Eureka Server依赖。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>

    在启动类中添加注解 @EnableEurekaServer

    package com.xfc.eureka.server;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
    
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaServerApplication.class, args);
        }
    
    }
  3. 配置文件 application.yml

    server:
      port: 8761
    
    # 通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server.
    eureka:
      instance:
        hostname: localhost
      client:
        registerWithEureka: false
        fetchRegistry: false
        serviceUrl:
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    
    spring:
      application:
        name: eurka-server
  4. 启动注册服务中心

    启动主类并访问 http://localhost:8761/

Eureka Client

  1. 创建module并选择Eureka Discovery Client依赖。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  2. 在启动类中添加注解 @EnableEurekaClient

    package com.xfc.eureka.client;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    
    @SpringBootApplication
    @EnableEurekaClient
    public class EurekaClientApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaClientApplication.class, args);
        }
    
    }
  3. 配置文件 application.yml

    server:
      port: 8762
    
    # 指定当前服务名称
    spring:
      application:
        name: eureka-client-1
    
    # 指定eureka-server
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
  4. 启动 Eureka Client

    注意:启动 Eureka Client 时应当保证 Eureka Client也处于启动状态。

    启动主类并访问 http://localhost:8761/ 进行查看。

    关于 Eureka 错误提示信息,请参考:EMERGENCYI EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’ RE NOT. RENEWALS ARE LESSE

    Instances currently registered with Eureka 列表中查看已注册的Eureka服务实例。

至此,一个 Eureka 服务即注册成功,同时注册多个 Eureka 服务,与 Eureka Client 方式相同,只须注意端口冲突即可。

服务消费者(rest+ribbon)

在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。

ribbon简介

Ribbon is a client side IPC library that is battle-tested in cloud. It provides the following features

  • Load balancing

  • Fault tolerance

  • Multiple protocol (HTTP, TCP, UDP) support in an asynchronous and reactive model

  • Caching and batching

    ---- 摘自https://github.com/Netflix/ribbon

Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。

---- 摘自https://www.jianshu.com/p/1bd66db5dc46

Ribbon 服务消费者示例

  1. 准备工作

    根据 Eureka 章节相关知识,同时创建Eureka Server和两个Eureka Client。

    注意:除端口不同外,两个Eureka Client的代码完全相同,以不同端口来模拟负载均衡。

  2. 创建服务消费者

    新建一个module,并添加ribbon及eureka client依赖。

    <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-ribbon</artifactId>
    </dependency>

    编辑配置文件

    server:
      port: 8764
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    
    spring:
      application:
        name: service-ribbon

    配置启动类

    在启动类中添加 @EnableEurekaClient@EnableDiscoveryClient 注解,并注入一个开启负载均衡的RESTFul模板。

    package com.xfc.service.ribbon;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient
    public class ServiceRibbonApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceRibbonApplication.class, args);
        }
    
        @Bean
        @LoadBalanced// 开启负载均衡
        RestTemplate restTemplate() {
            return new RestTemplate();
        }
    }

    在EurekaClient的服务中编写接口

    这里直接在两个EurekaClient的启动类中编写RESTFul风格的接口即可,即修改启动类,如下:

    package com.xfc.eureka.client;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    @EnableEurekaClient
    @RestController
    public class EurekaClientApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaClient2Application.class, args);
        }
    
        @Value("${server.port}")
        String port;
    
        @RequestMapping("/test")
        public String test(@RequestParam(value = "name", defaultValue = "ErDong") String name) {
            return "hi " + name + " ,this test api is from port: " + port;
        }
    
    }
  3. 在ribbon模块中添加测试

    TestService.java

    package com.xfc.service.ribbon.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 0027 12:46
     * @Description:
     */
    @Service
    public class TestService {
    
        @Autowired
        RestTemplate restTemplate;
    
        public String test(String name) {
            return restTemplate.getForObject("http://EUREKA-CLIENT/test?name=" + name, String.class);
        }
    
    }

    TestController.java

    package com.xfc.service.ribbon.controller;
    
    import com.xfc.service.ribbon.service.TestService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 0027 12:56
     * @Description:
     */
    @RestController
    public class TestController {
    
        @Autowired
        TestService testService;
    
        @GetMapping(value = "/test")
        public String test(@RequestParam String name) {
            return testService.test(name);
        }
    }
  4. 启动服务及测试负载均衡

    分别启动EurekaServer,两个EurekaClient及ServiceRibbon。

    访问 http://localhost:8761/ 已注册的服务实例。

    可以看到端口分别为 87628763 两个 EUREKA-CLIENT 实例,及一个端口为 8764EUREKA-CLIENT 实例。

    负载均衡测试

    多次访问 http://localhost:8764/test?name=testUser

    可以看到相应结果如下:

    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    ……

    测试结论

    从测试结果我们可以看到,端口分别为8762何8763的两个 EUREKA-CLIENT 实例被轮流调用,即实现了负载均衡。

服务消费者(Feign)

在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。

Feign简介

Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign’s first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.

---- 摘自https://github.com/OpenFeign/feign

Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。

简而言之:

  • Feign 采用的是基于接口的注解
  • Feign 整合了ribbon,具有负载均衡的能力
  • 整合了Hystrix,具有熔断的能力

---- 摘自https://blog.csdn.net/forezp/article/details/81040965

  1. 准备工作

    根据 Eureka 章节相关知识,同时创建Eureka Server和两个Eureka Client。

    注意:除端口不同外,两个Eureka Client的代码完全相同,以不同端口来模拟负载均衡。

  2. 创建服务消费者

    新建一个module,并添加feign及eureka client依赖。

    <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-openfeign</artifactId>
    </dependency>

    配置文件

    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    server:
      port: 8765
    spring:
      application:
        name: service-feign

    配置启动类

    在启动类中添加 @EnableEurekaClient @EnableFeignClients@EnableDiscoveryClient 注解开启Feign的功能。

    package com.xfc.service.feign;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient
    @EnableFeignClients
    public class ServiceFeignApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceFeignApplication.class, args);
        }
    
    }

    定义一个 Feign 接口

    这里直接在两个EurekaClient的启动类中编写RESTFul风格的接口即可,即修改启动类,如下:

    package com.xfc.eureka.client;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    @EnableEurekaClient
    @RestController
    public class EurekaClientApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaClient2Application.class, args);
        }
    
        @Value("${server.port}")
        String port;
    
        @RequestMapping("/test")
        public String test(@RequestParam(value = "name", defaultValue = "ErDong") String name) {
            return "hi " + name + " ,this test api is from port: " + port;
        }
    
    }
  3. 在feign模块中添加测试

    TestService.java

    package com.xfc.service.feign.service;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 0027 14:38
     * @Description:
     */
    @FeignClient(value = "EUREKA-CLIENT")// 指定服务名称
    public interface TestService {
    
        // 指定服务方法及参数
        @RequestMapping(value = "/test",method = RequestMethod.GET)
        String test(@RequestParam(value = "name") String name);
    
    }

    TestController.java

    package com.xfc.service.feign.controller;
    
    import com.xfc.service.feign.service.TestService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 0027 14:40
     * @Description:
     */
    @RestController
    public class TestController {
    
        @Autowired
        TestService testService;
    
        @GetMapping(value = "/test")
        public String test(@RequestParam String name) {
            return testService.test(name);
        }
    }
  4. 启动服务及测试负载均衡

    分别启动EurekaServer,两个EurekaClient及ServiceFeign。

    访问 http://localhost:8761/ 已注册的服务实例。

    可以看到端口分别为 87628763 两个 EUREKA-CLIENT 实例,及一个端口为 8765SERVICE-FEIGN 实例。

    负载均衡测试

    多次访问 http://localhost:8765/test?name=testUser 可以看到相应结果如下:

    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    ……

    测试结论

    从测试结果我们可以看到,端口分别为8762何8763的两个 EUREKA-CLIENT 实例被轮流调用,即实现了负载均衡。

熔断器(Hystrix)

在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。为了解决这个问题,业界提出了熔断器(断路器)模型。

熔断器简介

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

---- 摘自https://github.com/Netflix/hystrix

Hystrix 中文介绍可参考 https://www.jianshu.com/p/76dc45523807

Hystrix 熔断器示例

  1. 准备工作

    根据 Eureka 章节相关知识,同时创建Eureka Server和两个Eureka Client。

    注意:除端口不同外,两个Eureka Client的代码完全相同,以不同端口来模拟负载均衡。

  2. 在 Ribbon 中使用断路器

    注意: 此部分内容基于springcloud002文档。

    serice-ribbon 模块添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

    配置启动类

    在启动类中添加 @EnableHystrix 注解开启Hystrix功能。

    package com.xfc.service.ribbon;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.client.loadbalancer.LoadBalanced;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.client.RestTemplate;
    
    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient
    @EnableHystrix
    public class ServiceRibbonApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceRibbonApplication.class, args);
        }
    
        @Bean
        @LoadBalanced// 开启负载均衡
        RestTemplate restTemplate() {
            return new RestTemplate();
        }
    
    }

    改写TestService

    package com.xfc.service.ribbon.service;
    
    import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 0027 12:46
     * @Description:
     */
    @Service
    public class TestService {
    
        @Autowired
        RestTemplate restTemplate;
    
        // 对当前方法创建熔断器的功能,并指定fallbackMethod熔断方法
        @HystrixCommand(fallbackMethod = "testError")
        public String test(String name) {
            return restTemplate.getForObject("http://EUREKA-CLIENT/test?name=" + name, String.class);
        }
    
        // fallbackMethod熔断方法
        public String testError(String name) {
            return "hi,"+name+",sorry,error!";
        }
    
    }

    启动服务及测试熔断器功能

    分别启动EurekaServer,两个EurekaClient及ServiceRibbon。

    访问 http://localhost:8761/ 已注册的服务实例。

    可以看到端口分别为 87628763 两个 EUREKA-CLIENT 实例,及一个端口为 8764SERVICE-FEIGN 实例。

    熔断器功能测试

    多次访问 http://localhost:8764/test?name=testUser

    可以看到相应结果如下:

    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    ……

    此时断开端口为 8763EUREKA-CLIENT 实例。

    再次进行多次访问,结果如下:

    hi,testUser,sorry,error!
    hi testUser ,this test api is from port: 8762
    hi,testUser,sorry,error!
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8762
    ……

    此时再断开端口为 8762EUREKA-CLIENT 实例。

    再次进行多次访问,结果如下:

    hi,testUser,sorry,error!
    hi,testUser,sorry,error!
    hi,testUser,sorry,error!
    hi,testUser,sorry,error!
    ……

    测试结论

    从测试结果我们可以看到,端口分别为8762何8763的两个 EUREKA-CLIENT 实例在正常启用时,正常实现负载均衡,当其中某个服务实例出现故障时,客户端会进入fallbackMethod指定的熔断方法,直接返回一组字符串,而不是等待响应超时,这很好的控制了容器的线程阻塞。

  3. 在 Feign 中使用断路器

    Feign 是自带断路器的,在D版本的 Spring Cloud 之后,默认关闭。

    serice-feign 模块添加依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

    编辑配置文件,向配置文件中添加熔断器配置:

    # feign开启hystrix支持
    feign:
      hystrix:
        enabled: true

    根据 TestService 创建 TestService 接口的实现类TestServiceHystric.java作为熔断器。

    package com.xfc.service.feign.service.hystric;
    
    import com.xfc.service.feign.service.TestService;
    import org.springframework.stereotype.Component;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 0027 15:39
     * @Description:
     */
    @Component
    public class TestServiceHystric implements TestService {
    
        @Override
        public String test(String name) {
            return "hi,"+name+",sorry,error!";
        }
    
    }

    在 @FeignClient 注解中指定fallback

    package com.xfc.service.feign.service;
    
    import com.xfc.service.feign.service.hystric.TestServiceHystric;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 0027 14:38
     * @Description:
     */
    @FeignClient(value = "EUREKA-CLIENT", fallback = TestServiceHystric.class)// 指定服务名称,指定熔断器
    public interface TestService {
    
        // 指定服务方法及参数
        @RequestMapping(value = "/test", method = RequestMethod.GET)
        String test(@RequestParam(value = "name") String name);
    
    }

    启动服务及测试熔断器功能

路由网关(zuul)

在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。

在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。

Zuul简介

Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.

---- 摘自https://github.com/Netflix/zuul/wiki

Hystrix 中文介绍可参考 SpringCloud教程 | 第五篇: 路由网关(zuul)

Zuul 路由网关示例

  1. 准备工作

    接续上一文档,在原有工程上添加新的模块。

  2. 创建zuul路由网关

    新建一个 module,并添加 zuul 及 eureka client 依赖。

    <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>

    编辑配置文件

    server:
      port: 8769
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    
    spring:
      application:
        name: service-zuul
    
    # 以/api-a/开头的请求转发至service-ribbon服务
    # 以/api-b/开头的请求转发至service-feign服务
    zuul:
      routes:
        api-a:
          path: /api-a/**
          serviceId: service-ribbon
        api-b:
          path: /api-b/**
          serviceId: service-feign

    配置启动类

    在启动类中添加 @EnableEurekaClient@EnableZuulProxy开启zuul的功能。

    package com.xfc.service.zuul;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    @SpringBootApplication
    @EnableZuulProxy
    @EnableEurekaClient
    @EnableDiscoveryClient
    public class ServiceZuulApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceZuulApplication.class, args);
        }
    
    }

    启动服务及测试路由网关

    分别启动EurekaServer,两个EurekaClient及ServiceRibbon。

    分别启动ServiceRibbon和ServiceFeign两个服务消费者。

    启动ServiceZuul网关。

    多次访问 http://localhost:8769/api-a/test?name=testUser 结果如下:

    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    ……

    多次访问 http://localhost:8769/api-b/test?name=testUser 结果如下:

    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    hi testUser ,this test api is from port: 8762
    hi testUser ,this test api is from port: 8763
    ……

    测试结论

    Zuul 起到路由的作用,它将不同请求分向不同的服务消费者进行处理。

  3. 服务过滤

    Zuul 不仅只是路由,并且还能过滤,做一些安全验证。

    添加过滤器(新建TokenFilter.java,用于过滤token)

    package com.xfc.service.zuul.filter;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @Auther: ErDong
     * @Email: xfc_exclave@163.com
     * @Date: 2019/12/27 21:29
     * @Description:
     */
    @Component
    public class TokenFilter extends ZuulFilter {
    
        private static Logger log = LoggerFactory.getLogger(TokenFilter.class);
    
    
        /**
         * filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,如下:
         * 1. pre:前置过滤器
         * 2. routing:路由之时
         * 3. post: 路由之后
         * 4. rror:发送错误调用
         *
         * @return
         */
        @Override
        public String filterType() {
            return "pre";
        }
    
        /**
         * 过滤的顺序,数字越大,优先级越低
         *
         * @return
         */
        @Override
        public int filterOrder() {
            return 0;
        }
    
        /**
         * 是否要进行过滤
         *
         * @return
         */
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        /**
         * 过滤器的具体逻辑
         *
         * @return
         */
        @Override
        public Object run() {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
            Object accessToken = request.getParameter("token");
            if (accessToken == null) {
                log.warn("token is empty");
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(401);
                try {
                    ctx.getResponse().getWriter().write("token is empty");
                } catch (Exception e) {
                }
                return null;
            }
            log.info("ok");
            return null;
        }
    }

    启动服务及测试路由过滤

    访问 http://localhost:8769/api-a/test?name=testUser 得到结果:token is empty

    访问 http://localhost:8769/api-b/test?name=testUser 得到结果:token is empty

    访问 http://localhost:8769/api-a/test?name=testUser&token=abc 得到结果:hi testUser ,this test api is from port: 8762

    访问 http://localhost:8769/api-b/test?name=testUser&token=abc 得到结果:hi testUser ,this test api is from port: 8763

    测试结论:Zuul 实现了服务过滤。

配置中心(Spring Cloud Config)

使用配置服务来保存各个服务的配置文件,即Spring Cloud Config。

Spring Cloud Config 简介

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
---- 摘自https://blog.csdn.net/forezp/article/details/81041028

Spring Cloud Config 为分布式系统中的外部配置提供服务器和客户端支持。使用 Config Server ,您可以在所有环境中管理应用程序的外部属性。客户端和服务器上的概念映射与 Spring EnvironmentPropertySource 抽象相同,因此它们与Spring应用程序非常契合,但可以与任何以任何语言运行的应用程序一起使用。随着应用程序通过从开发人员到测试和生产的部署流程,您可以管理这些环境之间的配置,并确定应用程序具有迁移时需要运行的一切。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。可以轻松添加替代实现,并使用Spring配置将其插入。

更多相关中文介绍,参考:Spring Cloud Config 中文文档

Spring Cloud Config 配置中心示例

构建 Config Server
  1. 创建模块并添加依赖

    新建一个module,并添加config server依赖。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>
  2. 编辑配置文件

    server:
      port: 8888
    
    # 如果Git仓库为公开仓库,可以不填写用户名和密码
    spring:
      application:
        name: config-server
      cloud:
        config:
          label: master # 配置仓库的分支
          server:
            git:
              uri: https://github.com/forezp/SpringcloudConfig/ # 配置git仓库地址
              searchPaths: respo # 配置仓库路径
              username: # 访问git仓库的用户名
              password: # 访问git仓库的用户密码
  3. 配置启动类

    在启动类中添加 @EnableConfigServer 注解开启配置服务器的功能。

    package com.xfc.config.server;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.config.server.EnableConfigServer;
    
    @SpringBootApplication
    @EnableConfigServer
    public class ConfigServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigServerApplication.class, args);
        }
    
    }
  4. 启动服务及测试ConfigServer

    启动服务后,访问 http://localhost:8888/config-client-dev.properties 得到如下结果:

    democonfigclient.message: hello spring io

    foo: foo version 21

    结论:配置服务中心可以从远程程序获取配置信息。

    http请求地址和资源文件映射如下:

    /{application}/{profiles:.[^-].}

    /{application}/{profiles}/{label:.*}

    /{application}-{profiles}.properties

    /{label}/{application}-{profiles}.properties

    {application}-{profiles}.json

    /{label}/{application}-{profiles}.json

    /{application}-{profiles}.yml 或 /{application}-{profiles}.yml

    /{label}/{application}-{profiles}.yml 或 /{label}/{application}-{profiles}.yml

构建Config Client
  1. 创建模块并添加依赖

    新建一个module,并添加config client依赖。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
  2. 编辑配置文件

    server:
      port: 8881
    
    spring:
      application:
        name: config-client
      cloud:
        config:
          label: master
          profile: dev # dev:开发环境,test:测试环境,pro:生产环境
          uri: http://localhost:8888/ # 指明配置服务中心的网址
  3. 创建测试API

    在启动类中添加API。

    package com.xfc.config.client;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    @RestController
    public class ConfigClientApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ConfigClientApplication.class, args);
        }
    
        @Value("${foo}")
        String foo;
    
        @RequestMapping(value = "/getFoo")
        public String getFoo() {
            return foo;
        }
    
    }

    启动并访问 http://localhost:8881/getFoo

    结果:foo version 21

    结论:config-client从config-server获取了foo的属性,而config-server是从git仓库读取的。

高可用的分布式配置中心
  1. 改造config-server

    添加eureka client依赖

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    修改配置文件

    server:
      port: 8888
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    
    # 如果Git仓库为公开仓库,可以不填写用户名和密码
    spring:
      application:
        name: config-server
      cloud:
        config:
          label: master # 配置仓库的分支
          server:
            git:
              uri: https://github.com/forezp/SpringcloudConfig/ # 配置git仓库地址
              searchPaths: respo # 配置仓库路径
              username: # 访问git仓库的用户名
              password: # 访问git仓库的用户密码

    主类添加 @EnableEurekaClient 注解

  2. 改造 config-client

    添加eureka client依赖

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    修改配置文件

    server:
      port: 8881
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    
    spring:
      application:
        name: config-client
      cloud:
        config:
          label: master
          profile: dev # dev:开发环境,test:测试环境,pro:生产环境
          discovery:
            enabled: true
            serviceId: config-server # 通过服务名指定配置服务中心(需要config-server和config-client都在服务注册中心注册)
    #      uri: http://localhost:8888/ # 通过网址指定配置服务中心

    主类添加 @EnableEurekaClient 注解

  3. 启动并测试

    依次启动 eureka-serverconfig-serverconfig-client

    访问 http://localhost:8761/

    结果:可以看到 config-serverconfig-client 均注册到服务注册中心。

    访问 http://localhost:8881/getFoo

    结果:foo version 21

    结论:config-serverconfig-client 可以同时作为EurekaClient注册到服务注册中心,最终实现高可用。

消息总线(Spring Cloud Bus)

Spring Cloud Bus 将分布式的节点用轻量的消息代理连接起来。它可以用于广播配置文件的更改或者服务之间的通讯,也可以用于监控。本文要讲述的是用Spring Cloud Bus实现通知微服务架构的配置文件的更改。

  1. 准备工作

    下载安装 Erlang 及 RabbitMQ。

  2. 改造config-client模块

    添加 bus-ampq 依赖。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bus-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    编辑配置文件

    server:
      port: 8881
    
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
    
    management:
      endpoints:
        web:
          exposure:
            include: bus-refresh
    
    spring:
      application:
        name: config-client
      cloud:
        config:
          label: master
          profile: dev # dev:开发环境,test:测试环境,pro:生产环境
          discovery:
            enabled: true
            serviceId: config-server # 通过服务名指定配置服务中心(需要config-server和config-client都在服务注册中心注册)
    #      uri: http://localhost:8888/ # 通过网址指定配置服务中心
        bus:
          enabled: true
          trace:
            enabled: true
      rabbitmq:
        host: localhost
        password: guest
        port: 5672
        username: guest

    在启动类中添加类注解 @RefreshScope

  3. 启动服务及测试

    复制一份 config-client ,端口改为8882。

    依次启动 eureka-serverconfig-server 及端口为8881和8882的两个 config-client

    访问 http://localhost:8881/getFoo

    访问 http://localhost:8882/getFoo

    返回结果均如下:

    foo version 3

    此时前往配置中心仓库修改foo配置为 foo version 22

    访问getFoo,返回结果仍为 foo version 3

    使用http工具发送POST请求

    http://localhost:8881/actuator/bus-refresh

    查看 config-server 控制台,显示已从配置中心仓库中获取到最新的配置文件。

    再次访问getFoo,返回结果如下:

    foo version 22

    结论:

    当git文件更改的时候,通过pc端用post 向端口为8882的config-client发送请求/bus/refresh/;此时8882端口会发送一个消息,由消息总线向其他服务传递,从而使整个微服务集群都达到更新配置文件。

服务链路追踪(Spring Cloud Sleuth)

Spring Cloud Sleuth 简介

Add sleuth to the classpath of a Spring Boot application (see below for Maven and Gradle examples), and you will see the correlation data being collected in logs, as long as you are logging requests.

quote from https://github.com/spring-cloud/spring-cloud-sleuth

Spring Cloud Sleuth 主要功能就是在分布式系统中提供追踪解决方案,并且兼容支持了 zipkin,你只需要在pom文件中引入相应的依赖即可。

构建server-zipkin

  1. 下载 zipkin

    下载地址:https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/

  2. 运行

    java -jar D:\jar\zipkin\zipkin-server-2.10.1-exec.jar
  3. 访问

    zipkin 默认端口为 9411

    浏览器访问:http://localhost:9411

创建测试模块

  1. 创建模块并添加依赖

    创建模块 service-zipkin-test1service-zipkin-test2,并添加 zipkin-client 依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zipkin</artifactId>
    </dependency>
  2. 编辑配置文件

    server:
      port: 8989
    spring:
      application:
        name: service-zipkin-test2
      zipkin:
        base-url: http://localhost:9411
        sleuth:
          sampler:
            probability: 1.0
  3. 修改启动类

    模块 service-zipkin-test1 启动类:

    package com.xfc.service.zipkin.test1;
    
    import brave.sampler.Sampler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    @SpringBootApplication
    @RestController
    public class ServiceZipkinTest1Application {
    
        private static final Logger LOG = Logger.getLogger(ServiceZipkinTest1Application.class.getName());
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceZipkinTest1Application.class, args);
        }
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Bean
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
    
        @RequestMapping("/test1")
        public String callTest2() {
            LOG.log(Level.INFO, "interface of test1 application");
            return restTemplate.getForObject("http://localhost:8989/test2", String.class);
        }
    
        @RequestMapping("/info")
        public String info() {
            LOG.log(Level.INFO, "application info of test1");
            return "application info of test1";
    
        }
    
        @Bean
        public Sampler defaultSampler() {
            return Sampler.ALWAYS_SAMPLE;
        }
    
    }

    模块 service-zipkin-test2 启动类:

    package com.xfc.service.zipkin.test2;
    
    import brave.sampler.Sampler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    @SpringBootApplication
    @RestController
    public class ServiceZipkinTest2Application {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceZipkinTest2Application.class, args);
        }
    
        private static final Logger LOG = Logger.getLogger(ServiceZipkinTest2Application.class.getName());
    
        @Autowired
        private RestTemplate restTemplate;
    
        @RequestMapping("/test2")
        public String callTest1Info() {
            LOG.log(Level.INFO, "interface of test2 application");
            return restTemplate.getForObject("http://localhost:8988/info", String.class);
        }
    
        @RequestMapping("/info")
        public String home() {
            LOG.log(Level.INFO, "application info of test2");
            return "application info of test2";
        }
    
        @Bean
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
    
        @Bean
        public Sampler defaultSampler() {
            return Sampler.ALWAYS_SAMPLE;
        }
    
    }
  4. 访问测试

    参考上文启动 server-zipkin 服务。

    启动模块 service-zipkin-test1service-zipkin-test2

    访问:http://localhost:8989/test2 返回结果:application info of test1

    访问:http://localhost:8988/test1 返回结果:application info of test1

    访问:http://localhost:9411/ 进行追踪,即可在 依赖 中查看到两个工程模块之间的依赖关系。

参考

版权声明

本文链接:https://www.chinmoku.cc/java/framework/springcloud-tutorial/

本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。

Press ESC to close