一、SpringCloud入门
1.1 SpringCloud是什么?
SpringCloud是一套完整的微服务解决方案,它是基于SpringBoot框架。准确来说,SpringCloud不是一个框架,而是一个很大的容器,它把市面上比较优秀的微服务框架集成在SpringCloud里面,从而简化分布式系统的开发,减少分布式各个模块的开发成本。
SpringCloud提供了构件分布式系统所需要的“全家桶”。它提供了一系列工具,可以帮助开发人员迅速搭建起分布式系统中的公共组件,如配置管理、服务发现、断路器、智能路由、微代理服务、分布式Session等等。SpringCloud能够协调分布式环境中的各个子系统,为各类服务提供模板化的配置方式。
1.2 SpringCloud的优缺点
SpringCloud的优点:
- 集大成者:SpringCloud包含了微服务框架的方方面面。
- 约定优于配置:基于注解,没有配置文件。
- 轻量级组件:SpringCloud组件大部分都是轻量级组件。
- 开发简便、灵活:SpringCloud对各个组件进行了大量封装,简化开发人员的代码量。而且组件之间都是解耦的,开发人员可以灵活选择所需要的组件。
SpringCloud的缺点:
- 项目结构复杂:每一个组件都要创建一个子系统。
- 部署门槛高:SpringCloud项目的部署需要配合Docker等容器技术进行集群部署,对于部署人员的技术要求相对较高。
1.3 SpringCloud和Dubbo对比
核心要素 | Dubbo | SpringCloud |
---|---|---|
服务注册中心 | Zookeeper、Redis | Spring Cloud Netflix Eureka |
服务调用方式 | RPC | RESTFul API |
服务网关 | 无 | Spring Cloud Netflix Zuul |
断路器 | 不完善 | Spring Cloud Netflix Hystrix |
分布式配置 | 无 | Spring Cloud Config |
分布式跟踪系统 | 无 | Spring Cloud Sleuth |
消息总线 | 无 | Spring Cloud Bus |
数据流 | 无 | Spring Cloud Stream基于Redis、Rabbit、Kafka实现的消息微服务 |
批量任务 | 无 | Spring Cloud Task |
从上表可以看出,Spring Cloud的功能比较全面,而Dubbo由于只实现了服务注册和发现,如果要使用其他组件,需要集成额外的模块,这样增加了用户的学习成本和集成成本。而且,SpringCloud本身就是Spring中的一个子项目,与Spring和SpringBoot的集成使用非常方便。
二、在SpringCloud中使用Eureka作为注册中心
2.1 Eureka介绍
Eureka是SpringCloud默认的服务注册和服务发现模块。它是一个高可用的组件,没有后端缓存,每一个实例注册之后,需要定时向服务注册中心发送心跳,告诉服务中心自己的状态,从而确保节点连接的有效性。
2.2 Eureka的运行模式
Eureka Server作为注册服务器,可以构建一个高可用性的集群。在Eureka Server中保存的每一个服务提供者的信息会自动在每一个Eureka Server节点中进行复制。当一个服务的消费者需要使用服务的时候,首先它会到Eureka Server获取一个注册列表,该列表记录了集群中有哪些能够提供服务的节点信息。然后通过一个客户端软件负载的策略在当前服务列表中检索到一个服务节点对应的服务器地址和端口,然后自行与对应的服务器进行交互。
2.3 在Eureka中注册服务
第一步:构建工程目录结构。
从上图看出,在最外层的工程CloudSample继承Spring Boot。然后在CloudSample下面有EurekaSample子模块,在Eureka模块下有EurekaServer子模块。目录结构如下图所示:
第二步:配置pom
在主工程ClouldSample的pom文件中定义全局属性。
<properties>
<!-- 项目默认编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- jdk版本 -->
<java.version>1.8</java.version>
<!-- springclould版本 -->
<spring-cloud.version>Edgware.SR1</spring-cloud.version>
</properties>
在EurekaServer工程中加入Eureka相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
完整的pom文件内容如下:
<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>
<parent>
<groupId>com.xjy</groupId>
<artifactId>EurekaSample</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>EurekaServer</artifactId>
<dependencies>
<!--eureka server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
第三步:在EurekaServer工程的src/main/resources目录下创建application.yml文件。
server:
port: 8260
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
上面配置指定eureka注册中心的端口号为8260。registerWithEureka和fetchRegistry设置为false,代表不需要在其他其他注册中心进行注册。
第四步:在SpringBoot主类中增加@EnableEurekaServer注解。
@EnableEurekaServer
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
第五步:运行SpringBoot,然后再浏览器上输入localhost:8260可以访问到Eureka管理界面。
2.4 定义服务提供者
服务提供者就是专门用于提供服务的Eureka Client。当Client向Server注册服务时候,它会提供一些元数据(如果主机、端口号、URL等等)。Server会定时接收每一个Client发送过来的心跳信息,如果心跳超时,就会把Client从注册列表中删除。
创建服务提供者Client的步骤:
第一步:在CloudSample下新建一个子模块,并命名为EurekaService。
第二步:在src/main/resources目录下创建application.yaml文件。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8260/eureka/
server:
port: 8261
spring:
application:
name: service-demo
上面eureka.client.serviceUrl.defaultZone指定服务注册中心的地址,server.port指定服务的端口号,spring.application.name指定服务名。
第三步:创建主类。
@EnableEurekaClient
@SpringBootApplication
@RestController
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
@Value("${server.port}")
String port;
@RequestMapping("/demo")
public String sayHello(@RequestParam String name) {
return "hi " + name + ", i am from port:" + port;
}
}
在主类上使用@EnableEurekaClient注解,该注解向注册中心注册该服务。而且还定义了一个服务方法sayHello,该方法上通过@RequestMapping注解指定该方法的检索路径。
第四步:运行主类。这时候可以在Eureka管理界面看到该服务。
2.5 定义服务消费者
在微服务架构中,业务被拆分为一个个独立的服务,服务与服务之间的通讯是基于HTTP RESTFul。Spring Cloud提供了两种服务调用的方式:
- Ribbon + restTemplate
- Feign
2.5.1 Ribbon + RestTemplate
Ribbon提供了一系列完善的配置选项,比如连接超时、重试、软件负载均衡算法等等。
Ribbon还提供了其他一些功能:
- 易于与服务发现组件集成
- 使用Archaius完成运行时配置
- 使用JMX暴露运维指标,使用Servo发布
- 多种可插拔的序列化选择
- 异步和批处理操作
- 自动SLA框架
- 系统管理和指标控制台
上面这些功能这里不做详解介绍,下面我们看一下如何使用Ribbon实现Eureka Client的负载均衡。
为了演示负载均衡,这里至少要提供两个或两个以上的服务提供者的实例。首先在EurekaService工程拷贝两份application.yml文件,并改名为application-s1.yml和application-s2.yml,然后在application.yml中指定加载哪个配置文件。
[application-s1.yml]
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8260/eureka/
server:
port: 8261
spring:
application:
name: service-demo
[application-s2.yml]
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8260/eureka/
server:
port: 8262
spring:
application:
name: service-demo
[application.yml]
spring:
profiles:
active: s1
启动服务之前,先把spring.profiles.active设置为s1。启动完成后,再把它修改为s2后再重新启动服务。这时候可以在管理平台上已经启动了两个服务提供者。
注册完成后,注册中心会以列表形式把这两个节点的地址和端口发送给服务的消费者。这时候消费者就可以使用Ribbon自动选择其中任意一个地址和端口来调用远程服务。
消费者Client的构建过程:
第一步:在CloudSample工程下创建一个子模块,并命名为EurekaServiceRibbon。
第二步:在pom中加入eureka相关的依赖。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
</dependencies>
第三步:在src/main/resources目录下新建application.yml文件。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8260/eureka/
server:
port: 8264
spring:
application:
name: service-ribbon
上面指定消费者服务的端口为8264,服务名为service-ribbon。
第四步:创建主类,然后在主类上使用@EnableDiscoveryClient注解。该注解用于向注册中心注册该消费者服务。
@SpringBootApplication
@EnableDiscoveryClient
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
我们还需要注入restTemplate实例,并使用@LoadBalanced注解指定该restTemplate开启负载均衡功能。
第五步:编写测试的业务类。
@Service
public class DemoService {
// 注入restTemplate
@Autowired
RestTemplate restTemplate;
// 测试方法
public String sayHello(String name) {
// 根据服务名和检索路径调用远程服务
return restTemplate.getForObject("http://SERVICE-DEMO/demo?name=" + name, String.class);
}
}
第六步:编写一个Controller,然后调用DemoService的方法。
@RestController
public class DemoController {
@Autowired
DemoService demoService;
@RequestMapping("/demo")
public String test(String name) {
return demoService.sayHello(name);
}
}
第七步:运行主类后,在Eureka管理平台上可以看到该服务。
我们也可以在浏览器上输入http://localhost:8264/demo?name=jacky查看服务的调用情况。
当不断刷新页面的时候,可以看到端口号发生变化。这是由于远程服务负载均衡所产生的效果。
2.5.2 Feign
Feign是一个声明式的伪HTTP客户端,它使得编写HTTP客户端变得简单。使用Feign,只需要创建一个接口后添加相应的注解即可。Feign默认集成了Ribbon,并和Eureka结合,默认也实现了负载均衡。
简而言之,
- Feign是基于接口的注解
- Feign整合了Ribbon
使用Feign构建消费者的过程:
第一步:在CloudSample工程下创建一个子模块,并命名为EurekaServiceFeign。
第二步:在pom文件中加入依赖。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
</dependencies>
完整的POM文件内容如下:
<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>
<parent>
<groupId>com.xjy</groupId>
<artifactId>CloudSample</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>EurekaServiceFeign</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
第三步:在src/main/resources目录下新建application.yml文件。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8260/eureka/
server:
port: 8265
spring:
application:
name: service-feign
上面指定消费者服务的端口为8265,服务名为service-feign。
第四步:创建主类,然后在主类上使用@EnableDiscoveryClient和@EnableFeignClient注解。EnableFeignClient注解用于开启Feign功能。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
第五步:定义feign接口,并使用@FeignClient注解指定调用的服务。
@FeignClient("service-demo")
public interface DemoService {
@RequestMapping(value="/demo", method=RequestMethod.GET)
String sayHello(@RequestParam("name") String name);
}
第六步:编写一个Controller,然后调用DemoService的方法。
@RestController
public class DemoController {
@Autowired
DemoService demoService;
@RequestMapping("/demo")
public String test(String name) {
return demoService.sayHello(name);
}
}
第七步:运行主类后,在Eureka管理平台上可以看到该服务。
我们也可以在浏览器上输入http://localhost:8265/demo?name=jacky查看服务的调用情况。
三、在SpringCloud使用Consul作为注册中心
关于Consul介绍可以参考帖子:https://blog.csdn.net/zhongliwen1981/article/details/92801783,这里我们看一下如何在Spring Cloud中使用Consul。
第一步:启动Consul服务。
$ consul agent -dev -client 0.0.0.0
启动完成后,可以在浏览器上输入192.168.31.20:8500进入Consul管理后台。
第二步:新建一个maven工程,并命名为ConsulServerDemo,然后加入consul相关的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
完整的pom文件内容如下:
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.xjy</groupId>
<artifactId>ConsulServerDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
注意上面springboot和spring cloud版本的对应问题。如果springboot有些版本和springcloud不一致,可能会导致服务注册失败。
第三步:配置consul服务中的地址。
spring:
cloud:
consul:
host: 192.168.31.20
port: 8500
discovery:
healthCheckPath: /health
healthCheckInterval: 15s
instance-id: consul-demo
application:
name: consul-demo
server:
port: 8902
spring.cloud.consul.host:注册中心地址
spring.cloud.consul.port:注册中心端口
spring.cloud.consul.discovery.healthCheckPath:服务器健康检查接口
spring.cloud.consul.discovery.healthCheckInterval:每次检查的时间间隔
spring.cloud.consul.discovery.instance-id:服务实例名称
第四步:创建主类,然后使用@EnableDiscoveryClient注解。
@SpringBootApplication
@RestController
@EnableDiscoveryClient
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@RequestMapping("/demo")
public String demo() {
return "hi, i am Consul....";
}
}
第五步:启动服务,然后在Consul控制台上可以看到consul-demo服务已经被注册成功。
四、构建客户端模块
第一步:创建客户端maven工程,并命名为ConsulClientDemo,然后引入服务端相同的依赖。
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath />
</parent>
<groupId>com.xjy</groupId>
<artifactId>ConsulClientDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
第二步:在application配置文件中配置服务。
spring:
cloud:
consul:
host: 192.168.31.20
port: 8500
discovery:
register: false
application:
name: consul-client
server:
port: 8903
设置spring.cloud.consul.discovery.register参数为false,代表客户端不需要向服务注册中心进行注册。
第三步:在主类中添加@EnableDiscoveryClient注解,并注入一个ConsulDiscoveryClient对象作为Consul请求的客户端。
@SpringBootApplication
@EnableDiscoveryClient
@EnableAutoConfiguration
public class App implements CommandLineRunner {
@Autowired
private ConsulDiscoveryClient client;
@Autowired
private RestTemplate restTemplate;
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Override
public void run(String... args) throws Exception {
// 获取远程服务对象
List<ServiceInstance> list = client.getInstances("consul-demo");
ServiceInstance serviceInstance = list.get(0);
System.out.println("服务地址:" + serviceInstance.getUri());
System.out.println("服务名称:" + serviceInstance.getInstanceId());
String info = restTemplate.getForObject(serviceInstance.getUri().toString() + "/demo", String.class);
System.out.println("服务返回的结果:" + info);
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
上面主类还继承了CommandLineRunner,并实现类它的run方法。在run方法中使用客户端对象提供的api完成远程服务调用。
第四步:运行客户端,运行结果如下图所示: