开始使用Spring Cloud实战微服务

Posted shi_zi_183

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开始使用Spring Cloud实战微服务相关的知识,希望对你有一定的参考价值。

开始使用Spring Cloud实战微服务

Spring Cloud 实战前提

Spring Cloud不一定适合所有人。学习之前需要了解需要具备什么样的技术能力,以及实战中会使用到哪些工具。

技术储备

Spring Cloud并不是面向零基础开发人员的,它有一定的学习曲线。

  • 语言基础:Spring Cloud是一个基于Java语言的工具套件,所以学习他需要一定的Java基础。当然,Spring Cloud同样也支持使用Scala、Groovy等语言进行开发。
  • Spring Boot:Spring Cloud是基于Spring Boot构建的,因此它延续了Spring Boot的契约模式以及开发方式。如果大家对Spring Boot不熟悉,建议花一点时间入门。
  • 项目管理与构建工具:目前业界比较主流的项目管理与构建工具有Maven和Gradle等,本书采用的是目前相对主流的Maven。大家也可使用Gradle管理与构建项目。并且,Maven与Gradle项目可以互换转换,例如,使用以下命令即可将Maven项目转换为Gradle项目。
gradle init --type pom

工具及软件版本

JDK:Spring Cloud官方建议使用JDK 1.8。
Spring Boot:本书使用Spring Boot1.5.9.RELEASE
Spring Boot:本书使用Spring Cloud Edgware RELEASE
IDE:IntelliJ IDEA
Maven:Maven3.39构建项目。和Spring Boot、Spring Cloud一样,Maven3.3.x默认也是运行在JDK1.8以上的。

服务提供者与服务消费者

使用微服务构建的是分布式系统,微服务之间通过网络进行通信。我们使用服务提供者与服务消费者来描述微服务之间的调用关系。

名词定义
服务提供者服务的被调用方(即:为其他服务提供服务的服务)
服务消费者服务的调用方(即:依赖其他服务的服务)

我们继续以电影售票系统为例。哟弄个胡向电影微服务发起了一个购票的请求。在进行购票的业务操作前,电影微服务需要调用用户微服务的接口,查询当前用户的余额是多少,是不是符合购票标准等。在这种场景下,用户微服务就是一个服务提供者,电影微服务则是一个服务消费者。

编写服务提供者

编写一个服务提供者(用户微服务),该服务可通过主键查询用户信息。为便于测试,使用Spring Data JPA作为持久层框架,使用H2作为数据库。

手动编写项目

1) 创建一个Maven项目,它的ArtifacId是microservice-simple-provider-user,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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>microservice-simple-provider-use</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <!--引入spring boot的依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <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-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

    <!--引入spring cloud的依赖-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.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>
        </plugins>
    </build>
</project>

其中,spring-boot-starter-web提供了Spring MVC的支持;spring-boot-starter-data-jpa提供了Spring Data JPA的支持。
准备好建表语句,在项目的classpath下创建sql文件夹并在其下建立schema.sql、data.sql
schema.sql

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

data.sql

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

在项目下创建entity包创建用户实体类
User.java

package com.example.entity;

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

@Entity
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;
    //省略setter、getter、tostring
}

创建dao包并在其下创建UserRepository.java

package com.example.dao;

import com.example.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Long> {
}

创建controller包并在其下创建UserController

package controller;

import dao.UserRepository;
import entity.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;

@RestController
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @GetMapping("/{id}")
    public User findById(@PathVariable Long id) {
        User findOne = this.userRepository.findOne(id);
        return findOne;
    }
}

Controller中用到的@GetMapping是Spring4.3提供的新注解。它是一个组合注解,等价于@RequestMapping(method = RequestMethod.GET),用于简化开发。同理还有@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping等。
编写启动类,在类上使用@SpringBootApplication声明这是一个Spring Boot项目。
ProvideUserApplication.java

package com.example;

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

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

@SpringBootApplication是一个组合注解,它整合了@Configuration、@EnableAutoConfiguration和@ComponentScan注解,并开启了Spring Boot程序的组件扫描和自动配置功能。在开发Spring Boot程序的过程中,常常会组合使用@Configuration、@EnableAutoConfiguration和@ComponentScan等注解,所以Spring Boot提供了@SpringBootApplication,来简化开发。
编写配置文件,命名为application.yml

server:
  port: 8000
spring:
  jpa:
    generate-ddl: false
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:           #指定数据源
    platform: h2        #指定数据源类型
    schema: classpath:sql/schema.sql    #指定h2数据库的建表脚本
    data: classpath:sql/data.sql         #指定h2数据库的数据脚本
logging:              #配置日志级别,让hibernate打印执行的SQL
  level:
    root: INFO
    org.hibernate: INFO
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.BasicExtractor: TRACE

在传统的Web开发中,常使用properties格式文件作为配置文件。Spring Boot以及Spring Cloud支持使用properties或者yml格式的文件作为配置文件。
yml文件格式是YAML(Yet Another Markup Language)编写的文件格式,YAML和properties格式的文件可互相转换。
但是,YAML比properties结构清晰;可读性、可维护性也更强,并且语法简捷,也更支持中文汉字(UTF-8)。yml有严格的缩进,并且key与value之间使用’:'分隔,冒号后的空格不能少,需要注意。
测试
访问http://localhost:8000/1,获得结果。


使用Spring Initializr快速创建Spring Boot项目

之前是手动创建项目的。事实上,也可使用Spring Initializr快速创建项目。虽然Spring Initializr不能生成应用程序的业务代码,但它可生成基本的项目结构。这样可以把更多精力放在具体的业务代码上,无须过分关注项目搭建的过程。

编写服务消费者

前文编写了一个服务提供者(用户微服务)。本节来编写一个服务消费者(电影微服务)。该服务非常简单,它使用RestTemplate调用微服务的API,从而查询指定ID的用户信息。
1)创建一个Spring Initializr项目,ArtifactId是microsevice-simple-consumer-movie。
2)选择web依赖

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

package com.example.entity;

import java.math.BigDecimal;

public class User {
    private Long id;
    private String username;
    private String name;
    private Integer age;
    private BigDecimal banlance;
	//省略setter、getter、tostring
}

4)修改启动类

package com.example;

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 MicroseviceSimpleConsumerMovieApplication {
	@Bean
	public RestTemplate restTemplate(){
		return new RestTemplate();
	}
	public static void main(String[] args) {
		SpringApplication.run(MicroseviceSimpleConsumerMovieApplication.class, args);
	}
}

@Bean是一个方法注解,作用是实例化一个Bean并使用该方法的名称命名。在本例中,添加@Bean注解的restTemplate()方法,等价于RestTemplate restTemplate = new RestTemplate();
5)创建controller包并在其下创建一个MovieController控制类

package com.example.controller;

import com.example.entity.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:8000/"+id,User.class);
    }
}

6)配置文件application.yml

server:
  port: 8010

测试
访问http://localhost:8010/user/1

为项目整合Spring Boot Actuator

Spring Boot Actuator提供了很多监控断点。可使用http://{ip}:{post}/{endpoint}的形式访问这些端点,从而了解应用程序的运行状况。

端点描述HTTP方法是否敏感
autoconfig显示自动配置的信息GET
beans显示应用程序上下文所有的Spring beanGET
configprops显示所有@ConfigurationProperties的配置属性列表GET
dump显示线程活动的快照GET
env显示应用的环境变量GET
health显示应用程序的健康指标,这些值由HealthIndicator的实现类提供。当应用开启安全保护时,对于未经用户认证的请求,只会显示简单的状态;如已认证,则会显示健康详情
info显示应用的信息,可使用info.*属性自定义info端点公开的数据GET
mappings显示所有@RequestMapping的路径列表GET
metrics显示应用的度量标准信息GET
shutdown关闭应用(默认情况下不启用,如需启用,需设置endpoints.shutdown.enabled=true)POST
trace显示跟踪信息(默认情况下为最近100个HTTP请求)GET

为项目添加以下依赖
pom.xml

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

这样就整合好Actuator了。
测试
/health端点

此时,可展示当前应用的健康状况。其中,UP表示运行正常,除UP外,还有DOWN、OUT_OF_SERVICE/UNKNOWN等状态。此时,只显示了一个概要情况,如需展示详情,可为应用添加spring-boot-starter-security或设置management.security.enable=false.
设置management.security.enable=false再次访问。

由结果可知,/health的本质,通过检查Spring Boot应用资源,来判断应用是否正常。
测试二
/info端口
访问http://localhost:8000/info,可以看到以下内容。

由结果可知,info端点并没有返回任何数据给我们。可使用info.*属性来自定义info端点公开的数据。

info: 
  app:
    name: @project.artifactId@
    encoding: @project.build.sourceEncoding@
    java:
      source: @java.version@
      target: @java.version@

重启,重新访问

其他端点可以自行测试。
Spring Boot出于安全考虑,从Spring Boot1.5开始,对于敏感路径,默认不允许访问(直接访问将看到错误页面,HTTP错误码为401)。

硬编码有哪些问题

MovieController.java中

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

由代码可知,我们是把提供者的网络地址(IP和端口等)硬编码在代码中的,当然,也可将其提取到配置文件中去。

user:
  userServiceUrl: http://localhost:8000/

代码改为

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

在传统的应用程序中,一般都是这么做的。然而,这种方式有很多问题。

  • 使用场景有局限:如果服务提供者的网络地址(ip和端口)发生改变,会影响服务消费者。例如,用户微服务的网络地址发生变化,就需要修改电影微服务的配置,并重新发布。这显然是不可取的。
  • 无法动态伸缩:在生产环境中,每个微服务一般都会部署多个实例,从而实现容灾和负载均衡。在微服务框架的系统中,还需要系统具备自动伸缩的能力,例如动态增减节点等。硬编码无法适应这种需求。

以上是关于开始使用Spring Cloud实战微服务的主要内容,如果未能解决你的问题,请参考以下文章

0201-开始使用Spring Cloud实战微服务准备工作

《Spring Cloud与Docker微服务架构实战》配套代码

Spring cloud微服务安全实战-4-4 OAuth2协议与微服务安全

Spring Cloud微服务实战视频课程

荐书丨疯狂Spring Cloud微服务架构实战

从0开始构建你的api网关--Spring Cloud Gateway网关实战及原理解析