微服务概念

服务注册中心

Eureka

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。

⚠️ 2018年7月份,Eureka官方停止对Eureka2.0进行维护与更新!

核心概念

如下服务注册调用示意图,服务提供者和服务的消费者,本质上也是 Eureka Client 角色。整体上可以分为两个主体:Eureka Server 和Eureka Client

20190703102014756

搭建Eureka Server服务注册中心

  1. 引入依赖

    1
    2
    3
    4
    5
    <!-- eureka-server -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
  2. 在SpringBoot主启动类中开启EurekaServer

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaServer // 注册中心
    public class EurekaServer {
    public static void main(String[] args) {
    SpringApplication.run(EurekaServer7001.class,args);
    }
    }
  1. 配置Eureka

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    spring:
    application:
    name: eureka-server # 应用名称
    server:
    port: 7001 # 更改端口 默认8761端口

    eureka:
    instance:
    hostname: localhost # 实例主机
    client:
    fetch-registry: false # 自己就是服务注册中心,不需要去检索服务
    register-with-eureka: false # 不向服务注册中心注册自己
    service-url:
    defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 服务注册需要依赖这个地址

  2. 启动项目,访问http://localhost:7001,看见以下界面则搭建成功!

    屏幕快照 2020-11-22 下午4.57.51

搭建Eureka Cilent

  1. 引入依赖

    1
    2
    3
    4
    5
    <!-- eureka-cilent -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-cilent</artifactId>
    </dependency>
  2. 在SpringBoot主启动类中开启EurekaCilent

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableEurekaClient // eureka cilent
    public class PaymentMain8001 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentMain8001.class,args);
    }
    }
  3. 配置eureka

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server:
    port: 8001 # 端口
    spring:
    application:
    name: cloud-payment-service # 项目名称
    eureka:
    client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
    defaultZone: http://localhost:7001/eureka/
  4. 首先启动Eureka Server,然后再启动Eureka Cilent。看到以下界面便是成功

    屏幕快照 2020-11-22 下午6.50.43

Eureka集群

实际工作生产环境中服务注册中心肯定不止一个。若只有一个,万一这个注册中心宕机了,那么整个微服务环境将不可用。为了提高整个系统的高可用性,所以可考虑部署注册中心集群,实现负载均衡 + 故障容错

搭建Eureka Server集群

⚠️ Eureka集群主要特点: 相互注册、相互守望

未命名绘图

  1. 修改/etc/hosts文件 ⚠️非常重要!

    1
    2
    3
    4
    5
    # Eureka Server集群hostname
    # 实际生产环境将127.0.0.1换成Eureka Server服务器的ip
    127.0.0.1 eureka7001.com # 实例1
    127.0.0.1 eureka7002.com # 实例2
    127.0.0.1 eureka7003.com # 实例3
  2. 配置Eureka Server实例1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    spring:
    application:
    name: eureka-server

    server:
    port: 7001

    eureka:
    instance:
    hostname: eureka7001.com # eureka server实例名称
    client:
    fetch-registry: false # 自己就是服务注册中心,不需要去检索服务
    register-with-eureka: false # 不向服务注册中心注册自己
    service-url: # 集群.将本服务注册到集群中的其他Eureka Server
    defaultZone: http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka/
  3. 配置Eureka Server实例2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    spring:
    application:
    name: eureka-server
    server:
    port: 7002

    eureka:
    client:
    register-with-eureka: true
    fetch-registry: true
    service-url: # 集群.将本服务注册到集群中的其他Eureka Server
    defaultZone: http://eureka7001.com:7001/eureka, http://eureka7003.com:7003/eureka/
    instance:
    hostname: eureka7002.com # eureka server实例名称
  4. 配置Eureka Server实例3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    spring:
    application:
    name: eureka-server

    server:
    port: 7003

    eureka:
    instance:
    hostname: eureka7003.com
    client:
    fetch-registry: true
    register-with-eureka: true
    service-url: # 集群,相互注册
    defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka/
  5. 依次启动Eueka Server实例1、实例2、实例3,在每个实例页面都可以看到其他两个Eureka Server实例则集群部署成功。 如下图所示:

image-20201122212805807 image-20201122212642513 image-20201122212729312
业务提供者集群
  1. 新建业务实例,核心代码如下,两个实例代码完全相同,但端口不同

    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
    @RestController
    public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort; // 获取实例的端口,以此区分业务消费者调用的哪个业务提供者。

    @GetMapping("/payment/{id}")
    public CommonResult getById(@PathVariable("id") Long id){
    Payment payment = paymentService.getPaymentById(id);
    if (ObjectUtils.isEmpty(payment)){
    return new CommonResult<>(999,"无此记录, serverPort=" + serverPort,payment);
    }
    return new CommonResult<>(200,"查询成功, serverPort=" + serverPort,payment);
    }


    @PostMapping("/payment/save")
    public CommonResult save(@RequestBody Payment payment){
    if (paymentService.save(payment) !=1){
    return new CommonResult<>(500,"save payment failed, serverPort=" + serverPort);
    }
    return new CommonResult<>(200,"save payment success, serverPort=" + serverPort);
    }

    }
  1. 业务提供者实例1配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 8001 # 实例1端口

    spring:
    application:
    name: cloud-payment-service # 应用名称
    eureka:
    client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
    defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka/ # 将本实例注册到注册中心集群
  2. 业务提供者实例2配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 8002 # 实例2端口

    spring:
    application:
    name: cloud-payment-service # 应用名称

    eureka:
    client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
    defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka/ # 将本实例注册到注册中心集群
  1. 将业务提供者实例1、实例2依次启动,并注册到注册中心集群。可以看到,注册中心已经有应用名称为CLOUD-PAYMENT-SERVICE的服务,并且该服务是个集群,有2个实例,分别占用80018002端口

    image-20201122214246796
  2. 业务消费者核心代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 配置类,将RestTemplate对象注入Spring容器
    */
    @Configuration
    public class RestConfig {

    @Bean
    @LoadBalanced // 负载均衡 使用微服务名称直接调用服务 重要!!!
    public RestTemplate restTemplate(){
    return new RestTemplate();
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @RestController
    @Slf4j
    public class OrderController {
    // 集群版 直接使用微服务名称调用服务
    private final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/order/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
    return restTemplate.getForObject(PAYMENT_URL+"/payment/"+id, CommonResult.class);
    }


    @PostMapping("/order/save")
    public CommonResult save(@RequestBody Payment payment){
    return restTemplate.postForObject(PAYMENT_URL + "/payment/save", payment, CommonResult.class);
    }

    }

⚠️ 千万别忘了注入Bean时添加@LoadBalanced注解,否则会报java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE异常!

  1. 演示业务消费者端通过RestTemplate调用业务提供者集群的结果:

    image-20201122215709252 image-20201122215746169

服务发现Discovery

  1. 启动类上加入@EnableDiscoveryCilent注解

    1
    2
    3
    4
    5
    6
    7
    8
    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient
    public class PaymentMain8001 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentMain8001.class,args);
    }
    }
  1. 核心代码

    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
    @RestController
    @Slf4j
    public class PaymentController {
    @Resource
    private DiscoveryClient discoveryClient;


    @GetMapping("/payment/discovery")
    public Object discovery(){
    // 获取全部微服务的服务名称
    List<String> services = discoveryClient.getServices();
    // 遍历输出服务名称
    for (String service : services) {
    log.info("*********service = " + service);
    // 获取某个服务名称的实例
    List<ServiceInstance> instances = discoveryClient.getInstances(service);
    // 遍历输出实例的一些信息
    for (ServiceInstance instance : instances) {
    log.info(instance.getInstanceId()
    + "\t" + instance.getHost()
    + "\t" + instance.getPort()
    + "\t" + instance.getUri());
    }

    }
    return discoveryClient;
    }

    }
  1. 输出结果

    image-20201122224557574 image-20201122224638364

Eureka自我保护机制

自我保护机制:
默认情况下Eureka Client定时(默认周期为30秒)向Eureka Server端发送心跳包,如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包 ,便会直接从服务注册列表中剔除该服务。但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eureka Senver会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误。因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)

简而言之:某时刻某个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。

属于CAP中的AP分支

禁止自我保护机制
1
2
3
eureka
server
enable-self-preservation: false # 关闭自我保护,默认开启
image-20201122234720611

Consul

Consul是一套开源的分布式服务发现配置管理系统,由HashiCorp公司用Go语言开发。

Consul提供了微服务系统中的服务治理配置中心控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的微服务网络。

优点:

  • 基于raft协议,比较简洁。
  • 支持健康检查,同时支持Http协议和DNS协议。
  • 提供图形界面。
  • 跨平台,支持Linux、Mac、Windows。
Cent OS7下载安装Consual

进入Consul官网进行下载后,解压后是个可执行文件,在终端执行以下命令即可运行

1
./consul agent -dev
image-20201123144538057
关闭防火墙或打开8500端口
1
2
3
4
# 永久开放8500端口   推荐
firewall-cmd --zone=public --add-port=8500/tcp --permanent
# 关闭防火墙
systemctl stop firewalld
验证是否成功运行Consul

打开浏览器访问http://localhost:8500/,能看到以下界面即成功。

image-20201123152740986
将服务提供者注册进Conusl
  1. 引入依赖

    1
    2
    3
    4
    5
    <!--consul-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
  2. 主启动类

    1
    2
    3
    4
    5
    6
    7
    @SpringBootApplication
    @EnableDiscoveryClient
    public class PaymentConsulMain8006 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentConsulMain8006.class,args);
    }
    }
  3. 服务提供者核心业务类

    1
    2
    3
    4
    5
    6
    7
    8
    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/payment/consul")
    public String paymentConsul(){
    return "spring cloud with consul : serverPort = "
    + serverPort + "\t" + UUID.randomUUID().toString();
    }
  4. 配置consul

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    server:
    port: 8006 # 端口

    spring:
    application:
    name: cloud-payment-consul # 项目名称

    cloud:
    consul: # 注册服务至consul
    host: localhost
    port: 8500
    discovery:
    hostname: 127.0.0.1
    service-name: ${spring.application.name}
  1. 启动后再次进入http://localhost:8500/,可以看到新增了一个服务。

    image-20201123152904318
将服务消费者注册进Consul
  1. 引入依赖

    1
    2
    3
    4
    5
    <!--consul-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
  2. 主启动类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @SpringBootApplication
    @EnableDiscoveryClient // 重要!!
    public class OrderConsulMain82 {
    public static void main(String[] args) {
    SpringApplication.run(OrderConsulMain82.class,args);
    }


    @Bean
    @LoadBalanced // 重要!!
    public RestTemplate restTemplate(){
    return new RestTemplate();
    }
    }
  3. 消费者核心业务类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
         @Resource
    private RestTemplate restTemplate;

    // 使用服务名进行远程调用服务提供者
    private static final String INVOKE_URL = "http://cloud-payment-consul";

    @GetMapping("/order/consul")
    public String orderConsul(){
    return restTemplate.getForObject(INVOKE_URL + "/payment/consul",String.class);
    }

  4. 配置conusl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 82 # 端口

    spring:
    application:
    name: cloud-order-consul # 服务名
    cloud:
    consul: # 注册进consul
    host: localhost
    port: 8500
    discovery:
    hostname: 127.0.0.1
    service-name: ${spring.application.name}
  1. 启动服务消费者,可以看到consul可视页面已经新增了一服务。

    image-20201123153732777
使用服务消费者调用服务提供者业务
image-20201123153855789

直接使用服务提供者提供的业务也是没问题的。

image-20201123153943968

Eureka与Consul的异同点

负载均衡与服务调用

Ribbon

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具

Ribbon是Netflix发 布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

​ ⚠️ 目前Ribbon已进入维护模式

image-20201123160007791

负载均衡(Loaa Balance)

简单来说就是讲用户的请求平均分摊到多个服务上,达到服务的高可用。常用负载均衡有Nginx、LVS。

Ribbon客户端负载均衡 VS Nginx服务端负载均衡
  • Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,由Nginx负载将这些请求转发给多个服务。
  • Ribbon是本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册服务列表信息后缓存到JVM本地,然后在本地实现轮询负载均衡策略。
架构说明
未命名绘图2

Ribbon在工作时分成两步
第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server.
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询随机和根据响应时间加权

POM依赖
1
2
3
4
5
6
7
8
9
10
11
<!-- 在引入eureka cilent依赖时其实就已经引入了Ribbon依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- 也可以单独引入Ribbon的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
启用Ribbon负载均衡

在注入RestTemplate bean组件时,加上@LoadBalance注解即可。

1
2
3
4
5
@Bean
@LoadBalanced // 负载均衡,默认轮询 使用微服务名称直接调用服务
public RestTemplate restTemplate(){
return new RestTemplate();
}
Robbon自带负载规则

核心组件:IRole:是定义负载均衡规则的接口。一个规则可视为负载均衡的一种策略。

Robbon自带以下负载均衡策略。

image-20201123235757701

  • RoundRobinRule 轮询
  • RadomRole 随机
  • RetryRule 先按照轮询的策略获取服务,若获取失败则在指定时间内重试,获取可用的服务。
  • WeightResponseTimeRule 是对RetryRule的扩展,响应速度越快的实例权重越大,越容易被选择。
  • BestAvailableRule 先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量小的服务。
  • AvailabilityFilteringRule 先过滤故障实例,再选择并发量小的实例。
  • ZoneAvoidanceRule 默认规则,符合判断server所在区域的性能和server的可用性选择服务器。
替换默认规则
1
2
3
4
5
6
7
8
9
10
11
/**
* 配置类,替换默认LB策略
*/
@Component
public class CustomLoadBalanceRule {
@Bean
public RandomRule randomRule(){
//定义为随机策略
return new RandomRule();
}
}
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "cloud-order-service", configuration = CustomLoadBalanceRule.class)
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}

​ ⚠️ 注意:这个自定义类不能放在@ComponentScan所扫描到的包及其子包下。否则它会被所有的@RibbonClients共享(意思就是覆盖所有客户端的默认值,达不到个性化定制的目的)。如果开发人员使用@ComponentScan(或@SpringBootApplication),那就必须采取措施避免被覆盖到(例如将其放入一个独立的,不重叠的包中,或以@ComponentScan指明要扫描的,并排除此配置类。

​ 需要提一下的是,@SpringBootApplication注解中包含的有@ComponentScan注解。

image-20201124003753941image-20201124003454735