Spring Cloud_Spring Cloud Alibaba_00000

contents

微服务介绍

微服务架构是一种架构模式。它提倡将单一应用程序划分成一组小的服务。服务之间相互协调、相互配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
在这里插入图片描述
Spring Cloud是分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶。
在这里插入图片描述

版本选择

Spring官网地址:https://spring.io/
Spring Boot官网地址:https://spring.io/projects/spring-boot

Spring Boot版本选择:
git源码地址:https://github.com/spring-projects/spring-boot/releases/
Spring Boot 2.0新特性git说明地址:https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes

Spring Cloud版本选择:
git源码地址:https://github.com/spring-projects/spring-cloud/wiki
Spring Cloud官网地址:https://spring.io/projects/spring-cloud

查看Spring Cloud和Spring Boot之间的依赖关系。地址:https://spring.io/projects/spring-cloud#overview
查看Spring Cloud和Spring Boot之间更详细的依赖关系。地址:https://start.spring.io/actuator/info

Spring Cloud Hoxton.SR1版本官网介绍地址:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/
Spring Cloud中文文档地址:https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md
Spring Boot 2.2.2.RELEASE版本官网介绍地址:https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/

创建聚合父工程

创建Maven Project

选择File->New->Project…
在这里插入图片描述
点击“Next”按钮。
在这里插入图片描述
点击“Next”按钮。
在这里插入图片描述

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.company.springcloud</groupId>
    <artifactId>cloud2020</artifactId>
    <version>1.0-SNAPSHOT</version>

    <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>

        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>

        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>

    </properties>

    <!--子模块继承之后,提供作用:锁定版本+子module不用写groupId和version-->
    <dependencyManagement>

        <dependencies>

            <!--spring boot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--spring cloud-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!--spring cloud alibaba-->
            <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>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>

            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis.spring.boot.version}</version>
            </dependency>

            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
            </dependency>

            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>${log4j.version}</version>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <optional>true</optional>
            </dependency>

        </dependencies>

    </dependencyManagement>

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

</project>

Maven中的dependencyManagement和dependencies

maven使用dependencyManagement元素来提供一种管理依赖版本号的方式。
通常会在一个组织或者项目的最顶层的父POM文件中看到dependencyManagement元素。
使用pom.xml中的dependencyManagement元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。
maven会沿着父子层向上走,直到找到一个拥有dependencyManagement元素的项目,然后它就会使用这个dependencyManagement元素中指定的版本号。

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

dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要使用的依赖。
如果不在子项目中声明依赖,是不会从父项目中继承下来的,只有在子项目中写了该依赖项,并且没有具体版本,才会从父项目中继承该项,并且version和scope都读取自父pom。
如果子项目中指定了版本号,那么会使用子项目中指定的jar版本。

maven中跳过单元测试

maven中跳过单元测试。
父工程创建完成执行mvn:install将父工程发布到仓库方便子工程继承。

相关设置

注解生效激活。
在这里插入图片描述
File Types过滤。
在这里插入图片描述

微服务工程构建

创建Maven模块

模块名称为:cloud-provider-payment8001
提供者支付Module模块。

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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>
    
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>

        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

    </dependencies>

</project>

yml文件

在src文件夹下的main文件夹下的java文件夹下的resources文件夹下创建application.yml文件。

server:
  port: 8001

spring:
  application:
    name: cloud-payment-service

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.company.springcloud.entities #所有Entity别名类所在包

创建并编写主启动类

package com.company.springcloud;

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

@SpringBootApplication
public class PaymentMain8001 {

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

}

业务类

建表

create table payment (
 
    id bigint(20) not null auto_auto_increment comment id,
    'serial' varchar(200) default,
    primary key(id)
    
) engine=innodb auto_increment=1 default charset=utf8

实体类

在com.company.springcloud包下创建包entities,在entities包下创建类Payment。

package com.company.springcloud.entities;

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

import java.io.Serializable;

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

    private long id;
    private String serial;
    
}

JSON封装体CommonResult

package com.company.springcloud.entities;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult <T>{

    private Integer code;
    private String message;
    private T data;

    public CommonResult(Integer code, String message){
        this(code, message, null);
    }

}

DAO层

在com.company.springcloud包下创建包dao,在dao包下创建类PaymentDao。

package com.company.springcloud.dao;

import com.company.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface PaymentDao {

    //增加
    int create(Payment payment);

    //查询
    Payment getPaymentById(@Param("id") Long id);

}

mapper文件

在文件夹src/main/resources下创建文件夹mapper,在mapper文件夹下创建PaymentMapper.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.company.springcloud.dao.PaymentDao">

    <insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
        insert into payment(serial) values (#{serial});
    </insert>

    <resultMap id="BaseResultMap" type="com.company.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <id column="serial" property="serial" jdbcType="VARCHAR" />
    </resultMap>

    <select id="getPaymentById" parameterType="long" resultMap="BaseResultMap">
        select * from payment where id = #{id};
    </select>

</mapper>

service层

在com.company.springcloud包下创建service包,在该包下创建接口PaymentService。

package com.company.springcloud.service;

import com.company.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

public interface PaymentService {

    //增加
    int create(Payment payment);

    //查询
    Payment getPaymentById(@Param("id") Long id);

}

在service包下创建Impl包,在该包下创建接口PaymentService的实现类PaymentServiceImpl。

package com.company.springcloud.service.impl;

import com.company.springcloud.dao.PaymentDao;
import com.company.springcloud.entities.Payment;
import com.company.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    //增加
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    //查询
    public Payment getPaymentById(Long id) {
        return paymentDao.getPaymentById(id);
    }

}

controller层

在com.company.springcloud包下创建controller包,在该包下创建类PaymentController。

package com.company.springcloud.controller;

import com.company.springcloud.entities.CommonResult;
import com.company.springcloud.entities.Payment;
import com.company.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@Slf4j
@RestController
public class PaymentController {

    @Resource
    private PaymentService paymentService;

    @Value("${server.port}")
    private String serverPort;
    
	@PostMapping(value="/payment/create")
    public CommonResult<Integer> create(@RequestBody Payment payment) {

        int result = paymentService.create(payment);

        log.info("*****插入结果:" + result);

        if(result > 0) {
            return new CommonResult<>(200, "插入数据库成功,serverPort:" + serverPort, result);
        } else {
            return new CommonResult<>(444, "插入数据库失败!", null);
        }
    }

    @GetMapping(value="/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {

        Payment payment = paymentService.getPaymentById(id);

        log.info("*****查询结果11:" + payment);

        if(payment != null) {
            return new CommonResult<>(200, "查询成功,serverPort:" + serverPort, payment);
        } else {
            return new CommonResult<>(444, "没有对应记录,查询id:" + id, null);
        }

    }
    
}

测试

测试get请求

启动模块,在浏览器地址栏中输入:http://localhost:8001/payment/get/33进行测试

测试post请求

打开postman进行测试。

开启热部署

添加依赖。

<dependency>
	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

添加插件。
在父工程中的pom.xml文件中的project标签中添加:

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

cloud-consumer-order80微服务消费者订单Module模块

启动类

在src/main/java目录下创建com.company.springcloud包。
在springcloud包下创建OrderMain80类。

package com.company.springcloud;

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

@SpringBootApplication
public class OrderMain80 {

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

}

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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>
    	
    	<!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
    </dependencies>

</project>

配置文件

在src/main目录下创建resources目录。
在resources目录下创建application.yml文件。

server:
  port: 80

spring:
  application:
    name: cloud-order-service

配置

在springcloud包下创建config包。
在config包下创建ApplicationContextConfig类。

package com.company.springcloud.config;

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

@Configuration
public class ApplicationContextConfig {

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

}

实体类

在springcloud包下创建entities包。

在entities包下创建Payment类。

package com.company.springcloud.entities;

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

import java.io.Serializable;

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

    private long id;
    private String serial;

}

在entities包下创建CommonResult类。

package com.company.springcloud.entities;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult <T>{

    private Integer code;
    private String message;
    private T data;

    public CommonResult(Integer code, String message){
        this(code, message, null);
    }

}

controller

在springcloud包下创建包controller。
在controller包下创建OrderController类。

package com.company.springcloud.controller;

import com.company.springcloud.entities.CommonResult;
import com.company.springcloud.entities.Payment;
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.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@Slf4j
@RestController
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/create")
    public CommonResult<Payment> create(Payment payment) {

        return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);

    }

	@GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {

        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); //返回对象为响应体中数据转化成的对象,基本上可以理解为JSON。

    }

}

测试

启动服务,访问如下地址进行测试:
http://localhost/consumer/payment/get/31
http://localhost/consumer/payment/create?serial=one

工程改进

创建cloud-api-commons模块。

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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>

        <!--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>
        </dependency>

        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.1.0</version>
        </dependency>

    </dependencies>

</project>

在src/main/java目录下创建com.company.springcloud.entities包。
将Payment类和CommonResult类复制到entities包下。
将cloud-provider-payment8001模块和cloud-consumer-order80模块中的entities包以及该包下的Payment类和CommonResult类删除。
在cloud-provider-payment8001模块和cloud-consumer-order80模块中pom.xml文件中的project标签下的dependencies标签中添加如下:

<!--parent:cloud-api-commons-->
<dependency>
    <groupId>com.company.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>

跳过测试
在这里插入图片描述
点击clean。
在这里插入图片描述
点击install。
在这里插入图片描述
启动服务进行测试:
http://localhost:8001/payment/get/31

http://localhost/consumer/payment/get/31
http://localhost/consumer/payment/create?serial=two

Eureka

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

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

Eureka Server提供服务注册服务。
各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。

Eureka Client通过注册中心进行访问。
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30s)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90s)。

单机Eureka构建

服务注册中心

创建模块

模块名称:cloud-eureka-server7001

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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>

        <!--parent:cloud-api-commons-->
        <dependency>
            <groupId>com.company.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

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

    </dependencies>

</project>
配置文件

在src/main/resources目录下创建application.yml。

server:
  port: 7001

eureka:
  instance:
    #hostname: eureka7001.com #eureka服务端实例名字
    hostname: localhost
  server:
    enable-self-preservation: true #关闭自我保护模式,默认是true,代表开启自我保护模式
    eviction-interval-timer-in-ms: 2000 #eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒

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

在src/main/java目录下创建com.company.springcloud包,在springcloud包下创建EurekaMain7001类。
启动类加注解@EnableEurekaServer。

package com.company.springcloud;

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

@EnableEurekaServer
@SpringBootApplication
public class EurekaMain7001 {

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

}
测试

启动服务,在浏览器地址栏中输入如下地址进行测试。
http://localhost:7001/
在这里插入图片描述

生产者注册进Eureka

Eureka Client端cloud-provider-payment8001将注册进Eureka Server成为服务提供者provider。

pom.xml

在cloud-provider-payment8001模块中的pom.xml文件中添加如下依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件

在cloud-provider-payment8001模块中的src/main/resources目录下的application.yml文件中添加:

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

在cloud-provider-payment8001模块中的src/main/java目录下的com.company.springcloud包下的PaymentMain8001类上面添加注解:@EnableEurekaClient

package com.company.springcloud;

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

@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {

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

}
测试

先启动模块cloud-eureka-server7001(启动类:EurekaMain7001)
在启动模块cloud-provider-payment8001( 启动类:PaymentMain8001)

服务启动后输入如下地址进行测试:
http://localhost:7001
在这里插入图片描述

消费者注册进Eureka

Eureka Client端cloud-consumer-order80将注册进Eureka Server成为服务消费者consumer。

pom.xml

在cloud-consumer-order80模块中的pom.xml文件中添加如下依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置文件

在cloud-consumer-order80模块中的src/main/resources目录下的application.yml文件中添加:

eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      #defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
      defaultZone: http://localhost:7001/eureka
启动类

在cloud-consumer-order80模块中的src/main/java目录下的com.company.springcloud包下的OrderMain80类上面添加注解:@EnableEurekaClient。

package com.company.springcloud;

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

@EnableEurekaClient
@SpringBootApplication
public class OrderMain80 {

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

}
测试

先启动模块cloud-eureka-server7001(启动类:EurekaMain7001)
在启动模块cloud-provider-payment8001( 启动类:PaymentMain8001)
最后启动模块cloud-consumer-order80(启动类:OrderMain80)

服务启动后输入如下地址进行测试:
http://localhost:7001
http://localhost/consumer/payment/get/31
在这里插入图片描述
在这里插入图片描述

Eureka集群

在这里插入图片描述
微服务RPC远程服务调用最核心的是什么?
高可用。如果注册中心只有一个,那么这个注册中心出现故障了就没有服务可用了。
解决办法:搭建Eureka注册中心集群,实现负载均衡+故障容错。

搭建Eureka集群

创建模块

将cloud-eureka-server7001模块复制,起名为cloud-eureka-server7002。

配置文件

cloud-eureka-server7002模块的配置文件。

server:
  port: 7002

eureka:
  instance:
    hostname: eureka7002.com #eureka服务端实例名字

  server:
    enable-self-preservation: true #自我保护机制,保证不可用服务被及时剔除,默认是true #关闭自我保护模式,默认是true,代表开启自我保护模式
    eviction-interval-timer-in-ms: 2000 #eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒

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

cloud-eureka-server7001模块的配置文件。

server:
  port: 7001

eureka:
  instance:
    hostname: eureka7001.com #eureka服务端实例名字
    #hostname: localhost
  server:
    enable-self-preservation: true #关闭自我保护模式,默认是true,代表开启自我保护模式
    eviction-interval-timer-in-ms: 2000 #eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒

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

cloud-eureka-server7002模块的启动类。

package com.company.springcloud;

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

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

修改host文件:
找到C:\Windows\System32\drivers\etc路径下的hosts文件,输入以下内容:

127.0.0.1  eureka7001.com
127.0.0.1  eureka7002.com
测试

先启动cloud-eureka-server7001模块,
在启动cloud-eureka-server7002模块。

输入如下地址进行测试:
http://localhost:7001
http://localhost:7002
http://eureka7001.com:7001
http://eureka7002.com:7002
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

订单、支付两微服务注册进Eureka集群

修改配置文件

修改cloud-provider-payment8001模块的配置文件:

server:
  port: 8001

spring:
  application:
    name: cloud-payment-service

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.company.springcloud.entities #所有Entity别名类所在包

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

修改cloud-consumer-order80模块的配置文件:

server:
  port: 80

spring:
  application:
    name: cloud-order-service

eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
      #defaultZone: http://localhost:7001/eureka
测试

启动顺序:
1、cloud-eureka-server7001模块
2、cloud-eureka-server7002模块
3、cloud-provider-payment8001模块
4、cloud-consumer-order80模块

输入如下地址进行测试:
http://localhost:7001
http://localhost:7002
http://eureka7001.com:7001
http://eureka7002.com:7002
http://localhost/consumer/payment/get/31
http://localhost/consumer/payment/create?serial=five
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

生产者集群搭建

支付服务提供者集群搭建。

创建支付服务的另一个模块

创建cloud-provider-payment8002模块。
此模块的创建可以复制cloud-provide-payment8001模块。

pom.xml

此pom.xml里面的内容和cloud-provide-payment8001模块中的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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>

        <!--parent:cloud-api-commons-->
        <dependency>
            <groupId>com.company.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!--jdbc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
        </dependency>

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

    </dependencies>

</project>
application.yaml

在src/main文件夹下创建resources文件夹;
在resources文件夹中创建application.xml文件。

server:
  port: 8002

spring:
  application:
    name: cloud-payment-service

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.gjt.mm.mysql.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root

mybatis:
  mapperLocations: classpath:mapper/*.xml
  type-aliases-package: com.atguigu.springcloud.entities #所有Entity别名类所在包

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

  instance:
    instance-id: payment8002
    prefer-ip-address: true #访问路径可以显示ip地址
启动类

在src/main/java文件夹中创建com.company.springcloud包;
在springcloud包下创建PaymentMain8002类。

package com.company.springcloud;

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

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

在resource文件夹下创建mapper文件夹;
在mapper文件夹下创建PaymentMapper.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.company.springcloud.dao.PaymentDao">

    <insert id="create" parameterType="com.company.springcloud.entities.Payment" useGeneratedKeys="true" keyProperty="id">
        insert into payment(serial) values (#{serial});
    </insert>

    <resultMap id="BaseResultMap" type="com.company.springcloud.entities.Payment">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <id column="serial" property="serial" jdbcType="VARCHAR" />
    </resultMap>

    <select id="getPaymentById" parameterType="long" resultMap="BaseResultMap">
        select * from payment where id = #{id};
    </select>

</mapper>
DAO

在springcloud包下创建dao包。
在dao包下创建PaymentDao接口。

package com.company.springcloud.dao;

import com.company.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface PaymentDao {
    //增加
    int create(Payment payment);

    //查询
    Payment getPaymentById(@Param("id") Long id);

}
service

在springcloud包下创建service包;
在service包下创建PaymentService接口。

package com.company.springcloud.service;

import com.company.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

public interface PaymentService {

    //增加
    public int create(Payment payment);

    //查询
    public Payment getPaymentById(@Param("id") Long id);

}
service实现类

在service包下创建impl包;
在impl包下创建PaymentServiceImpl类。

package com.company.springcloud.service.impl;

import com.company.springcloud.dao.PaymentDao;
import com.company.springcloud.entities.Payment;
import com.company.springcloud.service.PaymentService;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class PaymentServiceImpl implements PaymentService {

    @Resource
    private PaymentDao paymentDao;

    //增加
    public int create(Payment payment) {
        return paymentDao.create(payment);
    }

    //查询
    public Payment getPaymentById(@Param("id") Long id) {
        return paymentDao.getPaymentById(id);
    }

}
controller

在springcloud包下创建controller包;
在controller包下创建PaymentController类。

package com.company.springcloud.controller;

import com.company.springcloud.entities.CommonResult;
import com.company.springcloud.entities.Payment;
import com.company.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @PostMapping(value="/payment/create")
    public CommonResult<Integer> create(@RequestBody Payment payment) {

        int result = paymentService.create(payment);

        log.info("****插入结果d:" + result);

        if(result > 0) {
            return new CommonResult<>(200, "插入数据库成功,serverPort:" + serverPort, result);
        } else {
            return new CommonResult<>(444, "插入数据库失败", null);
        }

    }

    @GetMapping(value="/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {

        Payment payment = paymentService.getPaymentById(id);

        log.info("****查询结果:" + payment);

        if(payment != null) {
            return new CommonResult<>(200, "查询成功,serverPort:" + serverPort, payment);
        } else {
            return new CommonResult<>(444, "没有对应记录,查询id:" + id, null);
        }
    }

}
相关修改

在cloud-consumer-order80模块下的src/main/java文件夹下的company.springcloud.config包下的ApplicationContextConfig类上添加注解:@LoadBalanced。
目的:开启负载均衡。

package com.company.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 {

    @LoadBalanced //开启负载均衡
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

在cloud-consumer-order80模块下的src/main/java文件夹下的company.springcloud.controller包下的OrderController类修改如下:

public static final String PAYMENT_URL = “http://localhost:8001”;
修改为:
public static final String PAYMENT_URL = “http://CLOUD-PAYMENT-SERVICE”;

测试

启动服务。启动顺序:
1、cloud-eureka-server7001模块
2、cloud-eureka-server7002模块
3、cloud-provider-payment8001模块
4、cloud-provider-payment8002模块
5、cloud-consumer-order80模块

输入以下地址进行测试:
http://localhost:7001
http://localhost:7002
http://eureka7001.com:7001
http://eureka7002.com:7002
http://localhost/consumer/payment/get/31
http://localhost/consumer/payment/create?serial=six
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

服务发现DiscoveryClient

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

controller

以cloud-provider-payment8001生产者模块为例。

在cloud-provider-payment8001模块下的src/main/java文件夹下的com.company.springcloud.controller包下的PaymentController类中添加如下代码:

@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value="/payment/discovery")
public Object discovery() {

    List<String> services = discoveryClient.getServices();

    for(String service : services){
        log.info("*****" + service);
    }

    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;

}
启动类

在在cloud-provider-payment8001模块下的src/main/java文件夹下的com.company.springcloud包下PaymentMain8001类上面添加注解:@EnableDiscoveryClient。

package com.company.springcloud;

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

@EnableDiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {

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

}
测试

启动服务,启动顺序:
1、cloud-eureka-server7001模块
2、cloud-eureka-server7002模块
3、cloud-provider-payment8001模块

输入如下地址进行测试:
http://localhost:8001/payment/discovery
在这里插入图片描述
控制台输入如下内容:

*****cloud-payment-service
*****cloud-order-service
CLOUD-PAYMENT-SERVICE 192.168.1.3 8001 http://192.168.1.3:8001
CLOUD-PAYMENT-SERVICE 192.168.1.3 8002 http://192.168.1.3:8002

Eureka自我保护

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

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

属于CAP里面的AP分支。

为什么会产生Eureka自我保护机制?
为了防止Eureka Client可以正常运行,但是与Eureka Server网络不通的情况下,Eureka Server不会立刻将Eureka Client服务剔除。

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

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

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不在注销任何服务实例。它的设计哲学就是宁可保留错误的服务注册信息,也不注销任何可能健康的服务实例。

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

禁止Eureka自我保护

默认自我保护机制是开启的:eureka.server.enable-self-preservation=true
禁用自我保护模式:eureka.server.enable-self-preservation=false

在cloud-eureka-server7001模块的src/main/resources文件夹中的application.xml文件中添加如下内容:

eureka:
  server:
    enable-self-preservation: false #关闭自我保护模式,默认是true,代表开启自我保护模式
    eviction-interval-timer-in-ms: 2000 #eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
eureka:
  instance:
    lease-renewal-interval-in-seconds: 30 #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30s)
    lease-expiration-duration-in-seconds: 90 #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务

Zookeeper服务注册与发现

Consul

官网地址:https://www.consul.io
中文参考文档地址:https://www.springcloud.cc/spring-cloud-consul.html

Consul是一套开源的分布式服务发现和配置管理系统。
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网络,总之Consul提供了一种完整的服务网格解决方案。
它具有很多优点。包括:基于raft协议,比较简洁;支持健康检查,同时支持HTTP和DNS协议,支持跨数据中心的WAN集群,提供图形界面、跨平台,支持Linux、Mac、Windows。

Consul下载、安装

官网下载,下载后会得到一个.zip压缩包,解压该压缩包会得到一个consul.exe文件。

查看版本:控制台进入consul.exe所在目录,输入命令:consul --version
在这里插入图片描述
启动:
方式一:双击consul.exe
方式二:控制台进入consul.exe目录,输入命令:consul agent -dev
在这里插入图片描述
访问首页地址:http://localhost:8500
在这里插入图片描述

将服务提供者注册进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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>

        <!--parent:cloud-api-commons-->
        <dependency>
            <groupId>com.company.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--consul-discovery-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</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>

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

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

</project>

application.yaml

在src/main文件夹下创建resources文件夹,在resources文件夹下创建application.yml文件。

server:
  port: 8006

spring:
  application:
    name: consul-provider-payment

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

主启动类

在src/main/java文件夹下创建com.company.springcloud包,在springcloud包下创建PaymentMain8006类。

package com.company.springboot;

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

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {

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

controller

在springcloud包下创建controller包,在controller包下创建PaymentController类。

package com.company.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;

@RestController
@Slf4j
public class PaymentController {

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

    @RequestMapping("/payment/consul")
    public String paymentConsul() {

        return "springcloud with consul:" + serverPort + "\t" + UUID.randomUUID().toString();

    }

}

测试

启动cloud-providerconsul-payment8006模块。

启动服务后,输入以下地址:
http://localhost:8500
http://localhost:8006/payment/consul
在这里插入图片描述

在这里插入图片描述

将服务提供者注册进Consul

创建模块

创建cloud-consumerconsul-order80模块。

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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumerconsul-order80</artifactId>


    <dependencies>

        <!--parent-->
        <dependency>
            <groupId>com.company.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--consul-discovery-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>

        <!--devtools-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!--actuator-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </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>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

</project>

application.yml

在src/main文件夹下创建resources文件夹,在resources文件夹下创建application.yml文件。

server:
  port: 80

spring:
  application:
    name: consul-consumer-order

  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        service-name: ${spring.application.name}

主启动类

在src/main/java文件夹下创建com.company.springcloud包,在springcloud包下创建OrderConsulMain80类。

package com.company.springcloud;

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

@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或zookeeper作为注册中心时注册服务
public class OrderConsulMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderConsulMain80.class, args);
    }
}

配置Bean

在springcloud包下创建config包,在config包下创建ApplicationContextConfig类。

package com.company.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 {

    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

controller

在springcloud包下创建controller包,在controller包下创建OrderConsulController类。

package com.company.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 {

    @Resource
    private RestTemplate restTemplate;

    private static final String INVOKE_URL = "http://consul-provider-payment/";

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

}

测试

启动服务。启动服务顺序:
1、cloud-providerconsul-payment8006模块
2、cloud-consumerconsul-order80模块

服务启动后,输入如下地址:
http://localhost:8500
http://localhost:8006/payment/consul
http://localhost/consumer/payment/consul
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Eureka、Zookeeper、Consul的区别

CAP:
C:Consistency(强一致性)
A:Availability(可用性)
P:Partition tolerance(分区容错

CAP理论关注的粒度是数据,而不是整体系统设计的策略。

最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA-单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP-满足一致性,分区容忍性的系统,通常性能不是特别高。
AP-满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
在这里插入图片描述
AP架构
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP。
在这里插入图片描述
CP(Zookeeper/Consul)
CP架构
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。
在这里插入图片描述

Ribbon

负载均衡服务调用。

Spring Cloud Ribbon是客户端负载均衡工具,主要功能是提供客户端的负载均衡算法与服务调用。
Ribbon客户端组件提供一系列完善的配置项,如连接超时、重试等。简单的说就是在配置文件中列出Load Balance(简称LB)后面所有的机器,Ribbon会自动的基于某种规则(如随机、轮询、最少连接数、一致性哈希、权重随机)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

GitHub地址:https://github.com/netflix/ribbon

LB(Load Balance)是什么?
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。

Ribbon本地负载均衡客户端与Nginx服务端负载均衡的区别:
Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的。
Ribbon本地负载均衡在调用微服务接口时,会在注册中心获取注册信息服务列表,之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

集中式LB:
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。

进程内LB:
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
在这里插入图片描述
Ribbon在工作时分成两步:
第一步:先选择Eureka Server,它优先选择在同一个区域内负载较少的server。
第二步:根据用户指定的策略,从server取到的服务注册列表中选择一个地址。

Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

RestTemplate

API地址:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
    return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); //返回对象为响应体中数据转化成的对象,基本上可以理解为JSON。
}

@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {

    //返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
    ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);

    if(entity.getStatusCode().is2xxSuccessful()) {

        return entity.getBody();
    }

    return new CommonResult<>(444, "操作失败");

}
@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {

    return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);
}

@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create1(Payment payment) {

    return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();
}

自定义算法

在模块cloud-consumer-order80的src/main/java目录下的com.company包下创建my_rule包,在my_rule包下创建MyRule类。

package com.company.my_rule;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyRule {

    @Bean
    public IRule mySelfRule(){
        return new RandomRule(); //定义为随机算法
    }

}

在cloud-consumer-order80模块的src/main文件夹中的com.company.springcloud包中的OrderMain80主启动类上面添加注解:@RibbonClient

package com.company.springcloud;

import com.company.my_rule.MyRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;

@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRule.class) //使用自定义的ribbon算法
@EnableEurekaClient
@SpringBootApplication
public class OrderMain80 {

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

}

测试:
启动服务进行测试,服务启动顺序:
1、cloud-eureka-server7001模块
2、cloud-eureka-server7002模块
3、cloud-provider-payment8001模块
4、cloud-provider-payment8002模块
5、cloud-consumer-order80模块

在浏览器地址栏中输入以下地址:
http://localhost/consumer/payment/get/31

手写轮询算法

cloud-provider-payment8001模块下的src/main/java文件夹下的com.company.springcloud.controller包下的PaymentController类添加如下代码:

@GetMapping(value = "/payment/lb")
public String getPaymentLB(){
    return serverPort;
}

cloud-provider-payment8002模块下的src/main/java文件夹下的com.company.springcloud.controller包下的PaymentController类添加如下代码:

@GetMapping(value = "/payment/lb")
public String getPaymentLB(){
    return serverPort;
}

将cloud-consumer-order80模块下的src/main/java文件夹下的com.company.config包下的ApplicationContextConfig类上面的注解@LoadBalanced去掉。

package com.company.springcloud.config;

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

@Configuration
public class ApplicationContextConfig {

    //@LoadBalanced //开启负载均衡
    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

在cloud-provider-payment8002模块下的src/main/java文件夹下的com.company.springcloud包下创建lb包,在lb包下创建LoadBalancer接口。

package com.company.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

public interface LoadBalancer {

    //收集服务器总共有多少台能够提供服务的机器,并放到list里面
    ServiceInstance instances(List<ServiceInstance> serviceInstances);

}

在cloud-provider-payment8002模块下的src/main/java文件夹下的com.company.springcloud.lb下创建MyLB类。

package com.company.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

//自定义ribbon轮询算法
@Component
public class MyLB implements LoadBalancer {

    private final AtomicInteger atomicInteger = new AtomicInteger(0);

    //坐标
    private int getAndIncrement(){

        int current;
        int next;

        do {
            current = this.atomicInteger.get();
            next = current >= 2147483647 ? 0 : current + 1;
        }while (!this.atomicInteger.compareAndSet(current, next));  //第一个参数是期望值,第二个参数是修改值是

        System.out.println("*****第几次访问,次数next: " + next);

        return next;

    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {  //得到机器的列表

        int index = getAndIncrement() % serviceInstances.size(); //得到服务器的下标位置
        return serviceInstances.get(index);

    }

}

在cloud-provider-payment8002模块下的src/main/java文件夹下的com.company.springcloud.controller包下的OrderController类中添加如下代码:

package com.company.springcloud.controller;

import com.company.springcloud.entities.CommonResult;
import com.company.springcloud.entities.Payment;
import com.company.springcloud.lb.LoadBalancer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URI;
import java.util.List;

@Slf4j
@RestController
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;

   @Resource
    private LoadBalancer loadBalancer; //自己写的轮询算法

    @Resource
    private DiscoveryClient discoveryClient;

    @GetMapping("/consumer/payment/create")
    public CommonResult<Payment> create(Payment payment) {

        return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class);

    }

    @GetMapping("/consumer/payment/create1")
    public CommonResult<Payment> create1(Payment payment) {

        return restTemplate.postForEntity(PAYMENT_URL + "/payment/create", payment, CommonResult.class).getBody();

    }

    @GetMapping("/consumer/payment/get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {

        return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); //返回对象为响应体中数据转化成的对象,基本上可以理解为JSON。

    }

    @GetMapping("/consumer/payment/getForEntity/{id}")
    public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {

        //返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);

        if(entity.getStatusCode().is2xxSuccessful()) {

            return entity.getBody();

        }

        return new CommonResult<>(444, "操作失败!");

    }

    @GetMapping(value="/consumer/payment/lb")
    public String getPaymentLB() {

        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");

        if(instances == null || instances.size() <=0 ) {

            return null;

        }

        ServiceInstance serviceInstance = loadBalancer.instances(instances);
        URI uri = serviceInstance.getUri();

        return restTemplate.getForObject(uri + "/payment/lb", String.class);

    }

}

测试:
启动服务进行测试,服务启动顺序:
1、cloud-eureka-server7001模块
2、cloud-eureka-server7002模块
3、cloud-provider-payment8001模块
4、cloud-provider-payment8002模块
5、cloud-consumer-order80模块

在浏览器地址栏中输入以下地址:
http://localhost/consumer/payment/lb
在这里插入图片描述
在这里插入图片描述

OpenFeign

服务接口调用。

官网地址:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign
gitHub地址:https://github.com/spring-cloud/spring-cloud-openfeign

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口在上面添加注解。
Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。
Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。

Feign能做什么?
Feign旨在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon。
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

Feign和OpenFeign的区别?

FeignOpenFeign
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端。Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口就可以调用服务注册中心的服务。OpenFeign是Spring Cloud在Feign的基础上支持了Spring MVC的注解,如@RequestMapping等等。OpenFeign的@FeignClient可以解析Spring MVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

OpenFeign使用

模块创建

cloud-consumer-feign-order80模块。
Feign在消费端使用。

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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>

        <!---->
        <dependency>
            <groupId>com.company.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

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

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

</project>

配置文件

在cloud-consumer-feign-order80模块下的src/main文件下创建resources文件夹;
在resources文件下创建application.yml文件。

server:
  port: 80

eureka:
  client:
    register-with-eureka: false

    service-url:
      defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka

启动类

在src/main/java文件夹下创建com.company.springcloud包,在springcloud包下创建OrderFeignMain80类。

package com.company.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@EnableFeignClients
@SpringBootApplication
public class OrderFeignMain80 {

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

}

service

在springcloud包下创建service包,在service包下创建PaymentFeignService接口。

package com.company.springcloud.service;

import com.company.springcloud.entities.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

    @GetMapping(value = "/payment/get/{id}")
    CommonResult getPaymentById(@PathVariable("id") Long id);

    @GetMapping(value = "/payment/feign/timeout")
    String paymentFeignTimeout();

}

controller

在springcloud包下创建controller包,在controller包下创建OrderFeignController类。

package com.company.springcloud.controller;

import com.company.springcloud.entities.CommonResult;
import com.company.springcloud.entities.Payment;
import com.company.springcloud.service.PaymentFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class OrderFeignController {

    @Resource
    private PaymentFeignService paymentFeignService;

    @GetMapping(value = "/consumer/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
        return paymentFeignService.getPaymentById(id);
    }

}

测试

启动服务进行测试,启动顺序:
1、cloud-eureka-server7001模块
2、cloud-eureka-server7002模块
3、cloud-provider-payment8001模块
4、cloud-provider-payment8002模块
5、cloud-consumer-feign-order80模块

在浏览器地址栏中输入以下地址进行测试:
http://localhost/consumer/payment/get/31
在这里插入图片描述

OpenFeign超时控制

默认OpenFeign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致OpenFeign客户端不想等待了,直接返回报错。为了避免这样的情况,需要设置OpenFeign客户端的超时控制。
在OpenFeign客户端的yaml文件中开启配置。

超时设置,故意设置超时演示出错情况。

服务提供者cloud-provider-payment8001模块故意写暂停程序。
在cloud-provider-payment8001模块下的src/main/java文件夹下的com.company.springcloud.comtroller包下的PaymentController类下添加如下代码:

@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout() {
    try {
        
        TimeUnit.SECONDS.sleep(3);

    }catch (Exception e) {
        e.printStackTrace();
    }

    return serverPort;
}

在服务消费者cloud-consumer-feign-order80模块下的src/main/java文件夹下的com.company.springcloud.service包下的PaymentFeignService接口添加超时方法。

@GetMapping(value = "/payment/feign/timeout")
String paymentFeignTimeout();

在服务消费者cloud-consumer-feign-order80模块下的src/main/java文件夹下的com.company.springcloud.controller包下的PaymentFeignController类中添加超时方法。

@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){

    return paymentFeignService.paymentFeignTimeout();
    
}

测试
启动服务,按照以下顺序启动:
1、cloud-eureka-server7001模块
2、cloud-eureka-server7002模块
3、cloud-provider-payment8001模块
4、cloud-consumer-feign-order80模块

在浏览器地址栏输入以下地址:
http://localhost:8001/payment/feign/timeout
http://localhost/consumer/payment/feign/timeout
在这里插入图片描述
在这里插入图片描述
解决:在服务消费者cloud-consumer-feign-order80模块下的src/main/resources文件夹下的application.yml文件里添加:

ribbon:
  ReadTimeout:  5000
  ConnectTimeout: 5000

测试:重启cloud-consumer-feign-order80模块。
在浏览器地址栏中输入以下地址:
http://localhost/consumer/payment/feign/timeout
在这里插入图片描述

OpenFeign日志打印功能

OpenFeign提供了日志打印功能,可以通过配置来调整日志级别,从而了解OpenFeign中HTTP请求的细节。
对OpenFeign接口的调用情况进行监控和输出。

日志级别:
NONE:默认的,不显示任何日志。
BASIC:仅记录请求方法、URL、响应状态码及执行时间。
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息。
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

在cloud-consumer-feign-order80模块下的src/main/java文件夹下的com.company.springcloud包下创建config包,在config包下创建OpenFeignConfig类。

package com.company.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenFeignConfig {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }

}

在cloud-consumer-feign-order80模块下的src/main/java/resources文件夹下的application.yml文件中添加:

logging:
  level:
    com.company.springcloud.service.PaymentFeignService: debug

测试:
重新启动cloud-consumer-feign-order80模块。
在浏览器地址栏中输入以下地址:
http://localhost/consumer/payment/feign/timeout
在这里插入图片描述
在这里插入图片描述

Hystrix

断路器。

分布式系统面临的问题?
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它微服务,这就是所谓的“扇出”。
如果“扇出”的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效用”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

当发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免地会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

“断路器”本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

服务降级

新建模块

新建模块:cloud-provider-hystrix-payment8001。

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>cloud2020</artifactId>
        <groupId>com.company.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>

        <!--parent:cloud-api-commons-->
        <dependency>
            <groupId>com.company.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

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

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

        <!--test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

</project>

配置文件

在src/main文件夹下创建resources文件夹,在resources文件夹下创建application.yml文件。

server:
  port: 8001

spring:
  application:
    name: CLOUD-PROVIDER-HYSTRIX-PAYMENT
  #    eviction-interval-timer-in-ms: 2000

eureka:
  client:
    register-with-eureka: true #表示不向注册中心注册自己
    fetch-registry: true #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务

    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/ #设置与eureka server交互的地址,查询服务和注册服务都需要依赖这个地址

  instance:
    instance-id: provider_payment_hystrix_8001
    prefer-ip-address: true #左下角显示ip地址

#  server:
#    enable-self-preservation: false

启动类

在src/main/java文件夹下创建com.company.springcloud包,在springcloud包下创建PaymentHystrixMain8001类。

说明

JVM/JUC/JMM/GC/Nginx/RabbitMQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值