三、开始使用Spring Cloud

开始之前

还是选择最新的Spring Boot 2.3.4.RELEASE 版本,采用mysql作为数据库而不是h2。 也用到了一些常用的maven插件

1.从例子入手

一个电影售票系统,用户向电影微服务发起购票请求,电影微服务要调用用户微服务接口,查询用户余额,场景示例如下

tars 微服务 微服务 actor_微服务

先编写一个用户微服务,再编写一个电影微服务

2.编写服务提供者

需求:通过主键查询用户信息。便于测试,使用Spring Data JPA作为持久层框架,使用H2作为数据库

编写项目

1.起一个项目microservice-simple-provider-user,使用IDEA创建Spring Boot项目即可(创建过程不再赘述),具体的pom文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lzy.cloud</groupId>
    <artifactId>microservice-simple-provider-user</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>microservice-simple-provider-user</name>
    <description>a user information provider</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.建表语句内容如下:

drop table user if exists;
create table user (
id bigint generated by default as identity,
username varchar (40),
name varchar (40),
age int(3),
balance decimal(10,2),
primary key (id)
);

3.插入初始数据:

insert into user (id,username,name,age,balance) values
(1,'account1','张三',20,100.00);

insert into user (id,username,name,age,balance) values
(2,'account2','李四',28,180.00);

insert into user (id,username,name,age,balance) values
(3,'account3','王五',32,280.00);

4.创建实体类

package com.lzy.cloud.microservicesimpleprovideruser.po;


import lombok.Data;

import javax.persistence.*;
import java.math.BigDecimal;

@Entity
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column
    private String username;

    @Column
    private String name;

    @Column
    private Integer age;

    @Column
    private BigDecimal balance;

}

5.创建DAO

package com.lzy.cloud.microservicesimpleprovideruser.dao;

import com.lzy.cloud.microservicesimpleprovideruser.po.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserDao extends JpaRepository<User, Long>{

}

6.创建Controller

package com.lzy.cloud.microservicesimpleprovideruser.controller;

import com.lzy.cloud.microservicesimpleprovideruser.dao.UserDao;
import com.lzy.cloud.microservicesimpleprovideruser.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.Optional;

@RestController
public class UserController {
    @Autowired
    private UserDao userDao;

    @GetMapping("/{id}")
    public User findById(@PathVariable Long id) {
        Optional<User> findOne = userDao.findById(id);
        User result = findOne.get();
        return result;
    }
}

7.启动类代码

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MicroserviceSimpleProviderUserApplication {

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

}

8.编写配置文件

server:
  port: 8080

spring:
  datasource:
    #通用数据源配置
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?charset=utf8mb4&useSSL=false
    username: root
    password: 123456
    # Hikari 数据源专用配置 Hikari 是Springboot用的连接池
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
    #初始化数据


  # JPA 相关配置
  jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: true
    hibernate:
      # 慎用!
#      ddl-auto: create

logging:
  level:
    root: INFO
    org.hibernate: INFO
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.Extractor: TRACE
# 配置在日志中打印出执行的 SQL 语句信息。
#spring.jpa.show-sql=true

# 配置指明在程序启动的时候要删除并且创建实体类对应的表。这个参数很危险,因为他会把对应的表删除掉然后重建。
# 所以千万不要在生成环境中使用。只有在测试环境中,一开始初始化数据库结构的时候才能使用一次。
#spring.jpa.hibernate.ddl-auto=create

# 在 SrpingBoot 2.0 版本中,Hibernate 创建数据表的时候,默认的数据库存储引擎选择的是 MyISAM。
# 这个参数是在建表的时候,将默认的存储引擎切换为 InnoDB 用的。
#spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

启动项目,进行测试,访问网址http://localhost:8080/1

得到json格式返回数据

{
"id": 1,
"username": "account1",
"name": "张三",
"age": 20,
"balance": 100
}

3.编写服务消费者

前面写了一个服务提供者(用户微服务),下面写一个服务消费者(电影微服务)。

该服务十分简单,它使用RestTemplate调用用户微服务API,查询指定id的用户信息

1.创建Maven项目,也可以直接创建Spring Boot项目,ArtifactId 是 microservice-simple-consumer-movie

2.和用户微服务相同,引入同样的依赖,可以使用和上面一样的pom.xml文件,但要把没有用到的组件依赖删除掉

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.3.4.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.lzy.cloud</groupId>
   <artifactId>microservice-simple-consumer-movie</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>microservice-simple-consumer-movie</name>
   <description>a consumer for movie</description>

   <properties>
      <java.version>1.8</java.version>
   </properties>

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

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
         <exclusions>
            <exclusion>
               <groupId>org.junit.vintage</groupId>
               <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
         </exclusions>
      </dependency>


      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.16.22</version>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>

</project>

3.创建用户实体类,该类是一个POJO

package com.lzy.cloud.microservicesimpleconsumermovie.pojo;


import lombok.Data;

import java.math.BigDecimal;

@Data
public class User {

    private Long id;
    private String username;
    private String name;
    private Integer age;
    private BigDecimal balance;
}

4.创建启动类,代码如下:

package com.lzy.cloud.microservicesimpleconsumermovie;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class MicroserviceSimpleConsumerMovieApplication {

   @Bean
   public RestTemplate restTemplate() {
      return new RestTemplate();
   }
   public static void main(String[] args) {
      SpringApplication.run(MicroserviceSimpleConsumerMovieApplication.class, args);
   }

}

@Bean是一个方法注解,可以实例化一个Bean并使用该方法的名称命名,添加了@Bean注解的restTemplate()方法,等价于RestTemplate restTemplate = new RestTemplate();

5.创建Controller,在其中使用RestTemplate请求用户微服务的API

package com.lzy.cloud.microservicesimpleconsumermovie.controller;

import com.lzy.cloud.microservicesimpleconsumermovie.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class MovieController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/user/{id}")
    public User findById(@PathVariable Long id){
        return this.restTemplate.getForObject("http://localhost:8080/" +id, User.class);
    }
}

6.编写配置文件application.yaml

server:
  port: 8010

测试访问http://localhost:8010/user/1,结果如下

{
"id": 1,
"username": "account1",
"name": "张三",
"age": 20,
"balance": 100
}

4.整合Spring Boot Actuator

Spring Boot Actuator提供了很多监控端点,能使用 http://{ip}:{port}/{endpoint}形式来访问,了解应用程序运行状况

为上面两个项目添加依赖

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

Springboot 2.x 之后,多数端点被禁,日志打印信息有变,变为如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvtZMKLn-1600789728197)(/Users/lzy/资料/学习/Typora图片/image-20200915131401169.png)]

所有的监管点加上了 /actuator 前缀,如果想暴露所有监管点,需要在配置文件中引入

配置application.yml文件暴露所有监管点:

management:
  endpoints:
    web:
      base-path: /actuator		#如果修改,则监管点访问路径前缀也会改变
      exposure:
        include: "*"  #星号代表暴露全部,可以选择暴露部分,填入想暴露的即可

更多参考:http://www.ityouknow.com/springboot/2018/02/06/spring-boot-actuator.html

原生端点
  • 应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与SpringBoot密切相关的配置类信息
  • 度量指标类:获取应用程序运行过程中用于监控的度量指标,如内存、线程池、http请求信息等
  • 操作控制类:提供了对应用关闭等操作类功能

下面我们看一下可用的端点,他们大部分在1.x中已经存在。尽管如此,有些端点新增,有些被删除,有些被重构。

  • /auditevents:同Actuator 1.x,还可以通过关键字进行过滤
  • /beans:同Actuator 1.x,不可以过滤
  • /conditions:返回服务中的自动配置项
  • /configprops:允许我们获取@ConfigurationProperties的bean对象
  • /env:返回当前的环境变量,我们也可以检索某个值
  • /flyway:提供Flyway数据库迁移的详细情况
  • /health:同Actuator 1.x
  • /heapdump:返回应用服务使用地jvm堆dump信息
  • /info:同Actuator 1.x
  • /liquibase:类似于 /flyway,但是组件工具为Liquibase
  • /logfile:返回应用的普通日志文件
  • /loggers:允许我们查询和修改应用的日志等级
  • /metrics:同Actuator 1.x
  • /prometheus:返回与/metrics类似,与Prometheus server一起使用
  • /scheduledtasks:返回应用的周期性任务
  • /sessions:同Actuator 1.x
  • /shutdown:同Actuator 1.x
  • /threaddump:dump所依赖的jvm线程信息

例如metrics端点,进入后变成如下结果:

{
"names": [
"http.server.requests",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.gc.pause",
...
  ],
}

如果想要看某项监控指标,需要路径中继续添加,如

http://localhost:8082/actuator/metrics/jvm.buffer.memory.used

结果如下,比1.X 更加详细

{
"name": "jvm.buffer.memory.used",
"description": "An estimate of the memory that the Java virtual machine is using for this buffer pool",
"baseUnit": "bytes",
"measurements": [
  {
		"statistic": "VALUE",
		"value": 69632
  }
],
  "availableTags": [
	{
		"tag": "id",
		"values": [
				"direct",
				"mapped"
		]
	}
]
}

需要注意 2.x 之后很多路径都改变了

访问 localhost:8080/info返回了空的json结构,可以用info.*属性来自定info端点公开的数据,在yaml配置文件中添加:

info:
  app:
    name:microservice-simple-provider-user

再访问localhost:8080/info,得到如下数据

{
"app": "name:microservice-simple-provider-user"
}

5.硬编码的问题

在电影微服务的Controller类中:

@GetMapping("/user/{id}")
    public User findById(@PathVariable Long id){
        return this.restTemplate.getForObject("http://localhost:8080/" +id, User.class);
    }

将服务提供者的网络地址和端口硬编码在了代码中,也可以提取到配置文件中:

user:
	userServiceUrl:http://localhost:8080/

代码改为

@Value("user.userServiceUrl")
private String userServiceUrl;

@GetMapping("/user/{id}")
    public User findById(@PathVariable Long id){
        return this.restTemplate.getForObject(this.userServiceUrl +id, User.class);
    }

传统应用程序中经常这样做,但依然存在很多问题

  • 适用场景有限:服务提供者网络地址发生改变,就会影响服务消费者
  • 无法动态伸缩:生产环境中每个微服务要部署多个实例,实现容灾和负载均衡,微服务架构系统中,还要系统具有自动伸缩能力,例如动态增删节点等。硬编码无法适应这种需求。

下一节将关注微服务的注册与发现,解决这些问题