SpringCloud系列——Eureka(一)

129 阅读11分钟

前言

仅用于当字典查

Eureka的理论部分

是什么?

Eureka是一个服务注册和发现的注册中心

需要注意上图在 Eureka Server 和 Service Provider 这两个部分可以做集群

Service Provider 先将自己的服务注册到注册中心 Eureka Server 中,接着 Service Consumer 也注册到 注册中心中,然后根据自己的需要选择服务

注册中心将服务提供者的地址发送给 Service Consumer 中,Service Consumer在根据地址远程调用 Service Provider

Eureka Server 和 Eureka Client 的故事

我们以房东和租户之间的关系来对照 Eureka Server 和 Eureka Client 之间的关系

租户每 30 天需要交房租给房东Eureka Client 每 30 秒对 Eureka Server 发送一次心跳包
租户如果90天(也就是3个月)没交房租,房子和押金都会被房东收回Eureka Server 发现某个 Eureka Client 超过 90 秒都没发送心跳包,Eureka Server会从注册表中把该 Eureka Client 删除

整合Eureka(单机)

父项目

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhazha.springcloud</groupId>
    <artifactId>springcloud_study_parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>cloud-provider-payment8001</module>
    </modules>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.7.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>2021.0.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2021.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.29</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.11</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.2.2</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.1</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>2.17.2</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.24</version>
                <optional>true</optional>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <finalName>springcloud_study_parent</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.1</version>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

创建 api 项目

<?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>springcloud_study_parent</artifactId>
        <groupId>com.zhazha</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-api-common</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.4</version>
        </dependency>
    </dependencies>
</project>
package com.zhazha.springcloud.entities;

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

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
	private Long id;
	private String serial;
}

package com.zhazha.springcloud.utils;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult<T> implements Serializable {
	private Integer code;
	private String message;
	private T data;
	
	public CommonResult(Integer code, String message) {
		this.code = code;
		this.message = message;
		this.data = null;
	}
}

创建注册中心项目

<?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>springcloud_study_parent</artifactId>
        <groupId>com.zhazha</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zhazha</groupId>
            <artifactId>cloud-api-common</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>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>
server:
  port: 7001
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false # false表示不向注册中心注册自己
    fetch-registry: false # false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
package com.zhazha.springcloud;

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

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

服务提供者

<?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>springcloud_study_parent</artifactId>
        <groupId>com.zhazha</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8001</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <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>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </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>
        <dependency>
            <groupId>com.zhazha</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

</project>
server:
  port: 8001
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource            # 当前数据源操作类型
    driver-class-name: com.mysql.cj.jdbc.Driver             # mysql驱动包
    url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 123456

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.zhazha.springcloud.entities    # 所有Entity别名类所在包
  configuration:
    map-underscore-to-camel-case: true

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

mybatis的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="com.zhazha.springcloud.dao.PaymentDao">

    <resultMap type="payment" id="PaymentMap">
        <result property="id" column="id" jdbcType="BIGINT"/>
        <result property="serial" column="serial" jdbcType="VARCHAR"/>
    </resultMap>

    <select id="getPaymentById" resultMap="PaymentMap">
        select * from payment p where p.id = #{id}
    </select>

</mapper>
package com.zhazha.springcloud;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@MapperScan(value = "com.zhazha.springcloud.dao")
@EnableEurekaClient
public class PaymentMain8001 {
	
	public static void main(String[] args) {
		SpringApplication.run(PaymentMain8001.class, args);
	}
	
}
package com.zhazha.springcloud.dao;

import com.zhazha.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;

import java.util.List;

public interface PaymentDao {
	@Insert("insert into payment(serial) values (#{serial})")
	int create(Payment payment);
//	@Select("select * from payment p where p.id = #{id}")
	Payment getPaymentById(@Param("id") Long id);
	
	@SelectProvider(type = PaymentProvider.class, method = "getAll")
	List<Payment> getAll();
}
package com.zhazha.springcloud.dao;

import org.apache.ibatis.jdbc.SQL;

public class PaymentProvider {
	public String getAll() {
		return new SQL().SELECT("*")
				.FROM("payment").toString();
	}
}

这里需要注意 public String getAll()函数必须是 public

package com.zhazha.springcloud.service;

import com.zhazha.springcloud.entities.Payment;

public interface PaymentService {
	int create(Payment payment);
	Payment getPaymentById(Long id);
}
package com.zhazha.springcloud.service.impl;

import com.zhazha.springcloud.dao.PaymentDao;
import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class PaymentServiceImpl implements PaymentService {
	@Resource
	private PaymentDao paymentDao;
	
	@Override
	public int create(Payment payment) {
		return paymentDao.create(payment);
	}
	@Override
	public Payment getPaymentById(Long id) {
		return paymentDao.getPaymentById(id);
	}
}
package com.zhazha.springcloud.controller;

import com.zhazha.springcloud.entities.Payment;
import com.zhazha.springcloud.service.PaymentService;
import com.zhazha.springcloud.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;

@RestController
@Slf4j
@RequestMapping("payment")
public class PaymentController {
	@Resource
	private PaymentService paymentService;
	
	/**
	 * 返回给前端的结果集
	 * 下面的 RequestBody 注解如果不加上, restTemplate 的 postForObject 将无法保存参数
	 * @param payment
	 * @return
	 */
	@PostMapping(value = "create")
	public CommonResult create(@RequestBody Payment payment) {
		Integer result = paymentService.create(payment);
		log.info("******插入结果:" + result);
		if (result > 0) {
			return new CommonResult(200, "插入数据库成功", result);
		} else {
			return new CommonResult(444, "插入数据库失败", null);
		}
	}
	
	@GetMapping(value = "get/{id}")
	public CommonResult getPaymentByID(@PathVariable("id") Long id) {
		Payment payment = paymentService.getPaymentById(id);
		log.info("******插入结果:" + payment);
		if (payment != null) {
			return new CommonResult(200, "查询成功", payment);
		} else {
			return new CommonResult(444, "没有查询记录", null);
		}
	}
}

注意:@RequestBody@ResponseBody的功能

如果不使用 @RequestBody每个属性对应一个参数,参数在请求的URL中

如果使用 @RequestBody的话,一个实体对应一个参数,参数不在请求的URL中

  • @RequestBody的作用是:读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上,再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上;

  • @ResponseBody的作用是:用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。返回的数据不是html标签的页面,而是其他某种格式的数据时(如jsonxml等)使用

添加日志显示 mybatis sql:

logging:
  file:
  	# 日志保存位置
    path: D:\programs\codes\java\springcloud_study_parent\logs
  level:
    # 某包下日志打印 level
    com:
      zhazha:
        springcloud:
          dao: debug

访问: http://localhost:7001/

就能够看到上面已经发现了我们的 8001 项目

可以访问:

http://localhost:8001/payment/get/2

创建消费者

maven pom.xml:

<?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>springcloud_study_parent</artifactId>
        <groupId>com.zhazha.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-order80</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>com.zhazha.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: 80

spring:
  application:
    name: cloud-order-service

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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableEurekaClient
@SpringBootApplication
public class MainApp80 {
	
	public static void main(String[] args) throws Exception {
		SpringApplication.run(MainApp80.class, args);
	}
	
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
}

package com.zhazha.springcloud.controller;

import com.zhazha.springcloud.dto.CommonResult;
import lombok.extern.slf4j.Slf4j;
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;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
@RequestMapping("consumer")
public class OrderController {
	
	@Resource
	private RestTemplate restTemplate;
	
	public static final String INVOKE_URL = "http://CLOUD-PAYMENT-SERVICE";
	
	@GetMapping("payment/get/{id}")
	public CommonResult get(@PathVariable("id") Long id) {
		return restTemplate.getForObject(INVOKE_URL + "/payment/get/{id}", CommonResult.class, id);
	}
	
	@GetMapping("payment/eureka")
	public String orderEureka() {
		return "order80: " + restTemplate.getForObject(INVOKE_URL + "/payment/eureka", String.class);
	}
	
}

访问: http://localhost:8080/consumer/payment/get/2

整合Eureka(集群)

eureka server集群

整合Eureka集群的大体思路是:

Eureka的注册中心是无法自己给自己注册的,但是多个注册中心是可以相互注册的

比如 注册中心A 不能将自己注册到注册中心A,但是可以将自己注册到 注册中心B,而注册中心B也可以将自己注册给注册中心A

现在我们先新建两个项目试试

将 7001 项目中的配置和源码复制到 7002 中

接着只要修改:

port : 7001

hostname : eureka7001.com

defaultZone : http:// eureka7002.com:7002/eureka/

这三个就可以了

7001:

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://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://eureka7002.com:7002/eureka/

7002:

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://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/

eureka服务提供方集群

最后服务的注册项目: 8001 需要修改一部分配置:

defaultZone : http:// eureka7001.com:7001/eureka/,eureka7002.com:7002/eureka/,htt… #将服务注册到 Eureka Server 集群

然后再创建一个cloud-provider-payment8002, 配置基本一样

**

完整代码:

server:
  port: 8001
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/springcloud2022?useUnicode=true&characterEncoding=utf-8&useSSL=false
#Eureka配置
eureka:
  client:
    # 表示是否将自己注册进EurekaServer,默认为 true
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,默认为 true 。单节点无所谓,
    # 集群必须设置为true才能配合 ribbon 使用负载均衡
    fetch-registry: true
    # EurekaServer的地址
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7001.com:7002/eureka
mybatis:
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mapper/*.xml
logging:
  file:
    path: D:\programs\codes\java\springcloud_study_parent\logs
  level:
    com:
      zhazha:
        springcloud:
          dao: debug
server:
  port: 8002
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/springcloud2022?useUnicode=true&characterEncoding=utf-8&useSSL=false
#Eureka配置
eureka:
  client:
    # 表示是否将自己注册进EurekaServer,默认为 true
    register-with-eureka: true
    # 是否从EurekaServer抓取已有的注册信息,默认为 true 。单节点无所谓,
    # 集群必须设置为true才能配合 ribbon 使用负载均衡
    fetch-registry: true
    # EurekaServer的地址
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7001.com:7002/eureka
mybatis:
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mapper/*.xml
logging:
  file:
    path: D:\programs\codes\java\springcloud_study_parent\logs
  level:
    com:
      zhazha:
        springcloud:
          dao: debug

修改 hosts

127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

这里我们需要注意

@LoadBalanced这个注解, 开启客户端负载均衡. 这个之后会讲

接着访问: http://localhost/consumer/payment/eureka 就能看到 8001 和 8002 轮询出现

这里就没截图 8002 了

成功了,而且是两个Eureka Server 都有

这里就不粘贴 eureka02.com的界面了

从图中需要知道的信息:

eureka.instance.hostname=eureka7001.com这个配置最后会显示在

spring.application.name=cloud-payment-service这个配置最后会显示在

Eureka的自我保护机制

紧急情况! EUREKA 可能不正确地声称实例已启动,但实际上并未启动。续订少于阈值,因此为了安全起见,实例不会过期。

有时候我们能够在 Eureka 注册中心页面上看到上面这样的警告

这表明注册中心(7001)没有收到服务的提供者(8001)的心跳包,但实际的服务还会显示在 注册中心页面中

Eureka 注册中心的虽然记录下服务提供者的信息,但实际上已经无法继续提供服务了

这是 Eureka 自我保护机制的策略:“好死不如赖活”

这种情况存在弊端,服务的消费端可能会正巧需要获得该服务,导致服务调用失败。此时,我们可以通过客户端的容错机制来解决此问题,比如:Ribbon和Hystrix

如果需要关闭 Eureka 的自我保护机制,则可以:

eureka:
  server:
    enable-self-preservation: false # false 关闭 Eureka 的自我保护机制,默认是开启,一般不建议大家修改

修改心跳包的周期

  instance:
    #心跳检测与续约时间
    #开发时设置小点,保证服务关闭后注册中心及时剔除服务
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

主机名称:服务名称修改

自定义一个客户端的名字

只需要在 application.yml 文件中加上:

eureka:
  instance:
    instance-id: payment8001

访问信息有IP信息提示

如果你左下角蓝色长方形中的网址没有 ip 显示则需要:

eureka:
  instance:
    prefer-ip-address: true

使用DiscoveryClient服务发现

  1. 在 application 启动类上添加@EnableDiscoveryClient注解
  2. 去controller里面注入
@Resource
private DiscoveryClient discoveryClient;

@RequestMapping(value = "discovery")
@ResponseBody
public Object discovery() {
    List<String> services = this.discoveryClient.getServices();
    for (String service : services) {
        log.info("*********** service: " + service);

    }
    List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
    for (ServiceInstance instance : instances) {
        log.info("service application host = {}, port = {}, uri = {}", instance.getHost(), instance.getPort(), instance.getUri());
    }
    return this.discoveryClient;
}

可以看到

c.z.s.controller.PaymentController: *********** service: cloud-payment-service
c.z.s.controller.PaymentController: service application host = 192.168.19.1, port = 8001, uri = http://192.168.19.1:8001
{
  "services": [
    "cloud-payment-service"
  ],
  "order": 0
}

Eureka停更

Eureka 目前已经停更好几年了,很多公司也早早转移战场

目前市面上的代替方案有很多:

  1. zookeeper(CP) paxos 共识

  2. consul(CP) raft 共识

  3. nacos(CP、AP) jraft + distro ★ 推荐使用

zookeeper代替方案

zk启动

直接在 linux 的 docker 环境下启动

version: "3.9"

services:
  zoo1:
    image: zookeeper:3.8.0
    restart: always
    hostname: zoo1
    ports:
      - "2181:2181"
    environment:
      ZOO_MY_ID: 1
    volumes:
      - "/root/zookeeper/zoo.cfg:/conf/zoo.cfg"
    privileged: true
    container_name: zoo1
dataDir=/data
dataLogDir=/datalog
tickTime=2000
initLimit=5
syncLimit=2
autopurge.snapRetainCount=3
autopurge.purgeInterval=0
maxClientCnxns=60
standaloneEnabled=true
admin.enableServer=true
server.1=localhost:2888:3888;2181

docker compose up -d 创建并启动 zookeeper

这里需要注意 zookeeper 的版本 3.8.0

如果在window下启动, 需要修改 zoo_sample.cfgzoo.cfg

然后在控制台下:

zkServer.cmd

zk服务提供方

创建项目:

cloud-provider-payment8004

<?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>springcloud_study_parent</artifactId>
        <groupId>com.zhazha</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-provider-payment8004</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.zhazha</groupId>
            <artifactId>cloud-api-common</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>
        <!-- SpringBoot整合zookeeper客户端,这里不再使用Eureka进行服务注册,所以这里导入的是Zookeeper的相关依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
            <!-- 先排除自带的zookeeper -->
            <exclusions>
                <exclusion>
                    <groupId>org.apache.zookeeper</groupId>
                    <artifactId>zookeeper</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--添加zookeeper3.8.0版本,注意这里要和 zookeeper 应用启动的版本一致 -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.8.0</version>
        </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>

这里 zookeeper 的版本 jar 包 是 3.8.0,要和 docker zookeeper 版本一致

package com.zhazha.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
	
	public static void main(String[] args) throws Exception {
		SpringApplication.run(PaymentMain8004.class, args);
	}
	
}

package com.zhazha.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;

@RestController
@Slf4j
public class PaymentController {
	
	@Value("${server.port}")
	private String serverPort;
	
	@GetMapping("payment/zk")
	public String paymentZk() {
	    return "springcloud with zookeeper:"+serverPort+"\t"+ UUID.randomUUID();
	}
	
}

接着我们能够看到 zookeeper 中被注册的服务:

访问:http://localhost:8004/payment/zk

当我们关闭 8004 项目,zookeeper 会一定时间内心跳验证

然后立即删除掉服务

这和Eureka不相同

zookeeper CP 与 Eureka AP

再久一点连 service 节点都没了

zk服务消费方

创建 cloud-consumerzk-order80 项目

pom.xml 和 8004 项目一致

server:
  port: 80
spring:
  application:
    name: cloud-consumer-order
  cloud:
    zookeeper:
      connect-string: 192.168.0.155:2181
package com.zhazha.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class OrderZKMain80 {
	
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[] args) throws Exception {
		SpringApplication.run(OrderZKMain80.class, args);
	}
	
}

package com.zhazha.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderZKController {
	
	public static final String INVOKE_URL = "http://cloud-provider-payment";
	
	@Resource
	private RestTemplate restTemplate;

	@GetMapping("/consumer/payment/zk")
	public String paymentInfo() {
		return restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
	}
}

访问:http://localhost/consumer/payment/zk

远程调用成功

Consul代替方案

Consul是什么?

Consul包含多个组件,但是作为一个整体,为你的基础设施提供服务发现服务配置的工具.他提供以下关键特性:

  • 服务发现 Consul的客户端可用提供一个服务,比如 api 或者mysql ,另外一些客户端可用使用Consul去发现一个指定服务的提供者.通过DNS或者HTTP应用程序可用很容易的找到他所依赖的服务.
  • 健康检查 Consul客户端可用提供任意数量的健康检查,指定一个服务(比如:webserver是否返回了200 OK 状态码)或者使用本地节点(比如:内存使用是否大于90%). 这个信息可由operator用来监视集群的健康.被服务发现组件用来避免将流量发送到不健康的主机.
  • Key/Value存储 应用程序可用根据自己的需要使用Consul的层级的Key/Value存储.比如动态配置,功能标记,协调,领袖选举等等,简单的HTTP API让他更易于使用.
  • 安全服务通信:Consul可以为服务生成和分发TLS证书,以建立相互的TLS连接。意图可用于定义允许哪些服务通信。服务分割可以很容易地进行管理,其目的是可以实时更改的,而不是使用复杂的网络拓扑和静态防火墙规则。
  • 多数据中心: Consul支持开箱即用的多数据中心.这意味着用户不需要担心需要建立额外的抽象层让业务扩展到多个区域.

Consul面向DevOps和应用开发者友好.是他适合现代的弹性的基础设施.

需要注意它是用 golang 写的

  1. 节点分类
    1. Consul 分为 Client 和 Server两种节点(所有的节点也被称为Agent),其中Server 节点保存数据,Client 负责健康检查及转发数据请求到Server。所有的 Server 节点组成了一个集群,他们之间运行 Raft 协议,通过共识仲裁选举出 Leader。所有的业务数据都通过 Leader 写入到集群中做持久化,当有半数以上的节点存储了该数据后,Server集群才会返回ACK,从而保障了数据的强一致性。所有的 Follower 会跟随 Leader 的脚步,保证其有最新的数据副本
  1. 数据中心内部通信
    1. Consul 数据中心内部的所有节点通过 Gossip 协议(8301端口)维护成员关系,这也被叫做LAN GOSSIP。当数据中心内部发生拓扑变化时,存活的节点们能够及时感知,比如Server节点down掉后,Client 就会将对应Server节点从可用列表中剥离出去。集群内数据的读写请求既可以直接发到Server,也可以通过 Client 转发到Server,请求最终会到达 Leader 节点。在允许数据轻微陈旧的情况下,读请求也可以在普通的Server节点完成,集群内数据的读写和复制都是通过8300端口完成
  1. 跨数据中心通信
    1. Consul支持多数据中心,上图中有两个 DataCenter,他们通过网络互联,注意为了提高通信效率,只有Server节点才加入跨数据中心的通信。跨数据中心的 Gossip 协议使用8302端口,也被称为WAN GOSSIP,是全局范围内唯一的。通常情况下,不同的Consul数据中心之间不会复制数据。当请求另一个数据中心的资源时,Server 会将其转发到目标数据中心的随机Server 节点,该节点随后可以转发给本地 Leader 处理
端口作用
8300RPC 调用
8301数据中心内部 GOSSIP 协议使用
8302跨数据中心 GOSSIP 协议使用
8500HTTP API 和 Web 接口使用
8600用于 DNS 服务端

Consul服务的注册与发现

绿色方框是服务的注册中心,使用 raft 共识算法选举出 leader 和 follower 两种节点

红色是服务的注册方,consul 的 client模式 将服务注册到注册中心

蓝色是服务的消费方,consul client 去注册中心拿服务信息,接着识别信息远程调用红色节点的服务

dubbo经典调用关系.jpg

springcloud整合Consul

服务的提供方

创建cloud-providerconsul-payment8006项目

添加pom.xml

<?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>springcloud_study_parent</artifactId>
        <groupId>com.zhazha</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-providerconsul-payment8006</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity --><!--SpringCloud consul-server -->
        <dependency>
            <groupId>com.zhazha</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <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>
        <!--日常通用jar包配置-->
        <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>
server:
  port: 8006
spring:
  application:
    name: consul-provider-payment
  cloud:
    consul:
      port: 8500
      host: 127.0.0.1
      discovery:
        service-name: service-producer
If you use Spring Cloud Consul Config, and you have set spring.cloud.bootstrap.enabled=true or spring.config.use-legacy-processing=true or use spring-cloud-starter-bootstrap, then the above values will need to be placed in bootstrap.yml instead of application.yml.

这里需要注意到,如果是开启了 bootstrap 主键,则需要将 上面的 配置从 application.yml 转移到 bootstrap.yml 中

To disable the Consul Discovery Client you can set spring.cloud.consul.discovery.enabled to false. Consul Discovery Client will also be disabled when spring.cloud.discovery.enabled is set to false.

并且如果你需要禁止 Consul Discovery Client(也就是我们现在这个项目)去发现服务,可以spring.cloud.consul.discovery.enabled 设置为 false

To disable the service registration you can set spring.cloud.consul.discovery.register to false.

同时呢,要禁止服务注册,你可以设置 spring.cloud.consul.discovery.registerfalse

package com.zhazha.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class PaymentConsulMain8006 {
	
	public static void main(String[] args) throws Exception {
		SpringApplication.run(PaymentConsulMain8006.class, args);
	}
	
}

访问:http://localhost:8500/

可以看到spring.cloud.consul.discovery.service-name=service-producer配置决定了 service 中的名字,后期客户端会根据这个名称来进行服务调用。

而我们的 spring.application.name=consul-provider-payment-8006出现在

表示 service-producer下面有一个叫consul-provider-payment-8006的实例

服务的消费方

 <dependencies>
        <!--SpringCloud consul-server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <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>
        <!--日常通用jar包配置-->
        <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>
###consul服务端口号
server:
  port: 80

spring:
  application:
    name: cloud-consumer-order
  ####consul注册中心地址
  cloud:
    consul:
      host: 127.0.0.1
      port: 8500
      discovery:
        #hostname: 127.0.0.1
        service-name: ${spring.application.name}

这里我们如果要把消费端变得更加纯粹,可以添加spring.cloud.consul.discovery.register=false

package com.zhazha.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;


@EnableDiscoveryClient
@SpringBootApplication
public class OrderConsulMain80 {
	
	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}
	
	public static void main(String[] args) throws Exception {
		SpringApplication.run(OrderConsulMain80.class, args);
	}
	
}
package com.zhazha.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class OrderConsulController {
	
	// 服务的提供的 spring.cloud.consul.discovery.service-name=service-producer 
	// 配置决定了,服务的消费方使用的地址
	// 这里的地址不再是服务提供方的 spring.application.name
	public static final String INVOKE_URL = "http://service-producer";
	
	@Resource
	private RestTemplate restTemplate;
	
	@GetMapping("consumer/payment/consul")
	public String paymentInfo() {
		return restTemplate.getForObject(INVOKE_URL + "/payment/consul", String.class);
	}
	
}

负载均衡

下期