几年前搭建过Spring Cloud的集群,之前公司使用的也是几年前的集群方案,但近两年Spring Cloud发生了比较大的变化,相关组件也基本都快替换一遍了。因此花了些时间重新学习并做个总结。
本次学习将会包含多篇,从基础的服务注册、调用、负载均衡,到配置中心、网关(熔断、限流)、集群监控等,感兴趣的可以关注。
本文主要讲述服务注册、调用及负载均衡实现。
Spring Cloud新版本提供了多个注册中心,如spring cloud consol、spring cloud zookeeper等,老的euerka也暂时还存在,但未来应该会被移除,建议新项目使用前两者。
本文讲述使用zk做注册中心的实现。
首先需要安装zk,可以直接安装也可以使用docker安装,win上使用docker安装有一个坏处就是需要启动wsl,这个东西本身比较消耗内存,而如果是linux环境就没这个顾虑了,建议直接使用docker安装。
安装过程比较简单,在此不关注。安装完成后我们来一步步创建一个spring cloud的微服务集群;
1. 启动与注册
使用zk作为注册中心后,不需要再额外启动注册中心的服务了(在Eureka做注册中心时,还需要启动一个Eureka Server的节点,实际上我们之前安装的ZK就相当于是Eureka Server这个节点了),我们需要做的只是添加spring-cloud-starter-zookeeper-discovery的依赖,然后服务启动的时候就可以自动注册到zk中去了,同时也可以使用DiscoveryClient获取到集群中对应服务的实例清单。
我们先来启动一个节点,应用名设置为service0.
1.1 Maven依赖
其依赖如下,主要是使用spring-cloud-starter-zookeeper-discovery:
<?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.huashi.sc</groupId>
<artifactId>service0</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.boot.version>2.4.5</spring.boot.version>
<spring-cloud.version>2020.0.2</spring-cloud.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<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>
</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>
1.2 main入口
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
此时就可以启动应用了。
1.3 添加测试接口
启动的应用不包含有任何的接口,为方便测试我们添加一个接口:
@RestController
@RequestMapping("test")
public class TestController {
@GetMapping
public String test() {
return "hello world";
}
}
再次启动,在浏览器中输入地址:http://localhost:8080/test,将会返回hello world。
至此我们就完成了启动一个服务节点并注册到zk中去的这样一个过程,同时我们对外暴露了一个接口用于测试。
1.4 获取实例列表
上面的步骤处理完成后,我们如何知道已经真正的注册上去了呢?如果后续引入了相关的应用监控,当然可以直接看出来。现在我们只能通过DiscoveryClient来获取实例了,如下所示:
@Resource
private DiscoveryClient discoveryClient;
@GetMapping
public String test() {
discoveryClient.getServices().forEach(logger::info);
return "test";
}
1.5 服务命名
还有一步需要处理,我们提供服务的时候,需要为每个服务取一个名称,如果不取这个名称,后续的服务调用就没办法通过名称去进行调用!这个名称可以根据spring.application.name的配置项来指定,可以配置在application.yml中,但更多时候我们会配置在bootstrap.yml中,因此我们在应用的resources目录下添加bootstrap.yml配置文件,其内容如下:
spring:
application:
name: service0
# 同时我们也可以指定应用启动的端口,否则启动第二个节点的时候默认还是使用8080就会出现端口冲突
server:
port: 8080
1. bootstrap配置文件是优先级最低的配置文件,如果存在application开头的配置文件,并且定义有相同名称的配置项,那么会被application中配置的内容给覆盖;
2. 如果添加bootstrap配置文件后启动发现配置未生效,请在启动命令中添加spring.cloud.bootstrap.enabled=true配置,或者直接在系统环境变量中添加这个配置项;
3. 为简化理解,本系列文章后续说的修改配置均是修改的bootstrap配置文件,但在生产上使用时,需要将多数配置都提取到配置中心中去。
然后重启,这样我们就为我们启动的服务指定了名称与端口了。
1.6 ZK地址修改
细心的读者可能会留意到一个问题,我们上面并没有指定ZK的地址,但服务可以成功的注册到ZK中去,其原因是如果我们未指定ZK地址,Spring Cloud会使用默认的地址:localhost:2181,如果我们的ZK不是安装在本机(实际环境肯定会分布在不同环境上),就需要修改这个地址了,需要在配置文件中添加以下配置:
spring:
cloud:
zookeeper:
connect-string: localhost:2181
请注意,本系列文章后续都会省略掉这一配置。
1.7 集群部署情况
此时我们的集群部署情况:
2. 接口调用实现
接下来我们启动一个新的节点来调用原节点中提供的接口;其中服务间调用使用Feign进行;
2.1添加依赖
我们先copy一份第一个服务的代码,命名为service1,然后添加openfeign的依赖(注意也要添加loadbanalance的依赖):
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
...
其它部分与第一个服务节点中是一样的。
2.2 配置修改
然后修改其bootstrap文件:
spring:
application:
name: service1
server:
port: 8081
2.3 EnableFeignClients
然后在main方法所在类中添加EnableFeignClients:
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2.4 添加FeignClient接口
使用FeignClient注解,添加调用接口;
/**
* @author LiuQi 2021/4/26-19:44
* @version V1.0
**/
@FeignClient(name = "service0")
public interface Service0Client {
@GetMapping("test")
String test();
}
注意Feign的使用,有理解困难的可以查找相关文档,简单地说,就是将我们要调用的接口的方法定义copy过来,然后修改路径,这里不能包含有方法体。
2.5 修改测试接口
修改TestController中的测试接口,使用service0Client来进行服务调用:
@RestController
@RequestMapping("test")
public class TestController {
@Resource
private Service0Client service0Client;
@GetMapping
public String test() {
String testResult = service0Client.test();
return testResult;
}
}
2.6 测试
启动service1,然后通过浏览器调用:http://localhost:8081/test,会返回hello world。与直接调用http://localhost:8080/test返回的结果应是一致的。
至此,我们已经启动了两个节点,并且第二个节点通过Feign调用了第一个节点中的接口;我们已经初步完成了一个Spring Cloud集群的搭建。
2.7 集群部署情况
此时集群情况:
3. 接口调用负载均衡
在我们目前搭建的集群中,包含有两个提供不同服务的节点,其中service1调用了service0中提供的接口;实际上,我们可以启动多个同名的节点并将其注册到集群中去,Feign调用时会根据名称取到实例列表,采用一定的负载均衡算法决定调用哪个节点。
3.1 使用默认的负载均衡算法
使用Feign调用时会使用默认的RoundRobin负载均衡算法(简单来说就是轮询,每一次调用一个节点,第二次调用第二个节点。。。)
我们再启动名称为service0的一个节点,在idea中Run/Debug Configurations中,复制一个service0的配置,然后指定环境变量为server-port=8082,如下所示:
然后启动service0-1这个节点,不出意外可以启动成功,并且其端口将会是8082。
这样,包括最开始启动的service0节点,名称为service0的节点我们就启动了两个。
我们为了看出service1调用接口的时候调用的是哪个service0的节点,修改service0项目中的TestController,添加打印:
@RestController
@RequestMapping("test")
public class TestController {
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@GetMapping
public String test() {
logger.info("test");
return "hello world";
}
}
重启service0的两个节点,然后在浏览器中访问service1提供的test接口:http://localhost:8081/test,连续访问多次,然后在idea的debug界面中查看打印,会发现service0/service0-1这两个节点轮流打印test。这说明负载均衡已经生效,service1调用service0的接口时,会按负载均衡算法分配到service0的某个节点上去执行。
3.2 负载均衡算法配置
默认的负载均衡算法实现是RoundRobinLoadBalancer;我们可以修改成随机算法RandomLoadBalancer;
以下变动都在service1服务中进行;
先添加配置类:
/**
* @author LiuQi 2021/4/26-20:41
* @version V1.0
**/
public class LoadBalanceConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
然后修改Service0Client,添加LoadBalanceClient注解:
@FeignClient(name = "service0")
@LoadBalancerClient(name = "service0", configuration = LoadBalanceConfiguration.class)
public interface Service0Client {
@GetMapping("test")
String test();
}
重启后后通过浏览器调用http://localhost:8081/test,可以看到service0的两个节点打印test不再是依次打印了,而是随机打印。
我们也可以基于这种模式来自行实现不同的负载均衡算法。
3.3 关于负载均衡缓存
注意在查找节点列表时,可以使用本地缓存来存储这个列表,默认是包含有缓存的,缓存35秒过期,默认使用的缓存是DefaultLoadBalancerCache。我们可以通过将配置spring.cloud.loadbalancer.cache.enabled修改成false来强制不使用缓存,但不使用缓存会给性能带来一定程度的降低,因此不建议关闭。
另外,如果依赖中包含有com.github.ben-manes.caffeine:caffeine,那么会使用其所提供的缓存而不是DefaultLoadBanancerCache。
3.4 集群情况
此时集群部署情况:
下一篇文章中我们将为集群引入配置中心。
本文暂时没有评论,来添加一个吧(●'◡'●)