SpringBoot 与常见中间件的搭建

Eureka

  • 服务发现,提供高可用的服务注册表功能,是之后 Ribbon、Zuul 等组件的基础。

创建 Eureka Server

  1. 创建 Spring Initializr 项目
    选中 Cloud Discovery -> Eureka Server 模块
  2. EurekaServerApplication 类上添加 @EnableEurekaServer
  3. 指定一个 Eureka Server
    在默认情况下 erureka server 也是一个 eureka client,必须要指定一个 server,在配置文件中添加:
    1
    2
    3
    4
    5
    6
        server.port=8761
    eureka.instance.hostname=localhost
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
    eureka.client.service-url.default-zone=http://eureka.instance.hostname:eureka.instance.hostname:
    {server.port}/eureka/
    其中通过 eureka.client.registerWithEureka=false 和 fetchRegistry=false 来表明自己是一个 Eureka Server。
  4. 访问 Eureka Server 服务器
    localhost:8761
    此时还没有服务被注册进来。

创建 Eureka Client

  1. 类似的,创建的 Spring Initializr 项目是 Eureka Discovery
  2. EurekaClientApplication 类上添加 @EnableEurekaClient 注解
  3. 添加配置
    1
    2
    3
    4
    5
    eureka.client.service-url.default-zone=http://localhost:8761/eureka
    server.port=8762
    spring.application.name=service-name
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
    需要指明 spring.application.name,这个很重要,这在以后的服务与服务之间相互调用一般都是根据这个 name。

集群化 Eureka Server

  1. 创建两个配置文件,分别代表两个实例的属性
    application-peer1.properties:
    1
    2
    3
    server.port=8761
    eureka.instance.hostname=peer1
    eureka.client.service-url.defaultZone=http://peer2:8769/eureka/
    application-peer2.properties:
    1
    2
    3
    server.port=8769
    eureka.instance.hostname=peer2
    eureka.client.service-url.defaultZone=http://peer1:8761/eureka/
  2. 在/etc/hosts 文件中添加下面两条记录:
    1
    2
    127.0.0.1 peer1
    127.0.0.1 peer2
  3. 启动
    先在application.properties中指定:
    1
    spring.profiles.active=peer1
    启动 peer1,然后指定:
    1
    spring.profiles.active=peer2
    启动 peer2。
    这时访问 localhost:8761 和 localhost:8769 可以看到两个节点的注册信息,他们分别是对方的复制,他们是对等的。

Ribbon - 服务消费

  • 用来作为软件的负载均衡,微服务之间相互调用是通过 ribbon 作为软件负载均衡使用负载到微服务集群内的不同的实例。

配置Bean

IClientConfig ribbonClientConfig: DefaultClientConfigImpl
IRule ribbonRule: ZoneAvoidanceRule
IPing ribbonPing: NoOpPing
ServerList ribbonServerList: ConfigurationBasedServerList
ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter
ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer

创建服务消费者

  1. 创建 Spring Initializr
    勾选 Eureka Discovery、Ribbon
  2. 添加配置
    1
    2
    3
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
    server.port=8764
    spring.application.name=service-ribbon
  3. 除了为 ServiceRibbonApplication 类添加 @EnableDiscoveryClient 外,还需要注册一个 RestTemplate 的 Bean:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 向Spring容器注入一个RestTemplate
    * 通过@LoadBalanced表明开启负载均衡功能
    * @return
    */
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
    return new RestTemplate();
    }
  4. 创建服务类来消费service-name服务的”/hi”接口的服务,当该服务名有多个服务实例时会从中选择一个具体的服务实例:
    1
    2
    3
    4
    5
    6
    7
    8
    @Service
    public class HelloService {
    @Autowired
    RestTemplate restTemplate;
    public String hello(String name) {
    return restTemplate.getForObject("http://service-name/hi?name=" + name, String.class);
    }
    }
  5. 使用一个 Controller 来调用负载均衡服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RestController
    public class HelloController {

    @Autowired
    HelloService helloService;

    @RequestMapping("/hi")
    public String hi(@RequestParam String name) {
    return helloService.hello(name);
    }
    }
  6. 此时访问 service-ribbon 的”/hi”接口就会被转发到 service-name 的对应服务上去,并且会根据负载情况来轮流调用不同实例的端口。

Feign - 服务消费

与 Ribbon 区别

  1. Feign 整合了 Ribbon
  2. Feign 是基于接口的注解,Ribbon 是基于 RestTemplate 的

创建 Feign 服务

  1. 创建一个 Spring Initializr 项目
    选中 Eureka Discovery 和 Feign
  2. ServiceFeignApplication 类上添加 **@EnableDiscoveryClient
    ** 和 @EnableFeignClients 注解开启 Eureka 服务发现和 Feign 功能
  3. 定义一个 Feign 接口
    1
    2
    3
    4
    5
    @FeignClient("service-name")
    public interface ScheduleServiceHi {
    @RequestMapping(value = "/hi", method = RequestMethod.GET)
    String sayHiFromClientOne(@RequestParam("name") String name);
    }
    注解 @FeignClient 指定了服务名,具体的接口使用@RequestMapping指定。
  4. 创建一个 Controller,对外暴露一个”/hi”接口,它调用上面定义的 Feign 客户端的 ScheduleServiceHi 来消费服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RestController
    public class HiController {

    @Autowired
    private ScheduleServiceHi scheduleServiceHi;

    @RequestMapping(value = "/hi", method = RequestMethod.GET)
    public String sayHi(@RequestParam String name) {
    return scheduleServiceHi.sayHiFromClientOne(name);
    }
    }

Zuul

Hystrix - 断路器

Dashboard 和 Turbine:一个是单个节点的监控,后者可以从多个 Hystrix 服务器获取数据、整合到一个 Dashboard 界面上显示

应用

  1. 防止服务器雪崩
    雪崩:如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果。
    断路就是及早阻止对这些不可用服务的请求。
  2. 断路原理
    当对特定的服务的调用的不可用达到一个阀值(Hystric 是 5 秒 20 次) 断路器将会被打开,然后???

为 Ribbon 工程添加 Hystrix 功能

  1. 创建 Spring Initializr 项目
    勾选 Hystrix、Eureka Discovery、Ribbon,或者在 Ribbon 项目中添加下面的依赖:
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    </dependency>
  2. ServiceRibbonApplication 类添加 @EnableHystrix 注解开启Hystrix
  3. 为 Service 方法添加 @HystrixCommand 注解
    1
    2
    3
    4
    5
    6
    7
    8
    @HystrixCommand(fallbackMethod = "hiError")
    public String hello(String name) {
    return restTemplate.getForObject("http://service-name/hi?name=" + name, String.class);
    }

    public String hiError(String name) {
    return "hi, " + name + ", sorry, error!";
    }
  4. 接下来在启动所有服务后,关闭 service-name 服务(这是断路器依赖的服务)
    这样就会在访问 service-ribbon 服务时被断路,并调用 hiError()方法。

为 Feign 工程添加 Hystrix 功能

  1. 为 Feign 客户端添加 fallback 属性,该属性值是服务接口的实现类
    1
    @FeignClient(value = "service-name", fallback = ScheduleServiceHiHystrix.class)
  2. 创建接口的实现类
    1
    2
    3
    4
    5
    6
    7
    @Component
    public class ScheduleServiceHiHystrix implements ScheduleServiceHi {
    @Override
    public String sayHiFromClientOne(String name) {
    return "sorry " + name;
    }
    }
  3. 添加下面的配置项
    feign.hystrix.enabled=true
  4. 必须还得添加下面的依赖包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
    </dependency>
    <dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-javanica</artifactId>
    <version>1.5.12</version>
    </dependency>
  5. 测试接口时,可以先开着 service-name 服务关掉再开起来,在这个过程中访问 service-feign 服务,可以看到响应的变化

Hystrix Dashboard

  1. 开启仪表盘很简单,先添加依赖库:
    1
    2
    3
    4
    5
    6
    7
    8
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    </dependency>
  2. 以 service-ribbon 服务为例,为 ServiceRibbonApplication 类添加 @EnableHystrixDashboard 注解
  3. 访问http://localhost:8764/hystrix 接口,进入监控界面
    HystrixDashboard

Hystrix Turbine

  1. 创建一个 Spring Initializr 工程
    勾选 Actuator、Turbine
  2. 为启动类添加注解 @EnableTurbine ,它其实包含了@EnableDiscoveryClient,所以 Turbine 默认是作为 Eureka 集群中的一员的
  3. 添加配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    spring.application.name=service-turbine
    server.port=8770
    security.basic.enabled=false
    # 指定聚合的集群,多个使用","分割,默认为default。可使用http://.../turbine.stream?cluster={clusterConfig之一}访问
    turbine.aggregator.cluster-config=default
    # 监控的Hystrix服务名列表
    turbine.app-config=service-ribbon
    # 1. clusterNameExpression指定集群名称,默认表达式appName;此时:turbine.aggregator.clusterConfig需要配置想要监控的应用名称
    # 2. 当clusterNameExpression: default时,turbine.aggregator.clusterConfig可以不写,因为默认就是default
    # 3. 当clusterNameExpression: metadata['cluster']时,假设想要监控的应用配置了eureka.instance.metadata-map.cluster: ABC,则需要配置,同时turbine.aggregator.clusterConfig: ABC
    turbine.cluster-name-expression=new String("default")
    eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
  4. 进入 Dashboard 查看监控流
    比如http://localhost:8764/hystrix,输入监控流 http://localhost:8770/turbine.stream
    会发现监控的服务列表都呈现在了 Thread Pools 中

Sleuth - 服务链路追踪

概念

  1. Zipkin:Sleuth 集成了服务追踪组件 Zipkin,而 Sleuth 通过了通过 Spring Boot 来集成 Zipkin 到微服务系统的方案。
  2. Span:基本工作单元,比如发送一个 PRC 请求或 RPC 响应都是一个 Span,使用一个 64 位的 ID 表示(文档这里相当费解???),启动 Trace 的第一个 Span 称为 Root Span;
  3. Trace:一组 Span 可以构成一个树状结构;
  4. Annotation:用于标识事件,比如请求的开始、结束,有以下 Annotation 种类:
    • cs - Client Sent -客户端发起一个请求,这个 annotion 描述了这个 span 的开始
    • sr - Server Received -服务端获得请求并准备开始处理它,如果将其 sr 减去 cs 时间戳便可得到网络延迟
    • ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果 ss 减去 sr 时间戳便可得到服务端需要的处理请求时间
    • cr - Client Received -表明 span 的结束,客户端成功接收到服务端的回复,如果 cr 减去 cs 时间戳便可得到客户端从服务端获取回复的所有所需时间

服务链路追踪

  1. 创建一个 Spring Initializr 项目
    一个 zipkin-server 勾选 Web、Zipkin Stream、Zipkin UI 和 Rabbit Stream,还有供跟踪的服务 zipkin-test1 和 zipkin-test2 勾选 Zipkin Client 和 Web,
  2. 加入注解
    在zipkin-server的Application上添加 @EnableZipkinServer
  3. 配置
    在 zipkin-server 中指定服务端口号即可:
    1
    server.port=9411
    在 zipkin-test1(zipkin-test2 类似)中需要指定 zipkin 服务器的地址
    1
    2
    3
    server.port=8988
    spring.zipkin.base-url=http://localhost:9411
    spring.application.name=service-hi
  4. 在两个 Service 之间使用 RestTemplate 进行互调
    第一个服务:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    private static final Logger LOG = Logger.getLogger(ZipkinTest1Application.class);
    @Autowired
    private RestTemplate restTemplate;
    @Bean
    public RestTemplate getRestTemplate() {
    return new RestTemplate();
    }

    @RequestMapping("/hi")
    public String callMiya(){
    LOG.log(Level.INFO, "calling trace service-hi ");
    return restTemplate.getForObject("http://localhost:8989/miya", String.class);
    }
    @RequestMapping("/info")
    public String info(){
    LOG.log(Level.INFO, "calling trace service-hi ");
    return "i'm service-hi";
    }
    另一个服务:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private static final Logger LOG = Logger.getLogger(ZipkinTest2Application.class);
    @Autowired
    private RestTemplate restTemplate;
    @Bean
    public RestTemplate getRestTemplate() {
    return new RestTemplate();
    }

    @RequestMapping("/miya")
    public String miya() {
    LOG.log(Level.INFO, "info is being called");
    return restTemplate.getForObject("http://localhost:8988/info", String.class);
    }
  5. 为 Service 注入 Sampler 用于记录 Span
    1
    2
    3
    4
    @Bean
    public AlwaysSampler defaultSampler() {
    return new AlwaysSampler();
    }
    如果不注入 Sampler,会出现之后访问服务后,在 Zipkin 界面看不到服务依赖图的情况。
  6. 访问,并读取链路
    访问服务http://localhost:8988/hi
    现在访问http://localhost:9411/会出现 Zipkin 界面,可以查看服务间的相互依赖关系,和服务间调用的具体数据。

Docker部署

应用

  1. web 应用的自动化打包和发布;
  2. 自动化测试和持续集成、发布;
  3. 在服务型环境中部署和调整数据库或其他的后台应用;
  4. 从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。

为 Spring Cloud 项目构建 Docker 镜像

  1. 添加依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!--Spotify的docker镜像构建插件-->
    <plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.4.3</version>
    <configuration>
    <!--
    imageName:镜像名
    dockerDirectory:Dockerfile位置
    resources:指定需要和Dockerfile放在一起构建镜像的文件,包括jar包-->
    <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
    <dockerDirectory>src/main/docker</dockerDirectory>
    <resources>
    <resource>
    <targetPath>/</targetPath>
    <directory>${project.build.directory}</directory>
    <include>${project.build.finalName}.jar</include>
    </resource>
    </resources>
    </configuration>
    </plugin>
  2. 配置
    这个是注册中心的配置:
    1
    2
    3
    4
    server.port=8761
    eureka.instance.prefer-ip-address=true
    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false
    服务的配置比较简单(注意 defaultZone 改成了镜像名):
    1
    2
    3
    eureka.client.service-url.defaultZone=http://eureka-server:8761/eureka/ # 使用docker启动,defaultZone的host改为镜像名
    server.port=8762
    spring.application.name=service-name
  3. 在 src/main/docker 下创建 Dockerfile 文件
    注册中心:
    1
    2
    3
    4
    5
    6
    FROM frolvlad/alpine-oraclejdk8:slim # 指定image,可以是官方仓库或本地仓库
    VOLUME /tmp # 使容器中的一个目录具有持久化数据的功能
    ADD eureka-server-0.0.1-SNAPSHOT.jar app.jar # 从src目录复制文件到容器的dest
    # RUN bash -c 'touch /app.jar' # 容器启动时执行的命令,可以多次设置但只有最后一个有效
    ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
    EXPOSE 8761 # 暴露的端口号
    服务类似,只是要将 ADD 操作拷贝的镜像名和 EXPOSE 暴露的端口号改一改。
  4. 构建
    1
    2
    mvn clean
    mvn package docker:build
    这样运行可能会报错:没有权限,这时候可以切换到 root 用户运行,或将当前用户加入到 docker 组中。
  5. 运行
    注册中心:
    1
    2
    # --name指定容器名用于之后被其他容器连接,-p指定端口映射,第一个是主机端口,第二个是容器内的端口,这两个端口也可以实现为一个范围且这两个范围中的端口数必须相同,比如`-p 1234-1236:1222-1224`,还可以在端口前面指定监听的主机ip,-t指定分配一个虚拟终端,这样在该终端退出后这个服务还是在运行着的,最后的参数是docker镜像名
    docker run --name eureka-server -p 8761:8761 -t springboot/eureka-server
    服务:
    1
    2
    # --link连接到某个容器:端口上,它会在两个容器之间建立一个安全通道,且该容器的该端口必须是暴露的
    docker run --link eureka-server:8761 -p 8762:8762 -t springboot/eureka-client

使用 Docker-Compose 启动镜像

Compose 是一个用于定义和运行多容器的 Docker 应用的工具。使用 Compose,你可以在一个配置文件(yaml 格式)中配置你应用的服务,然后使用一个命令,即可创建并启动配置中引用的所有服务。

  1. 按上面的步骤构建 docker 镜像
  2. 在上面两个项目的父一级目录下创建配置文件 docker-compose.yml 配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    version: '2'
    services:
    eureka-server:
    image: springboot/eureka-server
    restart: always
    ports:
    - 8761:8761

    eureka-client:
    image: springboot/eureka-client
    restart: always
    ports:
    - 8762:8762
  3. 启动
    1
    docker-compose up

使用Docker-Compose构建并启动镜像

docker-compose 也可以用于构建镜像,这样就不用一个一个镜像构建再去运行了。

  1. 在 docker-compose.yml 同级目录下创建配置文件 docker-compose-dev.yml:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    version: '2'
    services:
    eureka-server:
    build: eureka-server
    ports:
    - 8761:8761

    eureka-client:
    build: eureka-client
    ports:
    - 8762:8762
  2. 构建并启动
    1
    docker-compose -f docker-compose.yml -f docker-compose-dev.yml up

参考

Eureka

  1. 服务发现:Eureka客户端

Ribbon

  1. Netflix/ribbon https://github.com/Netflix/ribbon/wiki
  2. 客户端负载平衡器:Ribbon https://springcloud.cc/spring-cloud-dalston.html#spring-cloud-ribbon

Feign

  1. 声明性 REST 客户端:Feign https://springcloud.cc/spring-cloud-dalston.html#spring-cloud-feign

Hystrix

  1. Netflix/Hystrix https://github.com/Netflix/Hystrix/wiki
  2. Circuit Breaker: Hystrix Clients https://springcloud.cc/spring-cloud-dalston.html#_circuit_breaker_hystrix_clients

Sleuth

  1. Zipkin https://zipkin.io/
  2. Spring Cloud Sleuth https://springcloud.cc/spring-cloud-dalston.html#_spring_cloud_sleuth
  3. Google Dapper-大规模分布式系统的基础跟踪设施 http://duanple.blog.163.com/blog/static/70971767201329113141336/
  4. http://research.google.com/pubs/pub36356.html
  5. http://bigbully.github.io/Dapper-translation/
  6. http://research.google.com/pubs/pub40378.html
  7. Eagleeye
  8. Sleuth 源码分析

Docker

  1. 用 Docker 构建、运行、发布一个 Spring Boot 应用
  2. docker-maven-plugin
  3. Docker Compose