写点什么

Spring Cloud 微服务实践 (1) - 用 Initializr 初始化

用户头像
xiaoboey
关注
发布于: 2020 年 09 月 16 日
Spring Cloud 微服务实践(1) - 用Initializr初始化

纸上得来终觉浅,绝知此事要躬行。这里我们就直接用Spring Initializr来初始化Spring Cloud项目,然后作一点配置,写几句代码,用比较笨的形式,“徒手”撸一个包含服务发现、网关和业务处理的开发环境版微服务。



1、先看看包含哪些模块(子项目)

回顾一下我们在《开篇闲话》中提到的简化版微服务:

服务发现与网关几乎不需要写代码,配置一下就可以跑起来,然后业务实现也暂时Say Hello,点到为止,重点是我们怎么把他们揉到一起。

2、用 Spring Initializr 初始化 Eureka Server

Spring Initializr 是Spring.io提供的一个Web应用,使用浏览器打开https://start.spring.io 就能使用,它的功能就是为你初始化一个基本的Spring Boot项目,并根据需要来加载用到的依赖。Spring Initializr也可以在Spring Tool Suite中使用,IntelliJ IDEA也做了集成。



为了体现“从零开始”,我们就用浏览器访问https://start.spring.io来初始化项目。

  • Project选Maven Project,使用Maven构建项目。

  • 开发语言Language选Java。

  • Project Metadata可以随意填,我这里把Artifact改成eureka-server。

  • 打包方式(Packaging)为Jar。

  • Java版本选8。

  • 然后依赖(Dependency)增加一个“Eureka Server”和“Spring Boot Actuator”。

  • 最后点击“GENERATE”按钮会下载一个zip文件,解压后就是我们的服务注册与发现项目。我这里因为Artifact填的“eureka-server“,所以得到的文件是eureka-server.zip,后续也会用eureka-server代指这个项目。



解压后,就是Spring Initializr为我们生成的eureka-server项目的基本文件和结构。

如果熟悉maven和git,解压后得到的这些文件就无需多说什么了



这里我们看一下pom.xml文件,主要是看依赖,中文是我加的注解,其他都是Spring Initializr生成的:

<?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>
<!-- Spring Boot版本是2.3.3 -->
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<!-- Spring Cloud 版本是Hoxton.SR8 -->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencies>
<!-- 服务注册中心 Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- Spring Boot 自带的监控功能 Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</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>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>



熟悉Spring Boot的话,会发现大部分都是Spring Boot相关的配置,只有group id为org.springframework.cloud的才是微服务的内容。



注意:Spring Boot的版本是2.3.3,Spring Cloud的版本是Hoxton.SR8,这两者的版本一定要匹配,不然容易出一些莫名其妙的问题。我们可以访问https://start.spring.io/actuator/info 这个地址来获取Spring Boot和Spring Cloud的版本对应关系,比如说:

"Hoxton.SR8":"Spring Boot >=2.2.0.M4 and <2.3.4.BUILD-SNAPSHOT"



3、配置Eureka Server - 实现服务注册与发现

本文的源码已在Github开源,eureka-server的源码地址: https://github.com/xiaoboey/from-zero-to-n/tree/master/zero/eureka-server



打开启动类(EurekaServerApplication.java),添加注解@EnableEurekaServer,开启注册中心能力。

开启 Eureka Server 能力



把src\main\resources\application.properties删除,增加文件application.yml:

server:
port: 8761
address: localhost
servlet:
context-path: /
spring:
profiles:
active: dev
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
#这个服务本身是注册中心,不用自己向自己注册
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
#Spring Boot监控Actuator的配置,对运维帮助很大,开发时很少用到
management:
endpoints:
web:
exposure:
include: ["health", "info", "shutdown"]
endpoint:
health:
show-details: always
shutdown:
enabled: true
server:
port: 7761



4、编译和运行

用Maven进行项目的编译都一样,命令是“mvn install”,我一般习惯再加一个clean先清理一下,所以就是“mvn clean install”。如果想跳过测试加快编译速度,则可以加上“-DskipTests”参数,完整的命令就是“mvn clean install -DskipTests”。



Spring Boot项目的运行,测试时可以在命令行或者PowerShell直接用maven运行:

mvn spring-boot:run



编译成jar文件后,也可以直接运行jar文件,这里以eureka-server为例:

cd target
java -jar eureka-server-0.0.1-SNAPSHOT.jar



5、Eureka Server自带的UI

eureka-server项目启动后,可以在http://localhost:8761查看注册到服务中心的微服务的信息。



目前暂时还没有服务注册到Eureka Server,红色的警告也不用管它,是Eureka的自检,测试环境容易出现这个问题。



6、网关 Spring Cloud Gateway

有了前面eureka-server的搭建经验后,其他微服务我们就简化一下,只列出我觉得重要的内容。

代码: https://github.com/xiaoboey/from-zero-to-n/tree/master/zero/gateway



还是用Spring Initializr进行项目初始化,pom.xml如下:

...
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
...
<dependencies>
...
<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-gateway</artifactId>
</dependency>
...
</dependencies>
...

主要是添加了eureka-client和gateway的依赖。



修改启动类GatewayApplication.java,增加服务发现客户端的注解,表示这是一个Eureka Client,要向服务中心(Eureka Server)注册,以便其他服务可以发现它。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

application.yml的配置如下:

server:
port: 8080
address: localhost
servlet:
context-path: /
spring:
profiles:
active: dev
application:
name: gateway
cloud:
gateway:
discovery:
locator:
#自动发现并路由到微服务
#与服务发现Eureka Server进行结合,通过Service Id转发到具体的服务实例,默认为false。
enabled: true
#小写ServiceId,默认是false
#这里是个坑,从Eureka Server注册中心获取的Service Id是大写的
lower-case-service-id: true
eureka:
client:
service-url:
#注册中心的地址,要对应eureka-server的配置
defaultZone: http://localhost:8761/eureka/
management:
endpoints:
web:
exposure:
include: ["health", "info", "shutdown"]
endpoint:
health:
show-details: always
shutdown:
enabled: true
server:
port: 7080



网关编译启动后,可以去Eureka Server看看网关是否已注册:



7、微服务 Service One

Eureka Server和Gateway都是Spring Cloud自带的为微服务架构服务的功能,接着我们实现一个“真正的业务”。

代码: https://github.com/xiaoboey/from-zero-to-n/tree/master/zero/service-one



还是用Spring Initializr进行项目初始化,这次是一个Spring MVC应用,pom.xml如下:

...
<artifactId>service-one</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>service-one</name>
...
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
...
</dependencies>
...



跟Gateway一样,都通过注解开启服务发现客户端能力,修改启动类ServiceOneApplication.java:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceOneApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceOneApplication.class, args);
}
}



配置文件application.yml:

server:
port: 8081
#把address注释掉,避免网关转发请求过来时,因为地址发生变化而拒绝连接(Connection refused)
#address: localhost
servlet:
context-path: /
spring:
profiles:
active: dev
application:
name: service-one
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
management:
endpoints:
web:
exposure:
include: ["health","info", "shutdown"]
endpoint:
health:
show-details: always
shutdown:
enabled: true
server:
port: 7081



写一个Controller,实现一个hello方法:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OneController {
@RequestMapping("/hello")
public String hello() {
return "Hello!";
}
}



编译启动后,访问http://localhost:8081/hello ,不出意外的话,Service One会立马响应一个“Hello!”。



再访问http://localhost:8080/service-one/hello 看看,这是通过网关的地址8080在访问,是不是一样的效果?说明网关自动把请求转发过来了,而Service One这个应用的信息,网关Gateway是从Eureka Server上的注册信息里获取到的。



8、网关的负载均衡

到目前为止,我们搭建了一个最简单的微服务“集群”,网关(Gateway)接受外部的请求,然后转发到内部的业务实现(Service One)上进行处理,并将结果反馈回去。如果只是这样的话,微服务的优势在哪里呢?这个Service One直接暴露给外部调用也是OK的呀?



我们再扩展一下Service One,提供多一点服务,并且启动多个Service One来看一下。



增加getPort方法,返回服务的端口(application.yml中的server.port):

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OneController {
@RequestMapping("/hello")
public String hello() {
return "Hello!";
}
@Value(("${server.port}"))
private int port;
@RequestMapping("/getPort")
public int getPort() {
return port;
}
}



编译后启动,访问http://localhost:8080/service-one/getPort ,得到一个端口号,这是application.yml中配置的端口(server.port)。



开启一个新的cmd或者shell,进入到service-one项目的pom.xml文件那级路径,再启动一个Service One,并指定不同的端口:server.port=8082,management.server.port=7082

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8082 --management.server.port=7082"
#如果是PowerShell,带=号的参数要用单引号引起来才能正确传递
mvn spring-boot:run -D'spring-boot.run.arguments="--server.port=8082 --management.server.port=7082"'

现在在浏览器里多刷新几次http://localhost:8080/service-one/getPort ,可以发现返回的端口号发生了变化,说明网关把请求转发到Service One的每个实例上。我们可以继续启动更多的服务实例,并模拟大量的请求来测试。测试的结果是网关(Gateway)把请求平均分发给每个实例,体现了微服务在“弹性伸缩、独立部署”上的优势。





9、宕机测试

除了有计划的停止,在实际的生产环境,服务器宕机或其他故障也可能会让服务哑火,失去响应。



前面说“弹性伸缩”,其实我们只测试了“伸”(增加更多的服务实例),还没测试“缩”(减少实例),那就试试停止一个Service One,再去反复刷新getPort看看会发生什么。



反复刷新的情况下,有时会出现响应变慢,然后错误“Internal Server Error, status=500”。这是因为Client失联或者停止后,Eureka Server还没get到这个状态,Gateway继续往停摆的服务转发请求,导致错误。直到注册中心把这个失联的服务剔除掉并通知网关,一切才恢复正常。



宕机导致的服务异常,对终端用户的使用是有影响的,需要解决。并且Spring Cloud Gateway代理转发请求给后端的服务实例,这个工作其实nginx也可以胜任,在某些方面甚至更好。所以到目前为止,微服务的优势还没体现出来。



所谓“师傅领进门,修行在个人”,通过这篇“入门”上手找到感觉之后,剩下的就是针对具体的需求和问题,去慢慢探索解决,积累经验,践行微服务开发方法。



上一篇: 《Spring Cloud 微服务实践(0) - 开篇闲话

下一篇: 《Spring Cloud 微服务实践(2) - Gateway重试机制



发布于: 2020 年 09 月 16 日阅读数: 920
用户头像

xiaoboey

关注

IT老兵 2020.07.20 加入

资深Coder,爱好钓鱼逮鸟。

评论

发布
暂无评论
Spring Cloud 微服务实践(1) - 用Initializr初始化