SpringCloud服务注册与发现之Eureka

Eureka组件内容预览

5.Eureka服务注册与发现.png

Eureka基础知识

服务治理

  • 什么是服务治理
    • Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务治理
    • 在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
  • 什么是RPC远程调用

什么是服务注册与发现

  • 什么是服务注册与发现
    • Eureka采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用 Eureka的客户端连接到 Eureka Server并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。
    • 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))

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 Server服务注册中心

搭建环境

  • 在cloud2023父工程中创建一个module,选择maven工程,cloud-eureka-server7001
  • 导入相关依赖
<?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">
  <parent>
    <artifactId>cloud2023</artifactId>
    <groupId>top.ljzstudy.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>cloud-eureka-server7001</artifactId>

  <dependencies>
    <!--eureka-server-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
      <groupId>top.ljzstudy.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--boot web actuator-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--一般通用配置-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>
  </dependencies>
</project>
  • 编写application.yml文件
server:
  port: 7001

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

编写业务

  • 主启动类EurekaServer7001
package top.ljzstudy.springcloud;

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

@SpringBootApplication
@EnableEurekaServer //开启Eureka Server
public class EurekaServer7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001.class,args);
    }
}

  • 添加@EnableEurekaServer,将EurekaServer7001设置为Eureka注册中心服务器

测试

  • 开启服务,访问http://localhost:7001

image.png

  • 测试结果

image.png

  • 可以看到Instance currently registered with Eureka(当前注册到Eureka的实例)中显示No Instances available(没有可用实例)
  • 上述结果由于仅仅只是开启Eurrka服务并没有编写用于注册的EurekaClient服务,因此Eureka中没有注册的服务实例

构建EurekaClient 端

构建服务提供者微服务module

cloud-provider-payment8001,将注册进EurekaServer成为服务提供者provider

  • 搭建环境
    • pom文件
<?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">
    <parent>
        <groupId>top.ljzstudy.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
        <artifactId>cloud2023</artifactId>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment-8001</artifactId>

    <dependencies>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>top.ljzstudy.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--mysql-connector-java-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
      <!-- 开启热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
  • 编写application.yml文件
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.jdbc.Driver            # mysql驱动包 com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:33068/cloud2023?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: abc123

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka #单体架构注册中心
mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: top.ljzstudy.springcloud.entities    # 所有Entity别名类所在包
  • 数据源
CREATE TABLE `payment` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `serial` varchar(200) DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb3;
  • 编写业务
    • 编写主启动类
package top.ljzstudy.springcloud;

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

@SpringBootApplication
@EnableEurekaClient //服务注册客户端,服务提供方
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8001.class,args);
    }
}
  • 测试
    • 先启动EurekaServer7001,再启动PaymentMain8001
  • 测试结果
    • http:localhost:7001/

image.png

  • 微服务注册名称,在yml中,对spring.application.name进行配置

image.png

  • Eurke自我保护机制

image.png

  • 在常见的服务注册和发现的技术栈中Eureka是基于AP架构,保证系统高可用
    • Consul和Zookeeper是属于CP,牺牲系统的高可用性,满足系统的数据强一致

构建服务消费者微服务module

  • cloud-consumer-order80,将注册进EurekaServer成为服务消费者consumer
  • 整合RestTemplate,基于HttpClient做了进一步封装
  • 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
  • Eureka默认集成了Ribbon服务调用,同时支持客户端负载均衡,采用轮询策略
  • 环境搭建
    • pom文件
<?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">
  <parent>
    <artifactId>cloud2023</artifactId>
    <groupId>top.ljzstudy.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>cloud-consumer-order-80</artifactId>

  <dependencies>
    <!--eureka-client-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
      <groupId>top.ljzstudy.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </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>

</project>
  • 编写application.yml配置
server:
  port: 80

spring:
  application:
    name: cloud-order-service #服务名称

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true #撤销eureka注册
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka 
  • 编写业务
    • 主启动类
package top.ljzstudy.springcloud;

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

@SpringBootApplication
@EnableEurekaClient //服务注册客户端,消费者服务
public class MainApp80{
    public static void main(String[] args){
        SpringApplication.run(MainApp80.class,args);
    }
}
  • 编写config类,让ConsumerClient调用ProviderServer
package top.ljzstudy.springcloud.config;

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 ApplicationContextConfig {
    @Bean
    @LoadBalanced//使用LoadBalance注解赋予RestTemplate负载均衡能力
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • 编写控制层
package top.ljzstudy.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import top.ljzstudy.springcloud.entities.CommonResult;
import top.ljzstudy.springcloud.entities.PaymentEntity;

import javax.annotation.Resource;

/**
 * RestTemplate提供了多种便捷访问远程Http服务的方法,
 * 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
 */
@RestController
@Slf4j
public class OrderController {
    public static final String PAYMENT_URL = "http://localhost:8001";//单机版
    @Resource
    private RestTemplate restTemplate;
    @GetMapping("/consumer/payment/createObj")
    public  CommonResult<PaymentEntity> createObj(PaymentEntity paymentEntity){
        return restTemplate.postForObject(PAYMENT_URL+"/payment/create",paymentEntity,CommonResult.class);
    }
    @GetMapping("/consumer/payment/createEntity")
    public  CommonResult<PaymentEntity> createEntity(PaymentEntity paymentEntity){
        return restTemplate.postForEntity(PAYMENT_URL+"/payment/create",paymentEntity,CommonResult.class).getBody();
    }
    @GetMapping("/consumer/payment/getObj/{id}")
    public CommonResult<PaymentEntity> getPaymentObj(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }
    @GetMapping("/consumer/payment/getEntity/{id}")
    public CommonResult<PaymentEntity> getPaymentEntity(@PathVariable("id") Long id){
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
        return entity.getStatusCode().is2xxSuccessful()?entity.getBody(): new CommonResult(444,"operation fail");
    }
}

  • Client通过基于HttpClient的RestTemplate远程调用PaymentService中的接口
  • PaymentService的控制层
package top.ljzstudy.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import top.ljzstudy.springcloud.entities.CommonResult;
import top.ljzstudy.springcloud.entities.PaymentEntity;
import top.ljzstudy.springcloud.service.PaymentService;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @PostMapping(value = "payment/create")
    public CommonResult<Integer> create(@RequestBody PaymentEntity paymentEntity) {
        int result = paymentService.create(paymentEntity);
        log.info("result of insert" + result);
        return result > 0 ? new CommonResult<Integer>(200, "successful insert data " , result) : new CommonResult<Integer>(444, "fail insert data", null);
    }

    @GetMapping(value = "payment/get/{id}")
    public CommonResult<PaymentEntity> getPaymentEntityById(@PathVariable("id") Long id) {
        PaymentEntity result = paymentService.getPaymentEntityById(id);
        log.info("result of getPaymentEntityById" + result + "6666666");
        return result != null ?
                new CommonResult<>(200, "successful query data to id =" + id , result) :
                new CommonResult<>(444, "fail query data nothing id is" + id , null);
    }
}

  • Service接口
package top.ljzstudy.springcloud.service;

import org.apache.ibatis.annotations.Param;
import top.ljzstudy.springcloud.entities.PaymentEntity;

public interface PaymentService {
     int create(PaymentEntity paymentEntity);

     PaymentEntity getPaymentEntityById(@Param("id") Long id);

}
  • Service实现类
package top.ljzstudy.springcloud.service.impl;

import org.springframework.stereotype.Service;
import top.ljzstudy.springcloud.dao.PaymentDao;
import top.ljzstudy.springcloud.entities.PaymentEntity;
import top.ljzstudy.springcloud.service.PaymentService;

import javax.annotation.Resource;

@Service
public class PaymentServiceImpl implements PaymentService {
    @Resource
    private PaymentDao paymentDao;

    @Override
    public int create(PaymentEntity paymentEntity) {
        return paymentDao.create(paymentEntity);
    }

    @Override
    public PaymentEntity getPaymentEntityById(Long id) {
        return paymentDao.getPaymentEntityById(id);
    }
}

  • Mapper接口
package top.ljzstudy.springcloud.dao;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import top.ljzstudy.springcloud.entities.PaymentEntity;

@Mapper
public interface PaymentDao {
    int create(PaymentEntity paymentEntity);
    PaymentEntity getPaymentEntityById(@Param("id") Long id);
}

  • Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="top.ljzstudy.springcloud.dao.PaymentDao">

    <resultMap id="BaseResultMap" type="top.ljzstudy.springcloud.entities.PaymentEntity">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>

    <!--
        useGeneratedKeys="true" 插入成功>0否则失败
        keyProperty="id" 主键为id
    -->
    <insert id="create" parameterType="PaymentEntity" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO payment(SERIAL) VALUES(#{serial});
    </insert>

    <select id="getPaymentEntityById" parameterType="Long" resultMap="BaseResultMap" >
        SELECT * FROM payment WHERE id=#{id};
    </select>

</mapper>
  • 为服务提供者设置实例id
eureka:
  instance:
    instance-id: payment8001 #设置的id
    prefer-ip-address: true #访问路径可以显示IP地址
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2
  • 关闭Eureka自我保护机制
eureka:
  instance:
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2
  • 测试
    • 依次启动EurekaServer7001,PaymentMain8001和MainApp80

image.png

  • 访问http://localhost:7001/

1693901070291.png

  • 测试接口
    • 消费者
GET http://localhost:80/consumer/payment/getObj/1

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 05 Sep 2023 08:14:51 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "message": "successful query data to id =1 from port number : 8001",
  "data": {
    "id": 1,
    "serial": "ljzTest01"
  }
}
Response code: 200; Time: 828ms; Content length: 116 bytes
  - 服务者
GET http://localhost:8001/payment/get/1

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 05 Sep 2023 08:16:58 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "message": "successful query data to id =1 from port number : 8001",
  "data": {
    "id": 1,
    "serial": "ljzTest01"
  }
}
Response code: 200; Time: 15ms; Content length: 116 bytes

集群Eureka构建过程

Eureka集群原理

image.png

  • 单机Eureka注册中心存在单点故障风险
  • 问题:微服务RPC远程服务调用最核心的是什么 ?
  • 高可用,试想你的注册中心只有一个only one, 它出故障了会导致整个为服务环境不可用,所以解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错

构建双注册中心EurekaServer集群

  • 创建一个新的module作为第二个注册中心EurekaServer7002
  • 搭建环境
    • pom文件
<?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">
    <parent>
        <artifactId>cloud2023</artifactId>
        <groupId>top.ljzstudy.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-eureka-server7002</artifactId>

    <dependencies>
        <!--eureka-server-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>top.ljzstudy.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--boot web actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--一般通用配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
    </dependencies>


</project>
  • 编写application.yml配置
server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己。
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://eureka7001.com:7001/eureka/

  • 编写业务
    • 主启动类
package top.ljzstudy.springcloud;

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaServer7002 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7002.class,args);
    }
}
  • 修改本地.hosts映射文件
    • 找到C:\Windows\System32\drivers\etc路径下的hosts文件

1693906107477.png

  • 添加配置

1693906266141.png

  • 修改EurekaServer7001中的yml配置
server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
  client:
    #false表示不向注册中心注册自己。
    register-with-eureka: false
    #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    fetch-registry: false
    service-url:
      #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
      defaultZone: http://eureka7002.com:7002/eureka/

  server:
    #关闭自我保护机制,保证不可用服务被及时踢除
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000
  • 修改PaymentMain8001中的yml文件,将Payment Service8001注册到两台EurekaServer注册中心上
server:
  port: 8001

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.jdbc.Driver            # mysql驱动包 com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:33068/cloud2023?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: abc123

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka #单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
  instance:
    instance-id: payment8001
    prefer-ip-address: true #访问路径可以显示IP地址
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2
mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: top.ljzstudy.springcloud.entities    # 所有Entity别名类所在包
  • 修改MainApp80中的yml文件将消费者服务注册到两台注册中心上(实际开发中消费者客户端微服务不会注册进来,此次仅作演示)
server:
  port: 80

spring:
  application:
    name: cloud-order-service

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true #撤销eureka注册
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka #单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

  • 测试
    • 先要启动EurekaServer,7001/7002服务
    • 再要启动服务提供者provider,8001
    • 再要启动消费者,80
    • 测试消费者接口
GET http://localhost:80/consumer/payment/getObj/1

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 05 Sep 2023 09:45:23 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "message": "successful query data to id =1 from port number : 8001",
  "data": {
    "id": 1,
    "serial": "ljzTest01"
  }
}

Response code: 200; Time: 187ms; Content length: 116 bytes

构建双服务提供方微服务PaymentService集群

  • 创建新module,PaymentService8002
  • 搭建环境
    • pom文件
<?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">
  <parent>
    <artifactId>cloud2023</artifactId>
    <groupId>top.ljzstudy.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>

  <artifactId>cloud-provider-payment8002</artifactId>

  <dependencies>
    <!--eureka-client-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
      <groupId>top.ljzstudy.springcloud</groupId>
      <artifactId>cloud-api-commons</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.10</version>
    </dependency>
    <!--mysql-connector-java-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--jdbc-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

</project>
  • 修改application.yml文件
server:
  port: 8002

spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.jdbc.Driver            # mysql驱动包 com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:33068/cloud2023?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: abc123

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      #defaultZone: http://localhost:7001/eureka #单机版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
  instance:
    instance-id: payment8002
    prefer-ip-address: true #访问路径可以显示IP地址

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: top.ljzstudy.springcloud.entities    # 所有Entity别名类所在包
  • 编写主启动类
package top.ljzstudy.springcloud;

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

@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8002 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8002.class,args);
    }
}
  • 控制层(客户端)
package top.ljzstudy.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import top.ljzstudy.springcloud.entities.CommonResult;
import top.ljzstudy.springcloud.entities.PaymentEntity;

import javax.annotation.Resource;

/**
 * RestTemplate提供了多种便捷访问远程Http服务的方法,
 * 是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
 */
@RestController
@Slf4j
public class OrderController {
    //public static final String PAYMENT_URL = "http://localhost:8001";//单机版
    public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";//集群
    @Resource
    private RestTemplate restTemplate;
    @GetMapping("/consumer/payment/createObj")
    public  CommonResult<PaymentEntity> createObj(PaymentEntity paymentEntity){
        return restTemplate.postForObject(PAYMENT_URL+"/payment/create",paymentEntity,CommonResult.class);
    }
    @GetMapping("/consumer/payment/createEntity")
    public  CommonResult<PaymentEntity> createEntity(PaymentEntity paymentEntity){
        return restTemplate.postForEntity(PAYMENT_URL+"/payment/create",paymentEntity,CommonResult.class).getBody();
    }
    @GetMapping("/consumer/payment/getObj/{id}")
    public CommonResult<PaymentEntity> getPaymentObj(@PathVariable("id") Long id){
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
    }
    @GetMapping("/consumer/payment/getEntity/{id}")
    public CommonResult<PaymentEntity> getPaymentEntity(@PathVariable("id") Long id){
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
        return entity.getStatusCode().is2xxSuccessful()?entity.getBody(): new CommonResult(444,"operation fail");
    }
}

  • 控制层(8001/8002)
package top.ljzstudy.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import top.ljzstudy.springcloud.entities.CommonResult;
import top.ljzstudy.springcloud.entities.PaymentEntity;
import top.ljzstudy.springcloud.service.PaymentService;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;

    @PostMapping(value = "payment/create")
    public CommonResult<Integer> create(@RequestBody PaymentEntity paymentEntity) {
        int result = paymentService.create(paymentEntity);
        log.info("result of insert" + result);
        return result > 0 ? new CommonResult<Integer>(200, "successful insert data from port number : " + serverPort, result) : new CommonResult<Integer>(444, "fail insert data", null);
    }

    @GetMapping(value = "payment/get/{id}")
    public CommonResult<PaymentEntity> getPaymentEntityById(@PathVariable("id") Long id) {
        PaymentEntity result = paymentService.getPaymentEntityById(id);
        log.info("result of getPaymentEntityById" + result + "6666666");
        return result != null ?
                new CommonResult<>(200, "successful query data to id =" + id + " from port number : " + serverPort, result) :
                new CommonResult<>(444, "fail query data nothing id is" + id + " from port number : " + serverPort, null);
    }
}

  • 测试 : 依次启动Eureka7001,Eureka7002,Provider8001,Provider8002,Consumer80

1694080633787.png

GET http://localhost:80/consumer/payment/getObj/1

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 07 Sep 2023 10:00:37 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "message": "successful query data to id =1 from port number : 8001",
  "data": {
    "id": 1,
    "serial": "ljzTest01"
  }
}

Response code: 200; Time: 874ms; Content length: 116 bytes
  • 对该接口进行访问,port在8001/8002之间切换
  • 访问http://eureka7001.com:7001


服务发现Discovery

  • 对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
  • 修改PaymentController(8001)
package top.ljzstudy.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import top.ljzstudy.springcloud.entities.CommonResult;
import top.ljzstudy.springcloud.entities.PaymentEntity;
import top.ljzstudy.springcloud.service.PaymentService;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class PaymentController {

    .....
    
    //服务发现Discovery
    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("/payment/discovery")
    public Object discovery(){
        //获取微服务列表
        List<String> services = discoveryClient.getServices();
        services.forEach(v-> log.info(v));
        //获取指定微服务的注册个数
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance instance : instances) {
            log.info(
                    instance.getServiceId()+"\t"+
                    instance.getHost()+"\t"+
                    instance.getPort()+"\t"+
                    instance.getUri()
            );
        }
        return this.discoveryClient;
    }

    .....

}

  • 在启动类上新增@EnableDiscoverClient开启服务发现

1694082766056.png

GET http://localhost:8001/payment/discovery

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Thu, 07 Sep 2023 10:38:08 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "services": [
    "cloud-payment-service",
    "cloud-order-service"
  ],
  "order": 0
}

Response code: 200; Time: 78ms; Content length: 70 bytes

Eureka自我保护机制

故障现象

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,
Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT.
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE
image.png

原因

为什么会产生Eureka自我保护机制?
为了防止EurekaClient可以正常运行,但是 与 EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
image.png
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

  • 一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存
  • 属于CAP里面的AP分支——AP,高可用

禁止Eureka自我保护

注册中心eureakeServer端7001

  • 出厂默认,自我保护机制是开启的eureka.server.enable-self-preservation=true
  • 使用eureka.server.enable-self-preservation = false 可以禁用自我保护模式
# (EurekaServer7001)
eureka: 
  server:
    #关闭自我保护机制,保证不可用服务被及时踢除
       enable-self-preservation: false
    eviction-interval-timer-in-ms: 2000
  • 关闭效果——在eurekaServer端7001处设置关闭自我保护机制

image.png

生产者客户端eureakeClient端8001

  • 客户端默认心跳连接
eureka:
  instance:
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 30
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 90
  • 修改其数据以适应测试是否关闭服务端自我保护机制
  • 测试保持上述服务运行

1694084302010.png

  • 关闭8001

1694084427488.png
1694084455917.png

  • payment8001被移除

补充

HttpClient与RestTemplate

Netty

RPC远程调用

CAP模型

SOA模型与分布式

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芝士君(Java 版)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值