(一)SpringCloud之Eureka注册中心

Eureka注册中心

1、什么是注册中心

打个比方,注册中心就好比手机中的通讯录,所有的联系人的联系方式就在这个通讯录中储存。当需要打电话的时候,只需要查询通讯录就可以获取某个联系人的联系方式。

注册中心类似于通信录,只不过注册中心储存的不是联系人的联系方式,而是每个服务的信息,从注册中心获取服务就好比通讯录的查询联系人,向注册中心注册服务,就好比通讯录的保存联系人,先有注册,才能查找。

注:注册中心只不过是用来注册和获取服务,并不会用来调取服务,具体的调取服务有获取服务方实现。就好比通讯录只负责储存和查询联系人,查到联系人后使用拨号软件拨打查询到的联系人电话。

2、CAP原则和BASE理论

2.1、CAP原则

在这里插入图片描述

名称 描述
Consistency 一致性。也叫做原子性。系统在执行某些操作后数据仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读到最新的值,这样的系统被认为是具有强一致性的。等同于所有节点访问同一份最新的数据副本。
Availability 可用性。每一个操作总是能够在一定的时间内返回结果,这里需要注意的是"一定时间内"和"返回结果”。一定时间内指的是在可以容忍的范围内返回结果,结果可以是成功或者是失败,且不保证获取的数据为最新数据。
Partition tolerance 分区容错性。分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障。

CAP原则又称CAP定理。值得是在系统中,ConsistencyAvailabilityPartition tolerance三者不可兼得。最多只能同时满足其中两者。

CAP 由 Eric Brewer 在 2000 年 PODC 会议上提出。该猜想在提出两年后被证明成立,成为我们熟知的 CAP定理。CAP三者不可兼得。

  • CA:一致性和可用性。如果同时要满足一致性和可用性,那么只有单体应用可以实现。因为如果是分布式应用的话,如果要保证多个系统的一致性,可定要消耗时间。那么可用性肯定就达不到要求。只有单体应用,可以满足CA
  • CP:一致性和分区容错性。这种典型的案例就是涉及到金钱的系统。涉及到金钱的系统肯定要保证数据一致,在分布式中多台服务器要保证数据一致,肯定不能再一定的时间内返回数据。好比在进行金钱转账的时候,宁愿牺牲可用性,也要保证数据一致性。
  • AP:可用性和分区容容错性。这种的经典案例就是抢购性的系统。例如春运的火车票,有时候明明看到还有票,但是在下单的时候却被告知没票了,这就是因为需要满足可用性,在一定的时间内返回数据,从而放弃了数据的一致性。但是这种情况在最后会有一个最终一致性。好比购票,显示的是还有车票,但是下单时却显示失败,就是因为最终一致性。在最后的时候会进行一致性判断。

如今,对于大多数互联网应用场景,主机众多,部署分散,并且规模也越来越大,节点只会越来越多,所以节点故障、网络故障时常态,分区容错性也就成为了一个分布式系统必然要面对的问题。那么就只能在C和A之前进行取舍。但是对于设计金钱的系统却不同,涉及到金钱的是非常重要的,宁愿牺牲A,也要保证C。如果出现机器故障的话,宁愿停止服务。

没有最好的策略,只有最符合当前系统的策略。

2.2、BASE理论

CAP 理论已经提出好多年了,难道真的没有办法解决这个问题吗?也许可以做些改变。比如 C 不必使用那么强的一致性,可以先将数据存起来,稍后再更新,实现所谓的 “最终一致性”。这个思路又是一个庞大的问题,同时也引出了第二个理论 BASE 理论。

BASE全称为 Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语句的缩写,来自 ebay 的架构师提出。

BASE 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大型互联网分布式实践的总结,是基于 CAP 定理逐步演化而来的。其核心思想是:既然无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

2.2.1、Basically Available(基本可用)

基本可用是指分布式系统在出现故障时,允许损失部分可用性(例如响应时间、功能上的可用性)。需要注意的是,基本可用绝不等价于系统不可用。

  • 响应时间上的丢失:例如原先查询数据只需要0.5秒,但是现在由于机器故障可以运行返回响应时间变为1~2秒。
  • 功能上的可用性:例如双十一购物的时候,为了满足非常庞大的流量冲击,为了保证系统的大方向的稳定,允许对一些服务降级处理(缩减服务器集群,给别的访问量大的使用)或者停止一些非必要的服务。

2.2.2、Soft state(软状态)

相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种 “硬状态”。

软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据会有多个副本,允许不同副本数据同步的延时就是软状态的体现。

2.2.3、Eventually consistent(最终一致性)

系统不可能一直是软状态,必须有个时间期限。在期限过后,应当保证所有副本保持数据一致性。从而达到数据的最终一致性。这个时间期限取决于网络延时,系统负载,数据复制方案设计等等因素。

实际上,不只是分布式系统使用最终一致性,关系型数据库在某个功能上,也是使用最终一致性的,比如备份,数据库的复制都是需要时间的,这个复制过程中,业务读取到的值就是旧值。当然,最终还是达成了数据一致性。这也算是一个最终一致性的经典案例。

3、为什么需要注册中心

在分布式系统中,不仅仅是需要在注册中心找到服务和服务地址的映射关系那么简单,还需要考虑很多的复杂问题:

  • 服务注册后,如何被及时的发现
  • 服务宕机后,如何及时处理下线
  • 服务如何有效的水平扩展
  • 服务发现时,如何进行路由
  • 服务异常时,如何进行降级
  • 注册溪红心如果实现自身的高可用

这些问题的解决都依赖于注册中心。简单看,注册中心的功能有点类似于 DNS 服务器或者负载均衡器,而实际上,注册中心作为微服务的基础组件,可能要更加复杂,也需要更多的灵活性和时效性。当然上面的问题,单单使用注册中心是无法完成的,还需要使用SpringCloud的其他组件共同完成。

注册中心解决了一下问题:

  • 服务管理
  • 服务之间的自动发现
  • 服务的依赖关系管理

4、Eureka

4.1、Eureka介绍

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

4.2、Eureka注册中心的各个角色

在这里插入图片描述

  1. **Eureka Server:**通过 Register、Get、Renew 等接口提供服务的注册和发现。
  2. **Service Provider(Eureka Client):**服务提供方,把自身的服务实例注册到 Eureka Server 中。
  3. **Service Consumer(Eureka Client):**服务调用方,通过 Eureka Server 获取服务列表,消费服务。

4.3、Eureka入门案例

4.3.1、创建Mave聚合项目

在这里插入图片描述

  • server01:注册中心
  • server02:注册中心(后期搭建集群版准备)
  • provider:服务提供者
  • consumer:服务消费者

4.3.2、添加依赖,配置文件

4.3.2.1、父项目

pom.xml

<!-- 继承 spring-boot-starter-parent 依赖 -->
<!-- 使用继承方式,实现复用,符合继承的都可以被使用 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.0.RELEASE</version>
</parent>


<!--
        集中定义依赖组件版本号,但不引入,
        在子工程中用到声明的依赖时,可以不加依赖的版本号,
        这样可以统一管理工程中用到的依赖版本
     -->
<properties>
    <!-- Spring Cloud Hoxton.SR1 依赖 -->
    <spring-cloud.version>Hoxton.SR5</spring-cloud.version>
</properties>

<!-- 项目依赖管理 父项目只是声明依赖,子项目需要写明需要的依赖(可以省略版本信息) -->
<dependencyManagement>
    <dependencies>
        <!-- spring cloud 依赖 -->
        <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>

4.3.2.2、server02

pom.xml

<!-- 项目依赖 -->
<dependencies>
    <!-- netflix eureka server 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <!-- spring boot web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- spring boot test 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

application.yml

server:
  port: 8761 # 端口

spring:
  application:
    name: eureka-server # 应用名称

# 配置 Eureka Server 注册中心
eureka:
  instance:
    hostname: localhost			  # 主机名,不配置的时候将根据操作系统的主机名来获取
  client:
    register-with-eureka: false   # 是否将自己注册到注册中心,默认为 true
    fetch-registry: false         # 是否从注册中心获取服务注册信息,默认为 true
    service-url:                  # 注册中心对外暴露的注册地址
      defaultZone: http://${
    
    eureka.instance.hostname}:${
    
    server.port}/eureka/

如果是单节点需要把register-with-eurekafetch-registry设置为false,否则会报错

4.3.3、单节点启动

package cn.yanghuisen;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @author admin
 * @version 1.0
 * @date 2020/6/15 15:16
 * @Description TODO
 */
@SpringBootApplication
@EnableEurekaServer     // 开启EurekaServer注解
public class EurekaServer01Application {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(EurekaServer01Application.class);
    }
}

访问:http://localhost:8761/

4.3.4、高可用(集群)

server02

pom.xml

<!-- 项目依赖 -->
<dependencies>
    <!-- netflix eureka server 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <!-- spring boot web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- spring boot test 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>cn.yanghuisen</groupId>
        <artifactId>eureka-server01</artifactId>
        <version>1.0-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

server02:application.yml

server:
  port: 8762 # 端口

spring:
  application:
    name: eureka-server # 应用名称(集群下相同)

# 配置 Eureka Server 注册中心
eureka:
  instance:
    hostname: eureka02            # 主机名,不配置的时候将根据操作系统的主机名来获取
  client:
    # 设置服务注册中心地址,指向另一个注册中心
    service-url:                  # 注册中心对外暴露的注册地址
      defaultZone: http://localhost:8761/eureka/

server01:application.yml

server:
  port: 8761 # 端口

spring:
  application:
    name: eureka-server # 应用名称(集群下相同)

# 配置 Eureka Server 注册中心
eureka:
  instance:
    hostname: eureka01            # 主机名,不配置的时候将根据操作系统的主机名来获取
  client:
    # 设置服务注册中心地址,指向另一个注册中心
    service-url:                  # 注册中心对外暴露的注册地址
      defaultZone: http://localhost:8762/eureka/

server02:启动类

package cn.yanghuisen;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @author admin
 * @version 1.0
 * @date 2020/6/15 15:16
 * @Description TODO
 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer02Application {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(EurekaServer01Application.class);
    }
}

访问:http://localhost:8761/ 或者 http://localhost:8762/

4.3.5、显示IP+端口

(server01和server02):application.yml

eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${
    
    spring.cloud.client.ip-address}:${
    
    server.port} # ip:port

4.3.6、provider

pom.xml

<!-- 项目依赖 -->
<dependencies>
    <!-- netflix eureka client 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- spring boot web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- lombok 依赖 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- spring boot test 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

application.yml

server:
  port: 7070 # 端口

spring:
  application:
    name: service-provider # 应用名称(集群下相同)

# 配置 Eureka Server 注册中心
eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${
    
    spring.cloud.client.ip-address}:${
    
    server.port} # ip:port
  client:
    service-url:                  # 设置服务注册中心地址
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

实体类

package cn.yanghuisen.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
    
    

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}

控制层

package cn.yanghuisen.controller;

import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/product")
public class ProductController {
    
    

    @Autowired
    private ProductService productService;

    /**
     * 查询商品列表
     *
     * @return
     */
    @GetMapping("/list")
    public List<Product> selectProductList() {
    
    
        return productService.selectProductList();
    }

}

服务类:接口

package cn.yanghuisen.service;


import cn.yanghuisen.pojo.Product;

import java.util.List;

/**
 * 商品服务
 */
public interface ProductService {
    
    

    /**
     * 查询商品列表
     *
     * @return
     */
    List<Product> selectProductList();

}

服务类:实现类

package com.example.service.impl;

import com.example.pojo.Product;
import com.example.service.ProductService;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

/**
 * 商品服务
 */
@Service
public class ProductServiceImpl implements ProductService {
    
    

    /**
     * 查询商品列表
     *
     * @return
     */
    @Override
    public List<Product> selectProductList() {
    
    
        return Arrays.asList(
                new Product(1, "华为手机", 2, 5888D),
                new Product(2, "联想笔记本", 1, 6888D),
                new Product(3, "小米平板", 5, 2666D)
        );
    }

}

启动类

package cn.yanghuisen;

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

@SpringBootApplication
// 开启 EurekaClient 注解,目前版本如果配置了 Eureka 注册中心,默认会开启该注解
//@EnableEurekaClient
public class ServiceProviderApplication {
    
    

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

}

访问:http://localhost:8761/ 或者 http://localhost:8762/。查看服务提供者是否已经注册到注册中心

4.3.7、consumer

pom.xml

<!-- 项目依赖 -->
<dependencies>
    <!-- netflix eureka client 依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        <exclusions>
            <exclusion>
                <groupId>com.fasterxml.jackson.dataformat</groupId>
                <artifactId>jackson-dataformat-xml</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- spring boot web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- lombok 依赖 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- spring boot test 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

application.yml

server:
  port: 9090 # 端口

spring:
  application:
    name: service-consumer # 应用名称

# 配置 Eureka Server 注册中心
eureka:
  client:
    register-with-eureka: false         # 是否将自己注册到注册中心,默认为 true
    registry-fetch-interval-seconds: 10 # 表示 Eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒
    service-url:                        # 设置服务注册中心地址
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

实体类

package cn.yanghuisen.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
    
    

    private Integer id;
    private String productName;
    private Integer productNum;
    private Double productPrice;

}

package cn.yanghuisen.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
    
    

    private Integer id;
    private String orderNo;
    private String orderAddress;
    private Double totalPrice;
    private List<Product> productList;

}

消费服务:接口

package cn.yanghuisen.service;


import cn.yanghuisen.pojo.Order;

public interface OrderService {
    
    

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    Order selectOrderById(Integer id);

}

控制层

package cn.yanghuisen.controller;

import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/order")
public class OrderController {
    
    

    @Autowired
    private OrderService orderService;

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Order selectOrderById(@PathVariable("id") Integer id) {
    
    
        return orderService.selectOrderById(id);
    }

}

消费服务:方式一

package cn.yanghuisen.service.impl;

import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
public class OrderServiceImpl implements OrderService {
    
    

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
    
    
        return new Order(id, "order-001", "中国", 31994D,
                selectProductListByDiscoveryClient());
    }

    private List<Product> selectProductListByDiscoveryClient() {
    
    
        StringBuffer sb = null;

        // 获取服务列表
        List<String> serviceIds = discoveryClient.getServices();
        if (CollectionUtils.isEmpty(serviceIds))
            return null;

        // 根据服务名称获取服务
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("service-provider");
        if (CollectionUtils.isEmpty(serviceInstances))
            return null;

        ServiceInstance si = serviceInstances.get(0);
        sb = new StringBuffer();
        sb.append("http://" + si.getHost() + ":" + si.getPort() + "/product/list");

        // ResponseEntity: 封装了返回数据
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                sb.toString(),
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {
    
    });
        return response.getBody();
    }
}

消费服务:方式二

package cn.yanghuisen.service.impl;

import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
public class OrderServiceImpl implements OrderService {
    
    

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient; // Ribbon 负载均衡器

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
    
    
        return new Order(id, "order-001", "中国", 31994D,
                selectProductListByLoadBalancerClient());
    }

    private List<Product> selectProductListByLoadBalancerClient() {
    
    
        StringBuffer sb = null;

        // 根据服务名称获取服务
        ServiceInstance si = loadBalancerClient.choose("service-provider");
        if (null == si)
            return null;

        sb = new StringBuffer();
        sb.append("http://" + si.getHost() + ":" + si.getPort() + "/product/list");

        // ResponseEntity: 封装了返回数据
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                sb.toString(),
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {
    
    });
        return response.getBody();
    }
}

消费服务:方式三

启动类

@Bean
@LoadBalanced // 负载均衡注解
public RestTemplate restTemplate() {
    
    
    return new RestTemplate();
}
package cn.yanghuisen.service.impl;

import cn.yanghuisen.pojo.Order;
import cn.yanghuisen.pojo.Product;
import cn.yanghuisen.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
public class OrderServiceImpl implements OrderService {
    
    

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 根据主键查询订单
     *
     * @param id
     * @return
     */
    @Override
    public Order selectOrderById(Integer id) {
    
    
        return new Order(id, "order-001", "中国", 31994D,
                selectProductListByLoadBalancerAnnotation());
    }

    private List<Product> selectProductListByLoadBalancerAnnotation() {
    
    
        // ResponseEntity: 封装了返回数据
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                "http://service-provider/product/list",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {
    
    });
        return response.getBody();
    }
}

访问:http://localhost:9090/order/1

4.3.8、自我保护

一般情况下,服务在 Eureka 上注册后,会每 30 秒发送心跳包,Eureka 通过心跳来判断服务是否健康,同时会定期删除超过 90 秒没有发送心跳的服务。

有两种情况会导致 Eureka Server 收不到微服务的心跳

  • 微服务自身的原因
  • 微服务与 Eureka 之间的网络故障

自我保护模式

Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,同时提示一个警告。这种算法叫做 Eureka Server 的自我保护模式。

为什么要启动自我保护

  • 因为同时保留"好数据"与"坏数据"总比丢掉任何数据要更好,当网络故障恢复后,这个 Eureka 节点会退出"自我保护模式”。
  • Eureka 还有客户端缓存功能(也就是微服务的缓存功能)。即使 Eureka 集群中所有节点都宕机失效,微服务的 Provider 和 Consumer 都能正常通信。
  • 微服务的负载均衡策略会自动剔除死亡的微服务节点。

关闭自我保护

server01和server02:application.yml

eureka:
  server:
    enable-self-preservation: false # true:开启自我保护模式,false:关闭自我保护模式
    eviction-interval-timer-in-ms: 60000 # 清理间隔(单位:毫秒,默认是 60*1000)

4.3.9、优雅停服

配置了优雅停服以后,将不需要 Eureka Server 中配置关闭自我保护。本文使用 actuator 实现。

1、添加依赖

provider:pom.xml

<!-- spring boot actuator 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

2、配置文件

provider:application.yml

# 度量指标监控与健康检查
management:
  endpoints:
    web:
      exposure:
        include: shutdown         # 开启 shutdown 端点访问
  endpoint:
    shutdown:
      enabled: true               # 开启 shutdown 实现优雅停服

使用 POST 请求访问:http://localhost:7070/actuator/shutdown

4.3.10、Eureka安全认证

1、添加依赖

server01和server02:pom.xml

<!-- spring boot security 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2、配置文件

server01和server02:application.yml

spring:
  # 安全认证
  security:
    user:
      name: root
      password: 123456

3、修改访问集群节点的 url

server01和server02:application.yml

# 配置 Eureka Server 注册中心
eureka:
  instance:
    hostname: eureka01            # 主机名,不配置的时候将根据操作系统的主机名来获取
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${
    
    spring.cloud.client.ip-address}:${
    
    server.port} # ip:port
  client:
    # 设置服务注册中心地址,指向另一个注册中心
    service-url:                  # 注册中心对外暴露的注册地址
      defaultZone: http://root:123456@localhost:8762/eureka/

provider:application.yml

# 配置 Eureka Server 注册中心
eureka:
  instance:
    prefer-ip-address: true       # 是否使用 ip 地址注册
    instance-id: ${
    
    spring.cloud.client.ip-address}:${
    
    server.port} # ip:port
  client:
    service-url:                  # 设置服务注册中心地址
      defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/

consumer:application.yml

# 配置 Eureka Server 注册中心
eureka:
  client:
    register-with-eureka: false         # 是否将自己注册到注册中心,默认为 true
    registry-fetch-interval-seconds: 10 # 表示 Eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒
    service-url:                        # 设置服务注册中心地址
      defaultZone: http://root:123456@localhost:8761/eureka/,http://root:123456@localhost:8762/eureka/

4.3.11、过滤CSRF

Eureka 会自动化配置 CSRF 防御机制,Spring Security 认为 POST, PUT, and DELETE http methods 都是有风险的,如果这些 method 发送过程中没有带上 CSRF token 的话,会被直接拦截并返回 403 forbidden。

官方给出了解决的方法,具体可以参考 spring cloud issue 2754,里面有大量的讨论,这里提供两种解决方案。

首先注册中心配置一个 @EnableWebSecurity 配置类,继承 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter,然后重写 configure 方法。

方案一:忽略/eureka/的所有请求

server01和server02:配置类

package cn.yanghuisen.config;

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;

/**
 * 安全认证配置类
 */
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        super.configure(http); // 加这句是为了访问 eureka 控制台和 /actuator 时能做安全控制
        http.csrf().ignoringAntMatchers("/eureka/**"); // 忽略 /eureka/** 的所有请求
    }

}

方案二:保持密码验证的同时禁用 CSRF 防御机制

package cn.yanghuisen.config;

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;

/**
 * 安全认证配置类
 */
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        // 注意,如果直接 disable 的话会把安全验证也禁用掉
        http.csrf().disable().authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .httpBasic();
    }

}

猜你喜欢

转载自blog.csdn.net/Asdzxc968/article/details/107104095