SpringCloud 微服务实战1-SpringCloud简介、Eureka微服务注册与发现

1.微服务架构与SpringCloud简介

1.1 微服务架构概述

1、什么是微服务?

微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是 Http 协议的 Restful API)。每个服务都围绕着具体业务进行构建,并且能够独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对齐进行构建。

2、基于分布式的微服务架构

基于分布式的微服务架构需要有如下功能:

  • 服务注册与发现
  • 服务调用
  • 服务熔断
  • 负载均衡
  • 服务降级
  • 服务消息队列
  • 配置中心管理
  • 服务网关
  • 服务监控
  • 全链路追踪
  • 自动化构建部署
  • 服务定时任务操作

1.2 Spring Cloud 简介

1、是什么?

SpringCloud是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶。

SpringCloud俨然已成为微服务开发的主流技术栈,在国内开发者社区非常火爆。

2、符合微服务技术维度

SpringCloud官方架构图

 SpringCloud 对微服务架构的落地组件:

  • 服务注册与发现:Eureka、Consul、Zookeeper
  • 服务调用(Restful调用):Feign
  • 服务熔断、降级:Hystrix
  • 负载均衡:Ribbon
  • 配置中心管理:Spring Cloud Config
  • 服务网关:Zuul、Gateway
  • 全链路追踪:Sleuth
  • 消息总线:Spring Cloud Bus
  • 服务开发:SpringBoot

某年京东营销微服务架构

 阿里微服务架构

一般我们把基础服务抽取出来,做成 无业务基础服务

  • 全局配置
  • ID自增器
  • 短链接服务
  • 文件存储服务
  • 身份验证
  • 邮件短信平台
  • 语音回拨

还有根据业务类型做成 业务性基础服务

  • 用户中心
  • 账户中心
  • 支付中心
  • 信审系统
  • 信息抓取系统
  • 消息中心
  • 活动广告、CMS......

2.SpringBoot 和 SpringCloud 版本选型

查看 SpringCloud 与 SpringBoot 对应关系: https://start.spring.io/actuator/info

D版SpringCloud版本,现在也能使用,但是比较老了。

  • SpringCloud:Hoxton.SR1
  • SpringBoot:2.2.2.Release
  • SpringCloud Alibaba:2.1.0.RELEASE
  • Java:Java8
  • Maven:3.5及以上
  • Mysql:5.7及以上

3. 关于 Cloud 各组件停更说明

停更不停用:被动修复 bugs,不再接受合并请求,不再发布新版本。

1、服务注册中心

  • Eureka:停新不停用
  • Zookeeper:替代 Eureka
  • Consul:替代Eureka,但不推荐使用
  • SpringCloud Alibaba Nacos:几乎可以完美的替换Eureka。

2、服务调用

  • Ribbon:继续使用,但停止更新
  • LoadBalance:正在发芽阶段,还不能使用

服务调用2

  • Feign:停止更新,几乎不能使用
  • OpenFeign:推荐使用这个

4、服务降级

  • Hystrix:停止更新国内正在大规模使用
  • resilience4j:官网推荐使用,但是国内未使用。
  • SpringCloud Alibaba Sentinel 强烈推荐使用。

5、服务网关

  • Zuul:不在使用
  • Zuul2:胎死腹中
  • gateway:推荐使用

6、服务配置

  • config:不推荐使用
  • SpringCloud Alibaba Nacos :推荐使用

7、服务总线

  • Bus:不推荐使用
  • SpringCloud Alibaba Nacos :推荐使用

4.微服务架构编码构建

1、创建 空的父工程(maven),并设置好包名和项目名。

Maven 选择我们自己安装的,不要使用工具默认的。

2、设置字符编码

 3、设置注解激活生效

4、选择工程的 Java 版本为Java8

5、 文件过滤

 6、父工程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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cloud.study</groupId>
    <artifactId>cloud-study</artifactId>
    <version>1.0</version>
    <!-- 表示是一个pom的父工程 -->
    <packaging>pom</packaging>

    <!--统一jar包管理-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>8.0.20</mysql.version>
        <druid.version>1.1.13</druid.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <!-- 子模块集成之后,提供作用:锁定版本+子module不用写 groupId 和 version -->
    <dependencyManagement>
        <dependencies>
            <!-- SpringBoot 2.2.2 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- SpringCloud Hoxton.SR1 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- SpringCloud Alibaba 2.1.0.RELEASE -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!-- druid -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!-- druid -->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

<dependencyManagement>

Maven 使用 dependencyManagement 元素来提供一种管理依赖版本号的方式,通常会在一个组织或者项目的最顶层的父POM文件中看到 dependencyManagement 元素。

使用 pom.xml 中的 dependencyManagement 元素能让所有子项目中引入一个依赖而不用显示列出版本号。Maven 会沿着父子层次向上走,直到找到一个拥有 dependencyManagement 元素的项目,然后它就会使用这个 dependencyManagement 元素中指定的版本号。

这样做的好处:如果有多个子项目都应用同一个依赖,则可以避免在每个使用的子项目里都声明一个版本,这样当想升级或切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改;另外如果某个子项目需要另外一个版本,只需要什么 <version> 即可。

7、maven 中跳过单元测试,点击【skip test】小图标,即可 关闭/打开 单元测试,如下图所示。

8、父工程创建完成,执行 clean、install 将父工程发布到仓库方便子工程继承。

5. 构建微服务模块 - 支付模块

1、建 module 

此时,父POM文件可以看到多了子模块的配置

    <modules>
        <module>cloud-provider-payment8001</module>
    </modules>

2、改 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>cloud-study</artifactId>
        <groupId>com.cloud.study</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

    <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>
        <!-- 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>
        <!-- devtools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

3、写 YML

在 resources 文件夹下新建 application.yml 

server:
  port: 8080
spring:
  application:
    name: cloud-payment-service
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.cloud.study #实体扫描,多个package用逗号或者分号分隔

4、主启动 PaymentMain8001 

package com.cloud.study;

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

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

5、业务类 

controller-service-dao-mysql

5.1、建表 SQL

CREATE TABLE payment
(
   id                   BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID',
   SERIAL               VARCHAR(200) DEFAULT '' ,
   PRIMARY KEY (id)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

INSERT INTO payment (SERIAL) VALUES('aaabbb01');

5.2、entities

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

5.3、dao

@Mapper
public interface PaymentDao {
    int save(Payment payment);
    Payment getById(@Param("id") Long id);
}

在 resources 文件下创建 mapper/payment.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.cloud.study.dao.PaymentDao">
    <insert id="save" parameterType="com.cloud.study.entities.Payment" useGeneratedKeys="true" keyProperty="id">
        insert into payment (serial) values (#{serial})
    </insert>

    <resultMap id="baseResultMap" type="com.cloud.study.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="serial" property="serial" jdbcType="VARCHAR"/>
    </resultMap>
    <select id="getById" resultMap="baseResultMap">
        select * from payment where id = #{id}
    </select>
</mapper>

5.4、service

package com.cloud.study.service;

import com.cloud.study.entities.Payment;

public interface PaymentService {
    int save(Payment payment);
    Payment getById(Long id);
}
package com.cloud.study.service.impl;

import com.cloud.study.dao.PaymentDao;
import com.cloud.study.entities.Payment;
import com.cloud.study.service.PaymentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

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

    @Override
    public int save(Payment payment) {
        return paymentDao.save(payment);
    }

    @Override
    public Payment getById(Long id) {
        return paymentDao.getById(id);
    }
}

5.5、controller

package com.cloud.study.controller;

import com.cloud.study.entities.Payment;
import com.cloud.study.entities.R;
import com.cloud.study.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

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

    @PostMapping(value = "/payment/save")
    public R save(@RequestBody Payment payment) {
        int result = paymentService.save(payment);
        log.info("*****插入结果:"+result);
        if (result > 0) {
            return new R(200,"插入数据库成功",result);
        } else {
            return new R(444, "插入数据库失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public R getById(@PathVariable("id") Long id) {
        Payment payment = paymentService.getById(id);
        log.info("*****查询结果:"+payment);
        if (payment != null) {
            return new R(200,"查询成功",payment);
        } else {
            return new R(444, "没有对应记录,查询Id:"+id,null);
        }
    }
}

6.6测试

5.7Run DashBoard

通过修改idea的workspace.xml的方式来快速打开 Run DashBoard 窗口。

开启 Run DashBoard

参考教程为:关于IDEA2019找不到Run Dashboard的问题_IT.小熊的博客-CSDN博客

6.开启热部署

  • Add devtools to your project
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
  • Adding plugin to your pom.xml
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
  • enable automatic build

  • update the value of

按住 Ctrl+Shif+Alt+/ ,并点击 Registry,弹出窗口,并在框出出选中

  • 重启 IDEA

7.构建微服务模块 - 订单模块

1、创建一个 Module ,名称为 cloud-consumer-order80

2、改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>cloud-study</artifactId>
        <groupId>com.cloud.study</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

    <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.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

3、该 yaml

一般对外,供用于访问的端口是 80.

server:
  port: 80

4、主启动类

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

5、业务类

5.1、entities

复用订单模块的 Payment 和 R 这两个类

5.2、RestTemplate

RestTemplate 提供了多种便捷访问远程 Http 服务的方法,是一种简单便捷的访问 restful 服务模板类,是 Spring 提供的用于访问 Rest 服务的客户端模板工具集。

官方API文档

5.3、config 配置类

package com.cloud.study.config;

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

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

5.4、controller

package com.cloud.study.controller;

import com.cloud.study.entities.Payment;
import com.cloud.study.entities.R;
import lombok.extern.slf4j.Slf4j;
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.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@Slf4j
@RestController
public class OrderController {
    private static final String PAYMENT_URL = "http://localhost:8001";

    @Autowired
    private RestTemplate restTemplate;

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

    @GetMapping("/consumer/payment/get/{id}")
    public R get(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, R.class);
    }
}

8.项目重构

观察问题:系统中有重复的部分,重构页面

1、新建 cloud-api-commons 模块。

2、改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>cloud-study</artifactId>
        <groupId>com.cloud.study</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-api-commons</artifactId>

    <dependencies>
        <!--开启热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--常用工具类集合 https://apidoc.gitee.com/dromara/hutool/-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>
    </dependencies>
</project>

3、entities

将 cloud-consumer-order 模块的 entities 包拷贝到 cloud-api-commons 。

4、maven clean install

选择并执行 cloud-api-commons 的 maven clean install

5、订单80和支付8001分别改造

删除 entities 的包,并在pom文件引入 cloud-api-commons 模块

        <!-- 引入自己的 cloud-api-commons 模块-->
        <dependency>
            <groupId>com.cloud.study</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

9.Eureka 服务注册与发现

1、什么是服务治理?

Spring Cloud 封装了 NetFlix 公司开发的 Eureka 模块来实现服务治理

在传统的 rpc 远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,所以需要使用服务治理,管理服务与服务之间的关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册

2、什么是服务注册?

Eureka 采用了 CS 的设计架构,Eureka Server作为服务注册功能的服务器,它是注册中心。而系统中的其他微服务,使用 Eureka 客户端连接到 Eureka Server 并维持心跳连接。这样系统的维护人员就可以通过 Eureka Server 来监控系统中各个微服务是否正常运行。

在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息 比如 服务器通讯地址等以别名的方式注册到注册中心上

另一方(消费者|服务提供者),以别名的方式去注册中心上获取到实际的通讯地址,然后再实现Rpc远程调用框架核心设计思想;在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何 rpc 远程调用框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))。

3、Eureka 的两个组件

Eureka 包含两个组件:Eureka Server 和  Eureka Client

  • EurekaServer 提供服务注册服务:各个微服务节点通过配置启动后,会在 EurekaServer 中进行注册,这样 EurekaServer 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
  • EurekaClient 通过注册中心进行访问:是一个 Java客户端,用于简化 EurekaServer 的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法负载均衡器。在应用启动后,将会向 EurekaServer 发送心跳(默认周期为 30 秒)。如果 EurekaServer 在多个心跳周期内没有收到某个节点的心跳,EurekaServer 将会从服务注册表中把这个节点移除(默认 90 秒)。

3、单机 Eureka 构建步骤

3.1 idea 生成 EurekaServer 端服务注册中心

新建一个 maven 模块,名称为 cloud-eureka-server7001

POM 文件如下

    <dependencies>
        <!-- Eureka server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <!-- 引入自己的 cloud-api-commons 模块-->
        <dependency>
            <groupId>com.cloud.study</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</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>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

新建 application.yaml ,并配置内容

server:
  port: 7001

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

主启动类

@EnableEurekaServer //表示服务为 EurekaServer
@SpringBootApplication
public class EurekaServerMain7001 {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerMain.class, args);
    }
}

启动服务,访问  http://localhost:7001/ ,可看到如下页面

3.2 EurekaClient 端 cloud-provider-payment 将注册进EurekaServer称为服务提供者

改写POM文件,增加Eureka 客户端相关依赖

        <!-- Eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

改写yaml文件,增加 Eureka 客户端相关配置

eureka:
  client:
    register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
    fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://localhost:7001/eureka/

改写主启动类,加上@EnableEurekaClient注解

@EnableEurekaClient //表示Eureka客户端
@SpringBootApplication
public class PaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain.class, args);
    }
}

测试

1.先启动 EurekaServer ,再启动cloud-provider-paymet

2.访问 http://localhost:7001,可以发现 微服务的名称就是配置文件的 spring.application.name 

3.3 EurekaClient 端 cloud-consume-order80 将注册进EurekaServer称为服务消费者

 改写POM文件,增加Eureka 客户端相关依赖

        <!-- Eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

改写yaml文件,增加 Eureka 客户端相关配置

spring:
  application:
    name: cloud-consume-order

eureka:
  client:
    register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
    fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://localhost:7001/eureka/

改写主启动类,加上@EnableEurekaClient注解

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

测试

1.先启动 EurekaServer ,再启动cloud-provider-paymet8001、cloud-consume-order80

2.访问 http://localhost:7001,可以发现 微服务的名称就是配置文件的 spring.application.name 

4、集群 Eureka 构建步骤

4.1Eureka 集群原理说明

问题:微服务 RPC 远程服务调用最核心的是什么?

高可用,试想你注册中心只有一个(only one),它出故障就呵呵了,会导致整个服务环境不可用。

解决办法:搭建Eureka注册中心集群,实现负载均衡+故障容错。

4.2Eureka 集群环境构建步骤

1.参考cloud-eureka-server7001,创建 cloud-eureka-server7002。

2.cloud-eureka-server7002的 pom.xml 配置和 cloud-eureka-serverserver7001 一致

3.修改系统映射配置(我们自行测试时使用,真实环境不应配置

  • 找到 【C:\Windows\System32\drivers\etc\HOSTS】(博主测试环境为 Win10 系统)

  • 修改映射配置添加进 hosts 文件,在文件末尾添加如下内容
#服务注册中心映射配置
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

4.该 yaml 文件

修改 cloud-eureka-server的yaml文件

server:
  port: 7001

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

cloud-eureka-server2的yaml文件

server:
  port: 7002

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

拓展: 如果有N台服务器,这里应该配置多个,以英文逗号(,)分隔,配置的内容为除本机之外的其他注册中心连接。

例如有3台服务器,当前为7001,其他为7002、7003,则配置内容为如下:

defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

5.cloud-eureka-server2的主启动类

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

6.测试是否启动成功

访问 http://eureka7001.com:7001/ 

访问 http://eureka7002.com:7002/ 

4.3 将支付服务微服务发布到2台 Eureka 集群 配置中

只要修改 yaml 中的 Eureka 配置即可,如下所示

eureka:
  client:
    register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
    fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置

4.4将订单服务发布到2台Eureka 集群配置中

 只要修改 yaml 中的 Eureka 配置即可,如下所示

eureka:
  client:
    register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
    fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置

4.5测试1

4.6支付服务提供者集群环境构建

1.参考 cloud-provider-payment8001 新建 cloud-provider-payment8002 moudle

2.cloud-provider-payment8002 的 pom.xml 配置和 cloud-provider-payment8001 一致

3.cloud-provider-payment8002 的 pom.xml 配置 与  cloud-provider-payment8001 yaml 配置基本一致,只是修改修改端口号

注意: spring.application.name ,即服务注册中的别名需要一致

4.主启动

拷贝 cloud-provider-payment8001 的主启动类,修改类名称为 PaymentMain8002

5.业务类

dao、service、controller 直接拷贝,resources 下的mapper 直接拷贝

6.修改8001/8002的 Controller,主要是在log输出中加入端口显示

@Slf4j
@RestController
public class PaymentController {
    @Resource
    private PaymentService paymentService;
    @Value("${server.port}")
    private String serverPort;

    @PostMapping(value = "/payment/save")
    public R save(@RequestBody Payment payment) {
        int result = paymentService.save(payment);
        log.info("*****插入结果:"+result);
        if (result > 0) {
            return new R(200,"插入数据库成功,serverPort:"+serverPort,result);
        } else {
            return new R(444, "插入数据库失败",null);
        }
    }

    @GetMapping(value = "/payment/get/{id}")
    public R getById(@PathVariable("id") Long id) {
        Payment payment = paymentService.getById(id);
        log.info("*****12查询结果:"+payment);
        if (payment != null) {
            return new R(200,"查询成功,serverPort:"+serverPort,payment);
        } else {
            return new R(444, "没有对应记录,查询Id:"+id,null);
        }
    }
}

7.测试服务是否注册成功

访问 http://eureka7001.com:7001/ ,发现所有的微服务都注册了。

4.7负载均衡

1.订单服务的 Controller不能写死

@Slf4j
@RestController
public class OrderController {
//    private static final String PAYMENT_URL = "http://localhost:8001"; //当支付服务是集群环境时,远程地址不能写死
    private static final String PAYMENT_URL = "http://cloud-payment-service";//要写微服务的注册名称,即服务提供者的 spring.application.name

    @Autowired
    private RestTemplate restTemplate;

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

    @GetMapping("/consumer/payment/get/{id}")
    public R get(@PathVariable("id") Long id) {
        return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, R.class);
    }
}

2.在配置类中配置 RestTemplate 组件时使用 @LoadBalanced 注解,赋予RestTemplate负载均衡的能力

@Configuration
public class ApplicationConfig {
    @Bean
    @LoadBalanced //赋予RestTemplate负载均衡的能力
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

4.8 测试2

 我们可以发现页面里面会自动切换不同的服务

{"code":200,"msg":"查询成功,serverPort:8001","data":{"id":1,"serial":"aaabbb01"}}
{"code":200,"msg":"查询成功,serverPort:8002","data":{"id":1,"serial":"aaabbb01"}}
  • Ribbon 和 Eureka 整合后 Consumer 可以直接调用服务而不再关心地址和端口号

5、actuator微服务信息完善

5.1主机名称:服务名称修改

1.当前问题

  • 含有主机名称,如上图主机名称为;
  • 无IP地址信息

2.修改 cloud-provider-payment8001、cloud-provider-payment8002 的yaml,添加 eureka.instance.instance-id 配置项

eureka:
  client:
    register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
    fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置
  instance: #修改节点名称信息
    instance-id: cloud-payment-service8002

3.修改之后

5.2访问信息有IP信息提示

修改 cloud-provider-payment8001、cloud-provider-payment8002 的yaml,添加 eureka.instance.prefer-ip-address 配置项

eureka:
  client:
    register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
    fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置
  instance:
    instance-id: cloud-payment-service8001
    prefer-ip-address: true #注解链接的IP信息

如下图的左下角,现在有了IP信息

6、服务发现 Discovery

对于注册进 Eureka 里面的微服务,可以通过服务发现来获得该服务的信息。

修改 Cloud-provider-payment8001 的Controller

@Slf4j
@RestController
public class PaymentController {
    @Resource
    private PaymentService paymentService;
    @Value("${server.port}")
    private String serverPort;
    @Resource
    private DiscoveryClient discoveryClient;

    ...

    @GetMapping("/payment/discovery")
    public Object discovery() {
        Map<String,Object> map = new HashMap<>();
        List<String> services = discoveryClient.getServices();
        for (String service : services) {
            List<ServiceInstance> instances = discoveryClient.getInstances(service);
            map.put(service, instances);
        }
        return map;
    }
}

启动8001主启动类

输入 http://localhost:8001/payment/discovery,可以发现我们可以获取到微服务的信息

7、Eureka 自我保护

7.1概述

保护模式主要用于一组客户端和EurekaServer之间存在网络分区场景下的保护。一旦进入保护模式,EurekaServer 将会尝试保护其服务注册表中的信息,不在删除服务注册表中的数据,也就是不会注销任何微服务

如果在EurekaServer的首页看到下面这段提示,则说明 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.

7.2导致原因

一句话:某个时刻某个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。微服务的自我保护属于CAP里面的AP分支,它符合高可用的思想。

CAP是 Consistency、Availability、Partition tolerance三个词语的缩写,分别表示一致性、可用性、分区容忍性。

为什么会产生 Eureka 自我保护机制?

为了防止 EurekaClient 可以正常运行,但是与 Eureka 网络不通情况下,EurekaServer 不会立刻将 EurekaClient 服务剔除。

什么是自我保护模式?

默认情况下,如果  EurekaServer 在一定时间内没有接手到某个微服务实例的心跳,EurekaServer 将会注销该实例(默认90秒)。但是当网络分区故障发生(延迟、卡顿、拥挤)时,微服务与EurekaServer 直接无法正常通讯,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka 通过“自我保护模式”来解决这个问题——当EurekaServer 节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。

在自我保护模式中,EurekaServer会保护服务注册表中的信息,不在注销任何服务实例

它的设计哲学就是宁可保留错误的信息,也不盲目注销任何可能健康的实例。一句话理解:好死不如赖活着

综上所述,自我保护是一种应对网络异常的安全保护措施。它的哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让 Eureka 集群更加的健壮、稳定。

7.3怎么禁止自我保护

注册中心 EurekaServer端7001|7002

默认,自我保护机制是开启的,现在需要禁用,修改yaml 配置项 eureka.server.enable-self-preservation=false

eureka:
  instance:
    hostname: eureka7001.com #Eureka 服务端的实例名
  client:
    register-with-eureka: false #false 表示不向注册中心注册自己
    fetch-registry: false #false 表示自己就是注册中心,我的职责就是维护服务端实例,并不需要去检索服务
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      defaultZone: http://eureka7002.com:7002/eureka/
  server:
    enable-self-preservation: false #关闭自我保护机制
    eviction-interval-timer-in-ms: 2000 #心跳间隔时间由默认90000改成2000

启动7001/7002 服务,访问 http://eureka7001.com:7001/ ,变成如下效果。

生产者客户端EurekaClient端8001|8002

修改yaml配置项  eureka.instance.lease-renewal-interval-in-seconds、eureka.instance.lease-expiration-duration-in-seconds,如下

eureka:
  client:
    register-with-eureka: true #表示是否将自己注册进EurekaServer,默认为true
    fetch-registry: true #是否从 EurekaServer 抓取自己有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    service-url:
      #设置与 Eureka Server 交互的地址查询服务和注册服务都需要依赖这个地址
      #      defaultZone: http://localhost:7001/eureka/
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/ #集群 Eureka 配置
  instance:
    instance-id: cloud-payment-service8001
    prefer-ip-address: true #显示IP
    #Eureka 客户端向服务端发送心跳时间间隔,单位为秒(默认30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka 服务端在收到最后一次心跳后等待时间上限,单位为秒(默认90秒),超时将剔除微服务
    lease-expiration-duration-in-seconds: 2

测试

  • 启动Eureka服务端7001、7002
  • 启动微服务8001、8002
  • 访问链接 http://eureka7001.com:7001/, 此时发现8001、8002在页面中

  • 关闭微服务8001,等2秒后,刷新页面发现此时只剩下8002微服务

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值