我在自己家里用近10台树莓派搭建了一个集群,平时在这个集群上学习一些技术知识。虽然这些机器性能不太好,但用起来比虚拟机和Docker要方便一些,应为虚拟机要装在自己电脑上,每次都要重启。

这个源代码是基于这个树莓派集群搭建的微服务的框架,采用Spring Cloud框架,包括了服务网关(Spring Cloud Gateway),服务器注册与发现(Spring Cloud Eureka),配置中心(Spring Cloud Config),服务调用(Spring Cloud OpenFeign),Redis数据库集群,MySQL数据库集群,RabbitMQ消息队列集群。

希望对想学习Spring Cloud微服务的同学有所帮助。

服务网关

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。

Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

pom.xml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
<?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>
        <version>2.5.4</version>
        <relativePath/>
    </parent>

    <groupId>net.liwenbo.cloud</groupId>
    <artifactId>cloud-gateway</artifactId>
    <version>1.0.0</version>
    <name>gateway</name>
    <description>Spring Cloud Gateway</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.3</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>net.liwenbo.cloud</groupId>
            <artifactId>cloud-common</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </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>

    <!-- 在maven中添加如下配置 -->
    <profiles>
        <profile>
            <id>dev</id>
            <properties>
                <profiles.active>dev</profiles.active>
            </properties>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <profiles.active>prod</profiles.active>
            </properties>
        </profile>
    </profiles>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

application.yml配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
server:
  port: 9000

spring:
  application:
    name: cloud-gateway
    
# Eureka Server 配置
eureka:
  client:
    service-url:
      defaultZone: http://192.168.3.202:9030/eureka/
  instance:
    #租约到期,服务失效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
    #这里修改为6秒
    lease-expiration-duration-in-seconds: 6
    #租约续约间隔时间,默认30秒,这里修改为3秒钟
    lease-renewal-interval-in-seconds: 3
    prefer-ip-address: true

# 配置路由,这里配置的是十分简单的规则,以/member/开头的,就路由到会员微服务,以/product/开头的,就路由到产品微服务
# 以/order/开头的,就路由到订单微服务。
spring:
  main:
    web-application-type: reactive
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: cloud-member-api
          uri: lb://cloud-member-api
          predicates:
            - Path=/member/**
        - id: cloud-product-api
          uri: lb://cloud-product-api
          predicates:
            - Path=/product/**
        - id: cloud-order-api
          uri: lb://cloud-order-api
          predicates:
            - Path=/order/**

Spring Cloud Gateway的功能十分强大,路由的规则可以十分灵活的配置,还包括熔断、限流的功能等。

熔断

暂未完善

限流

暂未完善

服务注册发现

服务注册与发现使用Spring Eureka组件,运行效果如下图所示。

image-20210911212544778

Eureka集群的搭建过程比较容易,可以参考官方的文档。

pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?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>
		<version>2.5.4</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<groupId>net.liwenbo.cloud</groupId>
	<artifactId>eureka-server</artifactId>
	<version>1.0.0</version>
	<name>eureka-server</name>
	<description>Spring Cloud Eureka Server</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>2020.0.3</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</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>

applcation.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
  port: 9030

eureka:
  instance:
    prefer-ip-address: true
    # 客户端每2秒发送一次心跳
    lease-renewal-interval-in-seconds: 2
    # 客户端服务过期时间为8秒
    lease-expiration-duration-in-seconds: 8
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  server:
    enable-self-preservation: false
    responseCacheUpdateIntervalMs: 3000
    # eureka定时检查实例时间, 如果在lease-expiration-duration-in-seconds时间内未收到实例心跳则注销该实例
    eviction-interval-timer-in-ms: 3000
    #关闭一级缓存,让客户端直接从二级缓存去读取,省去各缓存之间的同步的时间
    use-read-only-response-cache: false
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${spring.cloud.client.ip-address}:${server.port}/eureka/ 

配置中心

Spring Cloud Config可以为微服务架构中的应用提供集中化的外部配置支持。Spring Cloud Config 分为服务端和客户端两个部分。服务端被称为分布式配置中心,它是个独立的应用,可以从配置仓库获取配置信息并提供给客户端使用。客户端可以通过配置中心来获取配置信息,在启动时加载配置。Spring Cloud Config 的配置中心默认采用Git来存储配置信息,所以天然就支持配置信息的版本管理,并且可以使用Git客户端来方便地管理和访问配置信息。

简单地理解,Spring Cloud Config配置中心就是将项目中的配置信息统一通过版本管理的方式进行集中管理。

pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/>
    </parent>

    <groupId>net.liwenbo.cloud</groupId>
    <artifactId>config-server</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>config-server</name>
    <description>Config Server</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.3</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</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>	

ConfigServer启动类

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {

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

application.yml配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server:
  port: 9020

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/winbomb/config.git
          force-pull: true

远程调用

在我们三个微服务中,存在服务间需要相互调用的情况,例如会员需要知道自己的订单,由于将数据库进行了拆分,会员微服务无法访问到订单库,因此需要调用订单微服务,来获取会员的订单信息。

Spring Cloud中可以使用OpenFeign来完成服务间的远程调用。OpenFeign的核心作用是为 HTTP 形式的 Rest API 提供了非常简洁高效的 RPC 调用方式。

例如我们的会员的微服务中,需要调用订单微服务,根据会员id获取该会员的订单列表。

1)会员微服务进行配置

在pom中添加openfeign的以来

1
2
3
4
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

使用 @EnableFeignClients 注解应用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class MemberServiceApplication {

    public static final String BASE_PACKAGE = MemberServiceApplication.class.getPackage().getName();

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

2)在会员微服务中定义个一个接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@FeignClient("cloud-order-api")
public interface IOrderClient {
    
    /**
     * 根据会员ID获得会员订单列表
     *
     * @param mbrId 会员id
     * @return 订单列表
     */
    @GetMapping("/order/member/{mbrId}/orders")
    List<Order> getOrdersByMemberId(@PathVariable Integer mbrId);

}

3)在订单微服务中实现这个请求路径

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@RestController
@RequestMapping("order")
@Slf4j
public class OrderController extends ApiController {

    @Autowired
    private IOrderService orderService;

    // ...

    @GetMapping("/member/{mbrId}/orders")
    public List<Order> getOrdersByMemberId(@PathVariable Integer mbrId) {
        return orderService.getOrdersByMemberId(mbrId);
    }
}

关于OpenFeign的工作原理:https://www.cnblogs.com/651434092qq/p/14260784.html