开端

抛开别的不说,在这大学三年的时间里,我总是有意无意的回避开了一门技术,那就是Spring Cloud。因为它复杂繁多的模块而让我望而生畏,因为它永远排在我目标技术栈的最后一位。即使前继的技术栈基本已经粗略撸了个遍,我还是迟迟不敢在分布式服务上动刀子。想想我的大学生活也不久了,早晚要整活微服务,然后写我的毕业设计。So,Hi there,I'm TremblingBoy.

了解

在初步了解了微服务框架之后,我总结出了几点微服务的优劣之处:

优点

  • 拆分项目,独立部署,耦合度低。也就是说不同服务之间不需要高度的依赖。
  • 快速启动,拆分的项目依赖库减少,代码量减少,启动速度必然加快。
  • 敏捷开发,发布新版本只需发布对应服务。
  • 高校代码复用,所有底层实现都可以以接口方式提供。

缺陷

  • 更多的网络问题、容错问题、调用关系等。
  • 独立的数据库面临更多的挑战,尤其是事务的问题。
  • 测试难度提升,API的修改涉及到所有调用它的服务。
  • 运维难度提升,部署和监控变得非常复杂。

总而言之,能量守恒,有得必有失,不可能存在完美无缺的代码,我相信微服务的少量缺陷,都能被它的大量优势所弥补。

常用模块

  • Eureka:服务注册中心,用于服务管理。
  • Ribbon:基于客户端的负载均衡组件。
  • Hystrix:容错框架,能够防止服务的雪崩效应。
  • Feign:Web服务客户端,能够简化HTTP接口的调用。
  • Zuul:API网关,提供路由转发、请求过滤等功能。
  • Config:分布式配置管理。
  • Sleuth:服务跟踪。
  • Stream:构建消息驱动的微服务应用程序的框架。
  • Bus:消息代理的集群消息总线。

Boot复习

要想使用Spring Cloud,有必要使用Spring Boot来简化操作。之前没有写过Spring Boot的文章,今天在这先简单创建一个Spring Boot项目吧,有机会咱们再整装待发杀入Spring Boot。

准备

首先准备好以下几个开发环境:

  • JDK1.8(说实话还是8好用,我之前的开发环境用的11部署的时候好多问题)
  • Maven(版本不规定了,用新的就好)
  • IDEA(如果使用Eclipse请安装Spring Tools 4 for Eclipse和Lombok插件)

创建项目

咱们首先打开IDEA,创建一个Maven项目。

截屏2021-03-08 上午11.16.57

先在pom里加个阿里的仓库。

<repositories>
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

设置JDK为1.8,加入spring-boot-maven-plugin插件,此插件可以将项目打包成jar文件直接启动。

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

添加启动包。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

编写启动类,并且使用@SpringBootApplication注解标记启动类。

package com.ky.kevin;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
}

编写REST接口,@GetMapping相当于@RequestMapping(method=RequestMethod.GET),并且写上映射的URL/hello

package com.ky.kevin.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

简单几步之后,启动我们的项目。默认的Tomcat端口号是8080,我们在浏览器访问localhost:8080/hello,成功获取到了hello的返回。简单的回顾到此为止。

截屏2021-03-08 上午11.33.45

热部署

开发过程中经常要修改代码,虽然后端方面的修改相比前端不那么需要立竿见影的看到,但是后台的启动时间明显慢很多,如果启动热部署,一次可能节省10秒,随着重启的次数增加,好处就明显了很多,你一定会对热部署的功能赞不绝口。

那么接下来,往pom里添加热部署的依赖。

<!--热部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

如果使用的是IDEA,请前往设置开启自动构建-Build project automatically。

截屏2021-03-08 上午11.47.49

接下来同时按下Shift+Ctrl+Alt+/,Mac用户按下Command+Shift+Option+/,然后选择Registry,把热编译选项勾上后重启IDEA。

截屏2021-03-08 上午11.45.26

我们再来启动项目,访问接口,然后修改接口返回的hello为world后保存,但是不重启项目。我们发现,项目自动进行了悄无声息的重启。虽然这样的操作和人为的点击暂停和点击开始并无差别,但是我们可以按下Command+S保存项目后继续编写代码,直到需要测试项目时再点开网页,这时候已经可以无缝衔接进行测试了。

Eureka注册中心

Origin

Eureka是Netflix微服务套件的一部分,做了二次封装,主要负责实现微服务架构中的服务治理功能。这是一套基于REST的服务,提供了基于Java的客户端组件,能够非常方便地将服务注册到Spring Cloud Eureka中进行统一管理。

除了Eureka,还有Zookeeper(阿里Dubbo专用)、Consul、Etcd等。当然Spring还是首选Eureka。根据分布式系统领域著名的CAP定理,C代表数据一致性,A代表服务可用性,P代表服务对网络分区故障的容错性。这三个特性在如何分布式系统中都不能同时满足,最多同时满足两个,而Eureka符合AP原则,Zookeeper符合CP原则,具体想要实现什么样的条件还是根据具体项目来定。

Server

重新创建一个Maven项目,取名为eureka-server,然后修改pom.xml为如下配置,请根据实际情况作修改。

根据我三个多月小时的尝试,自行搭建注册中心失败,原因可能是Spring Boot的版本不兼容,那么既然我不能从底层构建,可以选择使用IDEA提供的自动初始化功能,又便宜不占王八蛋。所以接下来给大家展示一下如何只通过点击鼠标初始化一个Eureka注册中心。

首先打开IDEA,选择创建新项目。点击其中的Spring Initialize,选择Java1.8后一路next。

截屏2021-03-10 下午2.16.35

填写好名称等信息,也可以直接用默认的,还要再选择一次Java的版本(我这默认11,不太好用),打包方式不用改。

截屏2021-03-10 下午2.17.41

接下来选择Spring Cloud Discovery,勾上其中的Eureka Server即可,接下来选择好项目路径就可以完成构建啦。如果是第一次创建,需要花大概5分钟的时间下载所需的jar包等内容。

截屏2021-03-10 下午2.17.50

构建完成之后,还没有满足全部的启动要求,首先打开自动生成的启动类,加上一个注解@EnableEurekaServer,代表启用Eureka注册中心服务。

然后我们修改Resource中的application.properties后缀名为yml(不修改也行,我这里推荐使用yml文件作为Spring的配置文件)。文件内还没有任何信息,所以我们要加上以下的代码:

spring:
  application:
    # 应用的名字
    name: eureka-server
server:
  # Eureka注册中心端口号
  port: 14001
eureka:
  instance:
    hostname: localhost
  client:
    # 由于该应用为注册中心,所以设置为false,代表不向注册中心注册自己
    register-with-eureka: false
    # 由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以也设置为false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

最后再瞟一眼pom,应该没什么问题,那就直接启动程序吧。打开浏览器访问`localhost:14001就可以成功进入注册中心了。

截屏2021-03-10 下午2.25.28

这样一来其实也省去了很多步骤,免得大家面对不同的环境和更新换代的pom文件不知所措。

Client

搭建好注册中心,接下来我们注册一个服务试试能不能正常调用接口。

重新创建一个叫eureka-client的项目,按照Server项目的流程来,在选择依赖库这一步取消选择Eureka Server,选上Eureka Discovery Client,其他一致。

创建完成之后,依旧是修改启动类,这一次我们加的注解是@EnableDiscoveryClient,注意不要写错。

接下来也是配置文件,和之前的略有不同,尤其是一个实例id中的端口号是当前的client端口,而客户端默认服务url则是之前设置的server端口(一个是14001,一个是8081,仔细甄别,选择空闲的端口就行)。

spring:
  application:
    # 应用的名字
    name: eureka-client
server:
  # Eureka服务提供者端口号
  port: 8081
eureka:
  instance:
    hostname: localhost
    # 采用IP注册
    prefer-ip-address: true
    # 实例id格式
    instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:14001/eureka/

现在在启动Server的情况下再启动Client,打开之前的`localhost:14001,就可以看到实例已经创建成功。

截屏2021-03-10 下午5.53.47

大家可以试试定义一个hello接口然后访问,注意使用之前Boot复习这一小节里的知识点哦,这是可以实现的,就不多赘述了。

Client Article Service

最后我们再试试消费者模块,消费刚刚编写的hello模块。同理和Client的流程一样,创建第三个项目名为eureka-client-article-service。此时yml文件中的文件名也同步修改,端口咱们设置为8763。

接下来创建一个RestTemplate,这是Spring提供用于访问Rest服务的客户端,提供多种远程访问Http服务的方法,并且我们在这里使用@LoadBalanced自动构造LoadBalancerClient接口的实现类并注册到Spring容器中。

package com.ky.kevin.configuration;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class BeanConfiguration {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

接下来我们创建一个控制器,直接使用Eureka中注册的服务名称来访问hello接口,但是在实际的浏览器中还是需要使用具体的ip而不是名字。

package com.ky.kevin.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@RequestMapping("/article")
public class ArticleController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/callHello")
    public String callHello() {
        return restTemplate.getForObject("http://eureka-client/test/hello", String.class);
    }
}

如此,当我们访问localhost:8082/article/callHello时,就会自动调用localhost:8081/test/hello的接口。

Security

抛开本地测试的环境,当我们把项目部署到服务器上时,公网IP必然直接暴露,任何用户都可以直接访问到Eureka管理的信息,所以我们再加入一个安全验证,使用Spring-Security进行认证。

现在我们回到注册中心,修改pom并加入下列内容。

<!--Spring Security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

打开yml,添加下列内容:

spring:
  security:
    user:
      name: xxx
      password: 123456

创建一个配置类:

package com.ky.kevin.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭csrf
        http.csrf().disable();
        // 支持httpBasic
        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .httpBasic();
    }
}

大功告成,现在重启项目打开Eureka管理页面,发现需要输入密码才可以查看信息。

TIPS:

  1. 记得修改每一个服务的DefaultZone中的地址,加上用户名和密码,比如:defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:14001/eureka/
  2. 在实际测试时我的端口号进行了修改,请以文字为准,图片仅供参考。

Cluster

安全问题解决之后,还要考虑一个高可用的性能问题,这就要涉及到集群的搭建了。Eureka的集群搭建比较简单:每一台Eureka只需要在配置中指定另外多个Eureka的地址就可以实现一个集群的搭建了。打个比方,有ABC三台机器,那么我们要实现:

  • 将A注册到B和C上
  • 将B注册到A和C上
  • 将C注册到A和B上

对于集群的实现,需要修改我们之前server的配置。把yml文件中关于port和defaultZone的内容注释掉,这两块我们要单独写在其他配置文件中。首先我们要想好集群中每个注册中心的名字叫什么,我这里就演示一个双机集群,一个叫master,一个叫slaveone,所以我们修改本机的host设置以下内容(关于如何修改host自行百度,mac用户的修改方法为在terminal中输入sudo vi /etc/hosts):

127.0.0.1 eurekamaster
127.0.0.1 eurekaslaveone

保存好后(先按Esc再输入wq后回车),我们回到server项目的yml文件。将hostname修改为eurekamaster,表示这是一台机器。然后添加一下内容:

spring:
  profiles:
    active: master

代表我们启用master配置,但是master配置还没有写,所以在application.yml同级创建一个叫application-master.yml的配置文件,在里面输入我们之前注释掉的内容:

server:
  port: 14001
eureka:
  client:
    service-url:
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eurekaslaveone:14002/eureka/

需要注意的是,这里defaultZone中的host我们填写的是另一个机器的名字:eurekaslaveone,并且端口也是另一台机器的端口。

接下来再创建一个叫application-slaveone.yml的配置文件,输入这些内容:

server:
  port: 14002
eureka:
  client:
    service-url:
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eurekamaster:14001/eureka/

第一个配置代表第一台机器,第二个配置代表第二台机器,第二个配置中的host为第一台机器的hosteurekamaster。

因此当我们设置active为master时,就会自动调用application-master中的配置。

接下来的操作步骤大家应该已经猜到了,没错,就是和创建第一个注册中心一样的步骤再创建一个eureka-server-cluster。同样的为启动类添加注释,拷贝第一个注册中心的三个yml文件,将host修改为eurekaslaveone,将active修改为slaveone,其他不变。

此时我们再分别启动两个项目,就可以访问到这一个小集群了。(但是这只是本地测试的方法,至于服务器上如何部署,还有待学习,等学完Spring Cloud上手项目之后应该可以给大家一个解决方案)下面我们来看看现在访问两个不同端口显示的内容。

截屏2021-03-11 下午5.07.28

上图为14001端口的Eureka显示内容,可以发现复制品这一行多了一个eurekaslaveone,代表14002端口运行了一个它的复制品注册中心。

截屏2021-03-11 下午5.10.21

上图为14002端口的Eureka显示内容,同样的显示的是14001端口的eurekamaster,也就是说,现在完成了A注册到B,B也同时注册到A的集群,至于更多数量的集群也是同理。

现在我们转到Client,想一想如何将服务注册到两个注册中心去。很简单,我们只需要修改defaultZone,同时写上两个url即可。

defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:14001/eureka/,/
http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:14002/eureka/

这里的host我们就仍旧使用localhost即可,不需要修改特定的名称,端口号对应集群中的两个注册中心。然后我们打开任意一个注册中心,都可以看到下面的显示。

截屏2021-03-11 下午5.23.46

集群的其中一个作用就是防止某一个注册中心因为一些问题而无法使用时服务的中断,所以我们现在随意中断其中一个注册中心的程序,就比如说master。这时候访问14001端口的网页,已经无法显示,但是访问14002端口的网页仍旧存在。IDEA中也会报错,提示找不到服务,但是没关系,我们访问localhost:8082/article/callHello试试,访问8081端口的接口操作成功!

截屏2021-03-11 下午5.26.27

此时我们重新开启master注册中心并且访问14001端口,发现两个模块重新注册到了master上,在此期间整个程序没有出现任何的问题。这样一来建立集群的目的也就达到了。

Protection

关于Eureka还有一个自我保护的功能,当你在控制台看到一串红色字体时,表明Eureka Server进入保护模式了。微服务每隔一段时间就会向注册中心发送一个心跳包,如果在15分钟内没有收到85%的心跳包,就会触发这个模式,被认定为网络故障。在该模式下,Eureka会保护服务注册表中的信息,不再删除服务注册表中的数据。

截屏2021-03-11 下午5.46.14

由于它既然开发出这一种模式,肯定是有利有弊的,所以我只提一下,只推荐在开发环境下关闭这个功能。(发送心跳包速率,检测时间等都可以修改)如果想要停用自动保护,想要在微服务失效时立即销毁实例的同学,可以为eureka-server的yml文件中添加以下代码:

eureka:
    server:
        enableSelfPreservation: false

Extend

最后来讲一些拓展应用,讲完之后我们的Eureka部分就正式结束啦。

REST API

Eureka作为注册中心,其本质是存储了每个客户端的注册信息,Ribbon在转发的时候会获取注册中心的服务列表,然后根据对应的路由规则来选择一个服务给Feign来进行调用。

微服务是没有依赖的,可以独立部署,端口也可以随机分配,但是API网关的部署则不能这样,大部分使用Nginx作为负载,就必须知道API网关有几个节点。我们可以利用Eureka提供的API来获取某个服务的实例信息,做到自动部署和扩容。具体的请查看官方文档。

比如,我们可以使用localhost:14001/eureka/apps/eureka-client来获取名为eureka-client的应用的信息,也可以使用Postman进行测试。由于我们开启了Spring Security的安全认证,所以需要在Authorization中开启Basic Auth,如下图:

截屏2021-03-13 下午1.08.57

这样就可以正常获取到信息了。

元数据

我们使用Eureka自带API获取到的都叫标准元数据,还可以自定义元数据,格式如下:

eureka:
    instance:
        metadataMap:
            key: value

如此,就会在原本的信息中添加一对自定义的元数据。使用元数据可以做一些扩展信息,比如灰度发布之类的功能。

EurekaClient

其实除了使用API,我们还可以用EurekaClient来获取到我们想要的信息。

package com.ky.kevin.controller;

import com.netflix.discovery.EurekaClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {
    @Qualifier("eurekaClient")
    @Autowired
    private EurekaClient eurekaClient;

    @GetMapping("/infos")
    public Object serviceUrl() {
        return eurekaClient.getInstancesByVipAddress("eureka-client", false);
    }
}

我们也可以使用Spring Cloud重新封装过的DiscoveryClient来获取信息,大同小异。

健康检查

在某些情况下,比如MongoDB出现了异常,但是应用进程还是存在,这就意味着应用可以继续上报心跳,保持自己的信息不被剔除。我们可以开启健康检查,及时将应用的实例信息下线,隔离正常请求,防止出错。

eureka:
    client:
    healthcheck:
      enabled: true

服务上下线监控

在某些特定的需求下,我们需要对服务的上下线进行监控,目前支持的事件:

  • EurekaInstanceCanceledEvent 服务下线事件
  • EurekaInstanceRegisteredEvent 服务注册事件
  • EurekaInstanceRenewedEvent 服务续约事件
  • EurekaRegistryAvailableEvent 注册中心启动事件
  • EurekaServerStartedEvent 服务启动事件

我们可以为注册中心添加下面这个类来进行监控:

package com.ky.kevin.component;

import com.netflix.appinfo.InstanceInfo;
import org.springframework.cloud.netflix.eureka.server.event.*;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class EurekaStateChangeListener {
    @EventListener
    public void listen(EurekaInstanceCanceledEvent event) {
        System.err.println(event.getServerId() + "\t" + event.getAppName() + "服务下线 ");
    }

    @EventListener
    public void listen(EurekaInstanceRegisteredEvent event) {
        InstanceInfo instanceInfo = event.getInstanceInfo();
        System.err.println(instanceInfo.getAppName() + " 进行注册 ");
    }

    @EventListener
    public void listen(EurekaInstanceRenewedEvent event) {
        System.err.println(event.getServerId() + "\t" + event.getAppName() + " 接受到心跳包 ");
    }

    @EventListener
    public void listen(EurekaRegistryAvailableEvent event) {
        System.err.println("中心启动 ");
    }

    @EventListener
    public void listen(EurekaServerStartedEvent event) {
        System.err.println("服务启动 ");
    }
}

总结

在本文中,我们首先了解了一下Spring Cloud的一些基础模块,然后复习了一下必须要使用到的Spring Boot。准备好基础之后,从Eureka注册中心开始,了解了注册中心的起源。构建了一个注册中心,又写了一个客户端服务注册到了中心。尝试使用Article Service调用Service中的服务。为项目添加了安全认证后,建立了一个小型的注册中心集群。尝试关闭了保护模式,然后测试了几个小型的拓展功能。

可以说,这篇文章非常详细的带领大家了解了Eureka的知识点,所有必要的代码和截图也都已经提供。那么接下来我们就要开始学习客户端负载均衡Ribbon了。本文到此结束,有问题可以评论问我。

:)