计算机系统应用教程网站

网站首页 > 技术文章 正文

Spring Cloud2020版本集群搭建实例——服务注册及调用

btikc 2024-09-20 14:51:22 技术文章 25 ℃ 0 评论

几年前搭建过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 集群情况

此时集群部署情况:

下一篇文章中我们将为集群引入配置中心。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表