Spring Cloud微服务实战一、二、三章内容的整理笔记

微服务概述

定义

微服务是系统架构上的一种设计风格,有如下特点:

  • 将原本的独立系统按照业务模块划分拆分成多个小型服务
  • 小型服务运行在各独立进程内,服务维护着自身的数据存储、业务开发、自动化测试及独立部署机制
  • 服务相互之间通过Http的Restful API进行通信协作
  • 服务的业务开发不用拘泥于用某种特定语言实现

优势

  • 解决了随着需求的扩大单应用系统变得臃肿难以维护的问题
  • 各服务可以独立扩展、维护及部署
  • 更准确地评估各服务的性能容量

挑战

  • 应用进程数量大大增加,运维成本增大
  • 服务间通信接口需保持一致性
  • 分布式下复杂性,如网络延迟、分布式事务等

特性

Martin FowlerMicroservice 一文中提炼的微服务架构九大特性,如下所示:

  • 服务组件化
  • 按业务组织团队
  • 做产品的态度
  • 智能端点及哑管道
  • 去中心化治理
  • 去中心化管理数据
  • 基础设施自动化
  • 容错设计
  • 演进式设计

为什么选择Spring Cloud

Spring Cloud是一个解决微服务架构实施的综合性解决框架,对于微服务架构下一些常见的问题都有其对应的解决方案,配置使用时不需要考虑各组件间的配置及兼容问题。

微服务构建:Spring Boot

优势

  • 自动化配置
  • 快速开发
  • 轻松部署

快速构建应用

可以访问官方的Spring Initializr工具来产生基础项目,链接请点击这里,依赖的引入根据具体需求操作,由于此处是要构建简单的Restful API,故引入web依赖即可。

项目工程产生的基础目录及其作用如下:

  • src/main/java:存放用于启动Spring应用的主程序入口Application类及java文件
  • src/main/resource:存放配置文件及静态资源文件的目录
  • src/test/:单元测试代码及测试资源的存放目录

实现Restful API:

@RestController
public class HelloController {

    @RequestMapping("/hello") /**Spring 4.3.9后可以改写为GetMapping("/hello")**/
    public String index() {
        return "Hello World";
    }
}

启动应用:执行mvn spring-boot:run命令运行应用

编写简单单元测试用例:

/**improt部分省略**/
@Runwith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = HelloApplication.class)
@WebAppConfiguration
public class HelloApplicationTests {

    private MockMvc mvc;

    @Before
    public void setUp() throw Exception {
        mvc = MockMvcBuilder.standaloneSetup(new HelloController()).build();
    }

    @Test
    public void hello() throw Exception {
        mvc.perform(MockMvcRequestBuilder.get("/hello").accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(content().string(equalTo("Hello World")));
    }
}

代码解析如下:

  • @Runwith(...):引入Spring对Junit4的支持
  • @SpringApplicationConfiguration(classes = ...):指定启动类
  • @WebAppConfiguration:开启Web应用的配置,用于模拟ServletContext
  • MockMVC对象:模拟controller发起请求
  • @Before:测试前初始化测试环境,此处为初始化HelloController的执行

配置详解

Spring Boot的配置文件存在在src/main/resource目录下,可以是application.properties,也可以是application.yml,两者配置的区别如下(以指定应用启动端口为例):

application.properties的配置:

server.port=8080

application.yml的配置:

server
 port: 8080

yml格式的配置文件还可以按环境划分配置,如下:

<!-- 不指定环境时执行的配置 -->
server:
 port: 8081

 --- <!-- 此行前面空格去掉 -->
<!-- 指定环境为test时执行的配置 -->
spring:
 profiles: test

sever:
 port: 8082

自定义配置:

personal.author=name

personal:
  author: name  

读取方式如下:

@Component
public class PersonalConfig {

    @Value("${personal.author}")
    private String author;

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

文件内引用配置(以yaml文件类型为例):

personal:
  author: lin
  book: hit
  all: ${personal.author} is writing ${personal.book}

文件内引入随机数(以yaml文件类型为例):

personal:
  random-string: ${random.value}
  random-int: ${random.int}
  random-long: ${ranom.long}
  <!-- 10以内的随机int类型随机数 -->
  random-int-less: ${random.int(10)}
  <!-- 10到20的int类型随机数 -->
  random-int-more: ${random.int(10, 20)}

Spring Boot加载配置的顺序:

  1. 在命令行传入的参数;
  2. SPRING\_APPLICATION\_JOSN中的属性,SPRING\_APPLICATION\_JOSN是以JSON格式配置在系统环境变量中的内容
  3. java:comp/env中的JNDI属性
  4. Java的系统属性,可以通过System.getProperties()获取的内容
  5. 操作系统的环境变量
  6. 通过random.*配置的随机属性
  7. 位于当前jar包之外,正对不同{profile}环境的配置文件内容,如application-{profile}.properties或YAML文件
  8. 位于当前jar包之内,正对不同{profile}环境的配置文件内容,如application-{profile}.properties或YAML文件
  9. 位于当前应用jar包之外的application.properties或YAML文件
  10. 位于当前应用jar包之内的application.properties或YAML文件
  11. @Configuration注解修改的类中,通过@PropertySource注解定义的属性
  12. 应用默认属性,使用SpringApplication.getDefaultProperties()定义的内容

注: 优先级由上到下,数字越小优先级越高!

监控及管理

为了获取各微服务的相关指标,需要引入依赖模块spring-boot-starter-actuator,它能为Spring Boot构建的应用提供一系列用于监控的端点,它会根据依赖及配置提供创建相应的监控及管理端点,用于获取各监控指标

引入依赖如下:

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

注: 大部分端点涉及到敏感信息,需要开启权限,方法为在配置中开启,如需加入保护机制,可以整合Spring Security进行安全校验

management:
  security:
    enabled: false

原生端点分以下三类:

  • 应用配置类:获取应用的配置类信息
  • 度量指标类:获取如内存信息、线程池信息等监控度量指标信息
  • 操作控制类:提供了对应用的关闭等操作类功能

应用配置类

URL 作用
/autoconfig 应用自动化配置报告,报告展示了各自动化配置项生效的先决条件,用于了解配置生效及未生效的原因
/beans 用于获取应用上下文中创建的所有Bean信息,包括名称、class文件具体路径及作用域等信息
/configprops 该断点用于获取应用中配置的属性信息报告,prefix属性代表属性的配置前缀,properties代表了各个属性的名称和值
/env 获取应用所有可用的环境属性报告,包括环境变量、JVM属性、应用的配置属性等
/mapping 获取所有Spring MVC控制器的映射关系报告
/info 获取应用自定义信息,在application.yml的info节下配置

度量指标类

URL 作用
/metrics 获取应用当前各度量指标,如内存信息、线程信息、垃圾回收等
/health 获取应用各类健康指标信息
/dump 获取运行中的线程信息
/trace 获取基本的HTTP跟踪信息,始终保留最近的100条请求记录

操作控制类

URL 作用
/shutdown 通过调用关闭接口,需开启权限,方法为在配置中将其开启:endpoints.shutdown.enabled=true

服务治理:Spring Cloud Eureka

服务治理围绕着服务注册服务发现机制完成对微服务应用实例的自动化管理。

服务注册: 每个微服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心维护各服务的可用应用实例清单。

服务发现: 服务间调用通过服务名向服务注册中心获取各服务的可用应用实例清单,服务调用端基于某种轮询策略调用应用实例。

搭建服务注册中心

新建 Spring Boot 工程,命名为eureka-server,并在pom.xml文件中引入以下依赖:

  <name>eureka-server</name>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.7.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring-cloud.version>Dalston.SR4</spring-cloud.version>
  </properties>

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

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

通过在 Application.class 启动类上添加@EnableEurekaServer注解启动一个服务注册中心,以对话其他应用。

并在 application.yml 配置文件中添加如下配置:

server:
  port: 1111
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

注:

  • eureka.client.register-with-eureka:设置不向注册中心注册自己
  • eureka.client.fetch-registry:设置为不检索服务

访问http://localhost:1111/可以查看到服务注册中心的相关信息

注册服务提供者

改造前面简单的 Spring Boot 应用为服务提供者,引入依赖如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </denpendencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在启动类Application 加上@EnableDiscoveryClient注解,激活Eureka中的 DiscoveryClient 实现

最后在 application.yml 配置文件中加入如下配置:

spring:
  application:
    name: hello-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1111/eureka/

访问 Eureka 的信息面板,可以在 Instances currently registered with Eureka 一栏中看到服务的注册信息

服务消费者

同服务消费者大致相同,新加入依赖如下,用于实现客户端的服务调用负责均衡(调用服务多实例):

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

同样地,启动类 Application 开启 @EnableDiscoveryClient 注解,同时声明 RestTemplate ,并开启负载均衡的注解 @LoadBalanced,如下:

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

需要将应用注册到服务注册中心,故加入如下配置:

spring:
  application:
    name: ribbon-consumer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1111/eureka/
server:
  port: 9000

创建 Controller 并注入 RestTemplate ,添加接口并调用服务名为 hello-service(不区分大小写) 的服务,hostname处用的是服务名称(服务ID),如下:

    @GetMapping("/ribbon-consumer")
    public String helloConsumer() {
        return restTemplate.getForEntity("http://hello-service/hello", String.class).getBody();
    }