SpringCloud微服务架构第二篇

上次发的SpringCloud第一篇文章,我看到了大家的评论,我在这里回复给大家,我确实是按照周阳老师讲的SpringCloud的视频来做的笔记,虽然我本身也有周阳老师的SpringCloud的脑图,但是我想自己做做笔记进行一个总结这样会更好,然后发出来给大家一起学习,也欢迎大家进行交流讨论。

最近听到说SpringCloud的Eureka2.0要闭源了,那么即使闭源也没事,我相信Spring那么多强大,肯定会有其他的来代替的,所以不用担心,而且我们只要掌握一个,那么基本上其原理都是一样的。

Eureka是什么?

Netflix在设计Eureka时遵守的就是AP原则

Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现

Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。

而系统中的其他微服务,使用 Eureka 的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。SpringCloud 的一些其他模块(比如Zuul)就可以通过 Eureka Server 来发现系统中的其他微服务,并执行相关的逻辑。
Eureka 和Dubbo的架构对比图:

Eureka包含两个组件:Eureka Server和Eureka Client

Eureka Server提供服务注册服务

各个节点启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到

EurekaClient是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。

现在我们要把我们部门微服务提供者模块注册进Eureka,然后从Eureka在去访问。

Eureka的三大角色

1.Eureka Server 提供服务注册和发现。

2.Service Provider服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到。

3.Service Consumer服务消费方从Eureka获取注册服务列表,从而能够消费服务。

Eureka的构建和使用

1.创建 Eureka服务注册中心的Module

1).在我们已经构建好的四个工程的父工程里面再创建一个名为:microservicecloud-eureka-7001的Eureka服务注册中心Module

2).pom文件添加jar包

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
<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.hhf.springcloud</groupId>
   <artifactId>microservicecloud</artifactId>
   <version>0.0.1-SNAPSHOT</version>
  </parent>
 
  <artifactId>microservicecloud-eureka-7001</artifactId>
 
  <dependencies>
   <!--eureka-server服务端 -->
   <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-eureka-server</artifactId>
   </dependency>
   <!-- 修改后立即生效,热部署 -->
   <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>springloaded</artifactId>
   </dependency>
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-devtools</artifactId>
   </dependency>
  </dependencies>
 
</project>

3).然后在resources资源文件夹下创建application.yml

1
2
3
4
5
6
7
8
9
10
11
server: 
  port: 7001
 
eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己。
    fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。

4).然后创建一个启动类进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.hhf.springcloud;
 
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
 
@SpringBootApplication
@EnableEurekaServer//EurekaServer服务器端启动类,接受其它微服务注册进来
public class EurekaServer7001_App {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001_App.class, args);
    }
}

5).然后启动microservicecloud-eureka-7001这个项目进行一个测试

在浏览器上输入:http://localhost:7001/

测试成功后的效果图:

目前我们还没有注册服务进来,所以是暂时没有服务的。

2.将已有的部门微服务注册进Eureka服务中心

我们将microservicecloud-provider-dept-8001工程注册进microservicecloud-eureka-7001工程Eureka服务中心

1).修改microservicecloud-provider-dept-8001工程的pom文件

添加pom配置文件如下:

1
2
3
4
5
6
7
8
9
<!-- 将微服务provider侧注册进eureka -->
   <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-eureka</artifactId>
   </dependency>
   <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-config</artifactId>
   </dependency>

2).在yml文件的最后添加内容

添加内容如下:

1
2
3
4
eureka:
  client: #客户端注册进eureka服务列表内
    service-url: 
      defaultZone: http://localhost:7001/eureka

3).在DeptProvider8001_App主启动类添加@EnableEurekaClient注解进行自动注册。

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class DeptProvider8001_App
{
  public static void main(String[] args)
  {
   SpringApplication.run(DeptProvider8001_App.class, args);
  }
}

4).测试最终的注册结果

1.先启动microservicecloud-eureka-7001这个工程主类,然后再启动microservicecloud-provider-dept-8001这个工程的主类。

2.在浏览器中输入:http://localhost:7001/ 进行一个测试

3.如无意外,那么我们的部门微服务就成功注册进Eureka服务中心了

效果图如下:

我们看到图中的Application下面已经存在了一个服务名字,那么这个服务名字是哪里来的呢?

那么就是从我们的microservicecloud-provider-dept-8001工程下的resources资源文件中写的一个配置

1
2
3
spring:
   application:
    name: microservicecloud-dept    //这个就是我们在页面中看到的微服务名字

3.完善我们的微服务

目前我们已经完成了注册功能,但是此时我们刷新一下http://localhost:7001/ 这个页面就会看到一段红色的字

这个是Eureka的自我保护的一个机制。

那么为什么Eureka会有这种自我保护的机制呢?

答:1.因为长时间没有另外的一些服务访问,也就是说没有心跳。

2.服务名没有变更,已经有的服务现在没了。

某时刻某一个微服务不可用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存

那么我们怎么解决呢?

答;采用actuator与注册微服务信息进行完善。

actuator是什么?

答:actuator是SpringBoot里面主要用来主管和监控和配置。

那么我们先完善我们的微服务再来解决这种Eureka自我保护的机制

1.我们先修改页面中Status下的主机名字。

2.访问信息有IP信息提示。

3.修改UP (1) - QFVBYIIOO509ZPZ:microservicecloud-dept:8001这个连接点击以后报的404错误

我们先修改第1个问题,修改主机名字。

在microservicecloud-provider-dept-8001下的application.yml文件里面最后一行添加如下配置:

1
2
instance:
    instance-id: microservicecloud-dept8001

因为我们做了热部署配置,所以会重新启动,那么直接刷新该浏览器http://localhost:7001/ 页面即可能看到主机名字已经被修改成功!

接下来我们解决第2个问题:访问信息有IP信息提示。

在我们鼠标移动到Status下面的microservicecloud-dept8001链接时,左下角显示了一个访问信息地址。

我们需要修改这个访问信息,以便更好的完善我们的微服务项目。

那么我们把这个访问信息改成一个主机的自身入驻微服务的ip。

那么需要添加的是在microservicecloud-provider-dept-8001工程下的资源文件下的application.yml

添加内容:

1
2
prefer-ip-address: true     #访问路径可以显示IP地址<code class="">
</code>

添加成功IP显示的效果图:

接下来我们修改第三步:修改UP (1) - microservicecloud-dept:8001这个链接点击以后报的Whitelabel Error Page错误

微服务info内容详细信息

1.先修改microservicecloud-provider-dept-8001这个工程下的Pom文件

添加内容:

1
2
3
4
5
<!-- actuator监控信息完善 -->
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
   </dependency>

2.修改总工程microservicecloud下的pom文件添加构建build信息

添加内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<build>
   <finalName>microservicecloud</finalName>
   <resources>
     <resource>
       <directory>src/main/resources</directory>
       <filtering>true</filtering>
     </resource>
   </resources>
   <plugins>
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-resources-plugin</artifactId>
       <configuration>
         <delimiters>
          <delimit>$</delimit>
         </delimiters>
       </configuration>
     </plugin>
   </plugins>
  </build>

3.再修改microservicecloud-provider-dept-8001工程下的application.yml文件,来添加我们这个微服务的一些描述。

添加内容:

1
2
3
4
5
info:
  app.name: hhf-microservicecloud
  company.name: https://www.jianshu.com/u/c7adbc6b595c  //我的博客地址
  build.artifactId: $project.artifactId$
  build.version: $project.version$

添加成功后的效果图:

那么目前我们微服务的功能已经基本上完善了!!

Eureka的自我保护机制介绍

默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式。

如果你真的想禁用,那么在我们的服务注册中心microservicecloud-eureka-7001工程的application.yml里面添加禁用模式即可。

添加禁用自我保护的内容:

1
2
3
server:
   enable-self-preservation: false<code class="">
</code>

但是eureka自我保护模式希望大家在没有其他特殊业务需求的话就不要去禁用它的自我保护模式

Eure的服务发现

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息

1.修改microservicecloud-provider-dept-8001工程的DeptController

我们添加一个服务发现接口名为:DiscoveryClient

在DeptController中添加内容:

1
2
3
import org.springframework.cloud.client.discovery.DiscoveryClient;
@Autowired
  private DiscoveryClient client;

我们要做一个DiscoveryRest风格的服务,让大家知道这个Discovery服务发现的信息

在DeptController中再添加内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMapping(value = "/dept/discovery", method = RequestMethod.GET)
    public Object discovery()
    {
        //client.getServices()的意思是获取eureka里面有多少个微服务
        //那么我们如果只有一个微服务是部门,后续我们在工作中可能会有越来越多的微服务
        //那么List查出来的就会有很多的微服务,然后进行遍历然后打印出来
        List<String> list = client.getServices();
        System.out.println("**********" + list);
        //client.getInstances()的意思就是在那么多的微服务里面,你指定只找哪一个微服务
        List<ServiceInstance> srvList = client.getInstances("MICROSERVICECLOUD-DEPT");
        for (ServiceInstance element : srvList) {
            //然后打印你指定要找的微服务的ID和主机和端口以及访问地址等信息
            System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"
                    + element.getUri());
        }
        return this.client;
    }

然后添加内容成功后,我们在主启动类上添加一个注解

1
2
@EnableDiscoveryClient //服务发现<code class="">
</code>

然后进入测试

1.先启动microservicecloud-eureka-7001工程

2.再启动microservicecloud-provider-dept-8001工程

3.然后在浏览器中输入:http://localhost:8001/dept/discovery 进行测试

测试的效果图:

然后在控制台也可以看到我们注册的微服务信息

4.我们使用consumer来访问该服务

1).修改microservicecloud-consumer-dept-80工程下的DeptController_Consumer类

添加内容:

1
2
3
4
5
6
//测试@EnableDiscoveryClient,消费端可以调用服务发现
  @RequestMapping(value="/consumer/dept/discovery") 
  public Object discovery()
  {
   return restTemplate.getForObject(REST_URL_PREFIX+"/dept/discovery", Object.class);
  }

添加成功后,我们直接再启动microservicecloud-consumer-dept-80进行测试。

启动成功后在浏览器输入:http://localhost/consumer/dept/discovery 进行测试

测试效果图:

说明从消费者也能成功的访问到

Eureka集群配置

经过我们上面的一步步操作和深入的理解,我们基本上已经理解了Eureka的服务注册与发现。

那么接下来我们进入重点,那就是Eureka的集群配置。

首先先新建microservicecloud-eureka-7002/microservicecloud-eureka-7003这两个工程来实现集群之配置

1.先在我们的总父工程下创建microservicecloud-eureka-7002的maven Module工程

1).按照microservicecloud-eureka-7001为模板粘贴Pom文件到microservicecloud-eureka-7002工程下的Pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
        <!--eureka-server服务端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <!-- 修改后立即生效,热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
 
</project>

2.再从总父工程下再创建microservicecloud-eureka-7003的maven Module工程

1).按照microservicecloud-eureka-7001为模板粘贴Pom文件到microservicecloud-eureka-7003工程下的Pom文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
        <!--eureka-server服务端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <!-- 修改后立即生效,热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
 
</project>

3.修改各自的主启动类

1).将microservicecloud-eureka-7001的主启动类分别给microservicecloud-eureka-7002和microservicecloud-eureka-7003都复制一份即可并分别重新起名字,避免混乱。

效果图:

4.接下来,为了区别集群域名和映射,我们在C:\Windows\System32\drivers\etc下的hosts文件进行修改

在hosts文件中添加以下内容:

1
2
3
4
127.0.0.1  eureka7001.com
127.0.0.1  eureka7002.com
127.0.0.1  eureka7003.com<code class="">
</code>

5.修改3台eureka服务器的application.yml配置

1).先修改第一台Eureka服务器microservicecloud-eureka-7001工程下的application.yml配置

直接替换原先的:

1
2
3
4
5
6
7
8
9
10
11
12
13
server: 
  port: 7001
 
eureka: 
  instance:
    hostname: eureka7001.com #Eureka服务端的实例名称
  client: 
    register-with-eureka: false     #false表示不向注册中心注册自己。
    fetch-registry: false     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url: 
      #单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/       
	 #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

2).然后在microservicecloud-eureka-7001工程的application.yml文件复制两份分别给microservicecloud-eureka-7002工程和microservicecloud-eureka-7003工程的resources文件夹中,并且修改microservicecloud-eureka-7002工程和microservicecloud-eureka-7003工程的application.yml的端口和服务器端的实例名称。

microservicecloud-eureka-7002工程的application.yml文件修改内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
server:
  port: 7002
 
eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    register-with-eureka: false     #false表示不向注册中心注册自己。
    fetch-registry: false     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      #单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/  #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/

microservicecloud-eureka-7003工程的application.yml文件修改内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
  port: 7003
 
eureka:
  instance:
    hostname: eureka7003.com #eureka服务端的实例名称
  client:
    register-with-eureka: false     #false表示不向注册中心注册自己。
    fetch-registry: false     #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      #单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/  
	#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

6.microservicecloud-provider-dept-8001微服务发布到上面3台eureka集群配置中

1.修改microservicecloud-provider-dept-8001的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
server:
  port: 8001
 
mybatis:
  config-location: classpath:mybatis/mybatis.cfg.xml  #mybatis所在路径
  type-aliases-package: com.hhf.springcloud.entities #entity别名类
  mapper-locations:
  - classpath:mybatis/mapper/**/*.xml #mapper映射文件
 
spring:
   application:
    name: microservicecloud-dept 
   datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/cloudDB01
    username: root
    password: 520hhf
    dbcp2:
      min-idle: 5
      initial-size: 5
      max-total: 5
      max-wait-millis: 200
 
eureka:
  client: #客户端注册进eureka服务列表内
    service-url: 
      defaultZone:  #注册进这三台集群
http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: microservicecloud-dept8001   #自定义服务名称信息
    prefer-ip-address: true     #访问路径可以显示IP地址
 
info:
  app.name: hhf-microservicecloud
  company.name: https://www.jianshu.com/u/c7adbc6b595c  #我的博客地址
  build.artifactId: $project.artifactId$
  build.version: $project.version$

好,集群配置完成,我们来测试一下

1.先启动microservicecloud-eureka-7001

2.再启动microservicecloud-eureka-7002

3.再启动microservicecloud-eureka-7003

4.然后再启动microservicecloud-provider-dept-8001

5.依次打开浏览器:http://eureka7001.com:7001/

7001的效果图:

打开 http://eureka7002.com:7002/ 进行测试

7002的效果图:

打开http://eureka7003.com:7003/ 进行测试

7003的效果图:

最终,我们的集群配置成功!!!

Eureka比Zookeeper好在哪里?

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性P在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。
因此
Zookeeper保证的是CP。
Eureka则是AP。

1.Zookeeper保证CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

2.Eureka保证AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
  2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
  3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中

因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。