Spring Cloud笔记 初级篇

前期概述

环境版本,有出入的话会导致各种各样奇奇怪怪的问题
Spring Cloud笔记 初级篇_第1张图片

组件主要内容

Spring Cloud笔记 初级篇_第2张图片

自动热部署Devtools

改动代码实时编译出来就可以运行,及时看到修改内容,自动化重启
如何使用呢?
第一步,把依赖粘贴进子工程pom


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>

第二部,把插件加入父工程pom

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-maven-pluginartifactId>
      <configuration>
        <fork>truefork>
        <addResources>trueaddResources>
      configuration>
    plugin>
  plugins>
build>

第三步,idea开启自动编译权限
Spring Cloud笔记 初级篇_第3张图片
第四步,配置自动编译
不同版本idea之间或许会有些许差异,找到名字勾上就行
Spring Cloud笔记 初级篇_第4张图片
Spring Cloud笔记 初级篇_第5张图片
注意:这个功能仅限在开发阶段使用,实际上线部署后这个功能必须关闭

支付小Demo

架构图

Spring Cloud笔记 初级篇_第6张图片
写一个微服务模块的步骤

1.建module
2.改pom
3.写yml
4.主启动
5.业务类

微服务提供者8001(订单支付模块)

新建module
Spring Cloud笔记 初级篇_第7张图片
建完之后的样子
Spring Cloud笔记 初级篇_第8张图片
子工程的pom文件
Spring Cloud笔记 初级篇_第9张图片
子工程的yml文件

server:
  port: 8001
spring:
  application:
    name: cloud-payment-service
  datasource:
    # 当前数据源操作类型
    type: com.alibaba.druid.pool.DruidDataSource
    # mysql驱动类
    driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3307/db2019?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
mybatis:
  mapper-locations: classpath*:mapper/*.xml
  #实体类
  type-aliases-package: com.eiletxie.springcloud.entities

主启动类

package com.springcloud.cc;

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);
    }
}

设计表、实体类、json结果封装体
Spring Cloud笔记 初级篇_第10张图片

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment {
    private Long id;
    private String serial;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private Integer code;
    private String msg;
    private T data;

    //介于上面已经有了全参和空参的构造方法,这里再加一个两个参数只有code和msg的构造方法
    
    public CommonResult(Integer code, String msg) {
        this(code, msg, null);
    }

}

dao层以及对应mapper文件

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

    public Payment getPaymentById(@Param("id") Long id);
}


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springcloud.cc.dao.PaymentDao">
    <insert id="create" parameterType="com.springcloud.cc.eneties.Payment" useGeneratedKeys="true" keyProperty="id">
        insert into payment (serial)
        Values (#{serial});
    insert>
    
    <resultMap id="BaseResultMap" type="com.springcloud.cc.eneties.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>

业务类

public interface PaymentService {
    public int create(Payment payment);

    public Payment getPaymentById(@Param("id") Long id);
}

业务实现类

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

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

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

controller层
注意有个@RequestBody注解,把地址栏里的内容匹配成对象


@RestController
@Slf4j
@RequestMapping("payment")
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @PostMapping("/create")
    //注意有个@RequestBody注解,把地址栏里的内容匹配成对象
    public CommonResult create(@RequestBody Payment payment){
        int result = paymentService.create(payment);
        log.info("插入结果"+result);
        if (result > 0) {
            return new CommonResult(200, "插入OK",result);
        } else {
            return new CommonResult(400, "error",null);
        }
    }

    @GetMapping("/getPaymentById/{id}")
    public CommonResult getPaymentById(@PathVariable("id") Long id){
        Payment payment=paymentService.getPaymentById(id);
        log.info("查询结果"+payment.toString());
        if (payment!=null){
            return new CommonResult(200, "查询成功", payment);
        }else {
            return new CommonResult(400, "查询失败", payment);
        }
    }
}

这个模块的整体分层结构
Spring Cloud笔记 初级篇_第11张图片
通过APIFox测试controller接口
Spring Cloud笔记 初级篇_第12张图片

Spring Cloud笔记 初级篇_第13张图片

微服务消费者80(消费者调用支付模块)

工程结构和上面的差不多
先说一下RestTemplate
Spring Cloud笔记 初级篇_第14张图片
Spring Cloud笔记 初级篇_第15张图片
使用的话要加入restTemplate配置类

@Configuration
public class ApplicationContextConfig {
    //这里记得用@Bean把这个方法注册到容器,不然在Controller里面无法自动注入
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

controller层

@RestController
@Slf4j
@RequestMapping("consumer/payment")
public class OrderController {

    public static final String Primart_Url = "http://localhost:8001";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("create")
    public CommonResult<Payment> commonResult(Payment payment){
		//这里通过拼接就可以访问到支付服务端url:http://localhost:8001/payment/create
        return restTemplate.postForObject(Primart_Url + "/payment/create", payment, CommonResult.class);
    }

   @GetMapping("get/{id}")
    public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
  		//这里通过拼接就可以访问到支付服务端url:http://localhost:8001/payment/getPaymentById/{id}
        return restTemplate.getForObject(Primart_Url + "/getPaymentById/" + id, CommonResult.class);
    }

}

测试controller接口
Spring Cloud笔记 初级篇_第16张图片
Spring Cloud笔记 初级篇_第17张图片
观察问题:两个微服务的实体类部分是通用的,这就涉及到了新的内容,工程重构

工程重构

观察cloud-consumer-order80与cloud-provider-payment8001两工程有重复代码(entities包下的实体)(坏味道),重构。

1.新建 - cloud-api-commons

2.POM


<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>springCloudartifactId>
        <groupId>com.springcloud.ccgroupId>
        <version>1.0-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>cloud-api-commonsartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.1.0version>
        dependency>
    dependencies>
project>

3.entities

将cloud-consumer-order80与cloud-provider-payment8001两工程的公有entities包移至cloud-api-commons工程下。

4.maven clean、install cloud-api-commons工程,以供给cloud-consumer-order80与cloud-provider-payment8001两工程调用。

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

将cloud-consumer-order80与cloud-provider-payment8001两工程的公有entities包移除
引入cloud-api-commons依赖

<dependency>
    <groupId>com.lun.springcloudgroupId>//安照你自己工程内的内容对应修改,不能一模一样
    <artifactId>cloud-api-commonsartifactId>
    <version>${project.version}version>
dependency>

删除原有位置重新导包即可
Spring Cloud笔记 初级篇_第18张图片

服务注册与发现—Eureka

基础知识

Eureka主管服务注册
就像上面的生产者和消费者的模式,两个还好,如果一多了,就会出现混乱难以管理的情况。这个时候就需要服务治理的情况。
Spring Cloud笔记 初级篇_第19张图片
在这里插入图片描述

Eureka架构
Spring Cloud笔记 初级篇_第20张图片
Dubbo架构
Spring Cloud笔记 初级篇_第21张图片

Eureka Server

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

Eureka Client

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

注:“心跳”指的是一段定时发送的自定义信息,让对方知道自己“存活”,以确保连接的有效性。大部分 CS 架构的应用程序都采用了心跳机制,服务端和客户端都可以发心跳。通常情况下是客户端向服务器端发送心跳包,服务端用于判断客户端是否在线。

构建单机Eureka

默认端口号7001
Spring Cloud笔记 初级篇_第22张图片
改pom引入Eureka Server端依赖,新老版本的依赖对比

以前的老版本(当前使用2018),没有标示,无法分辨是服务端还是客户端
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
 
现在新版本(当前使用2020.2),新版本加入了server的标示,能够对服务端进行区分
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

yml文件配置内容

server:
  port: 7001

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

Spring Cloud笔记 初级篇_第23张图片
编写完毕后,要在主启动类上加入注解,来标示这个服务是Eureka的注册中心
@EnableEurekaServer
Spring Cloud笔记 初级篇_第24张图片
浏览器访问7001端口:http://localhost:7001/
得到如下页面即为注册成功
Spring Cloud笔记 初级篇_第25张图片
在这里插入图片描述

把服务入驻进入注册中心

支付端口8001入驻

把之前的8001端口的支付服务入驻进Eureka,这个时候8001的身份就变成了Eureka Client,那要做哪些工作呢?
首当其冲就是加入**@EnableEurekaClient**注解,如果要加入这个注解,就一定要改POM文件加依赖
改pom引入Eureka Server端依赖,新老版本的依赖对比

 以前的老版本(当前使用2018),没有标示,无法分辨是服务端还是客户端
<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
 
 
现在新版本(当前使用2020.2),新版本加入了server的标示,能够对客户端进行区分
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Spring Cloud笔记 初级篇_第26张图片
Spring Cloud笔记 初级篇_第27张图片
加完了这些之后,就得改yml了,你得让Eureka 认出客户端
Spring Cloud笔记 初级篇_第28张图片
启动服务再次访问
Spring Cloud笔记 初级篇_第29张图片
配置yml的名字和实例相对应
Spring Cloud笔记 初级篇_第30张图片

订单微服务80入驻

过程和上面差不多,加依赖,改yml,加注解,启动服务,测试
yml文件
Spring Cloud笔记 初级篇_第31张图片
加注解
Spring Cloud笔记 初级篇_第32张图片
访问测试
Spring Cloud笔记 初级篇_第33张图片
测试访问成功
Spring Cloud笔记 初级篇_第34张图片

Eureka集群

集群搭建的原理说明

多个Eureka服务之间互相注册,相互守望
Spring Cloud笔记 初级篇_第35张图片
问题:微服务RPC远程服务调用最核心的是什么
高可用,试想你的注册中心只有一个only one, 它出故障了那就呵呵( ̄▽ ̄)"了,会导致整个为服务环境不可用,所以
  解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错

集群搭建

集群就是不再是单一的7001,要有多个注册中心,包括7002,7003…多个注册中心组集群
先添加映射
Spring Cloud笔记 初级篇_第36张图片
新建时和7001的新建过程一样,一定要把Eureka的服务改名,都叫一个名字,Client端就傻了
由于之前的7001里没改名,还叫localhost,所以这边要先改7001的名字
Spring Cloud笔记 初级篇_第37张图片
编写7002的yml文件
Spring Cloud笔记 初级篇_第38张图片
7002主启动类
Spring Cloud笔记 初级篇_第39张图片
两个注册中心同时启动
在这里插入图片描述
访问7001:http://localhost:7001/
Spring Cloud笔记 初级篇_第40张图片
访问7002:http://localhost:7002/
Spring Cloud笔记 初级篇_第41张图片
到这里集群搭建完毕,就可以把服务注册进去了

服务注册进入集群

注册主要是改yml文件
注册8001服务
Spring Cloud笔记 初级篇_第42张图片
注册80服务
Spring Cloud笔记 初级篇_第43张图片
示意图
Spring Cloud笔记 初级篇_第44张图片
启动所有服务
Spring Cloud笔记 初级篇_第45张图片
测试7001
Spring Cloud笔记 初级篇_第46张图片测试7002
Spring Cloud笔记 初级篇_第47张图片
测试端口成功
Spring Cloud笔记 初级篇_第48张图片

集群配置

将8001的服务复制一份出来变成8002.也是client端
注意看yml
Spring Cloud笔记 初级篇_第49张图片
二者之间的区别也就是在两个端口号不同,具体走哪个服务就看端口号
很有可能一个名字下有n多台机器来执行这个服务
这个时候可以在业务代码里进行修改
修改8001的业务代码。在controller层加入端口号的读取
8002也是一样的改法,来区分出端口号
Spring Cloud笔记 初级篇_第50张图片
启动服务测试
8001测试
Spring Cloud笔记 初级篇_第51张图片
8002测试
Spring Cloud笔记 初级篇_第52张图片
访问Eureka 注册中心,这里只看一个7001的注册中心,7002不过多赘述
Spring Cloud笔记 初级篇_第53张图片
之前订单服务里的内容
以后只认服务名称,不认端口号是多少,因为单机下端口号写死了可能没有问题,但是一组集群了就有可能出现端口号冲突的问题,所以以后只认服务名称不认端口号
Spring Cloud笔记 初级篇_第54张图片
更正后按照Eureka的微服务名称走,但是这样是不够的,因为我只有一个微服务的名字是没有办法准确定位到某个服务器上的
Spring Cloud笔记 初级篇_第55张图片
直接访问会报错

那么怎么去配置自动定位到某个端口上呢?答案是负载均衡,自动把找到这个微服务的人引导到负载低的服务器上
这个任务就交给RestTemplate来做,加一个负载均衡的注解就可以了
在RestTemplate的配置类里加入注解@LoadBalance 赋予RestTemplate负载均衡的能力
这样就不需要再去关心端口号了,自动帮你负载均衡
在这里插入图片描述

actuator信息完善

上面说到,到了真正部署的阶段可能会有n多台机器,那么这个时候单靠主机名称去定位哪个机子上出问题了是不现实的,这个时候有个ip地址显示一下就很舒服,到时候排错直接定位到哪个ip哪个端口哪个微服务速度很快
修改方法在yml的Eureka下加入一个

服务名称自定义

  instance:
    instance-id: payment8001 #名字自己定

加完之后的样子
Spring Cloud笔记 初级篇_第56张图片
再次访问Eureka
在这里插入图片描述

ip地址显示

instance:
    instance-id: payment8001 #自定义的名称
    prefer-ip-address: true #启动ip显示

启动ip显示
Spring Cloud笔记 初级篇_第57张图片
访问测试
Spring Cloud笔记 初级篇_第58张图片

服务发现

加入了新的注解@EnableDiscoveryClient
类似于“关于我们”的功能,介绍注册到我Eureka的服务。通过服务发现来获得服务的信息。
相当于写一个接口
Spring Cloud笔记 初级篇_第59张图片
注意这里注入的时候不要导错包了,导错包就麻烦了
import org.springframework.cloud.client.discovery.DiscoveryClient;
Spring Cloud笔记 初级篇_第60张图片
controller层代码,用于输出ip,端口,服务名称等信息

@GetMapping(value = "/discovery")
    public Object discovery(){
        List<String> services = discoveryClient.getServices();
        for (String element : services) {
            System.out.println(element);
        }
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        for (ServiceInstance element : instances) {
            System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t"+ element.getUri());
        }
        return this.discoveryClient;
    }

最后的最后在主启动类加入注解@EnableDiscoveryClient这个注解以后会常用
Spring Cloud笔记 初级篇_第61张图片
启动测试访问8001端口下的服务发现接口,此时Eureka等微服务应该已经启动
Spring Cloud笔记 初级篇_第62张图片

Eureka自我保护机制

基础知识

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

如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT.
RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE (网页上那一大串红字)
在这里插入图片描述

一句话概括:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存
打个比方,公司欠科技园物业费了,科技园不会立马把他清走,会给他缓两天。交上来继续,长时间交不上来才走人

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

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

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着

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

禁止自我保护机制

自我保护一关闭就代表了只要心跳包一停,马上就删掉服务。
相当于一欠物业费了马上就把公司清出去
关闭步骤
yml的Eureka7001服务端配置
注意,这个是在Eureka服务端设置的,不是在Client端设置的

  server:
    #关闭自我保护机制,保证不可用服务被及时踢除
    enable-self-preservation: false
    #心跳包间隔时长:2000ms
    eviction-interval-timer-in-ms: 2000

更改之后
Spring Cloud笔记 初级篇_第64张图片
进行访问测试可以发现保护模式已经关闭
Spring Cloud笔记 初级篇_第65张图片
yml的Eureka8001客户端配置

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      defaultZone: http://localhost:7001/eureka 这是之前的单机版
      #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka  # 集群版
  instance:
    instance-id: payment8001 #自定义的名称
    prefer-ip-address: true #启动ip显示
    #心跳检测与续约时间
    #开发时设置小些,保证服务关闭后注册中心能即使剔除服务
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

Spring Cloud笔记 初级篇_第66张图片
启动测试,这里只启动7001和8001两个服务
访问7001
Spring Cloud笔记 初级篇_第67张图片
此时停掉8001服务,模拟8001宕机状态
重新访问7001端口可以看到服务已经清掉了
Spring Cloud笔记 初级篇_第68张图片

服务注册与发现—Zookeeper

由于Eureka已经停更了,一部分注册的服务被Zookeeper代替
zookeeper是一个分布式协调工具,可以实现注册中心功能,代替之前的Eureka作为新的注册中心
Spring Cloud笔记 初级篇_第69张图片

安装Zookeeper

普通版

安装教程地址
配置文件里的Zookeeper端口号为2181,具体要根据实际配置的来

docker版

docker配置要自行搜索,这里只提供命令,docker真香,一拉就好用,香香了家人们

#拉取Zookeeper镜像
docker pull zookeeper

#启动Zookeeper
docker run --name zk01 -p 2181:2181 --restart always -d zookeeper

入驻Zookeeper演示

支付模块入驻

新建工程8004
在这里插入图片描述
pom文件中新引入的Zookeeper依赖

	<!-- SpringBoot整合zookeeper客户端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
        </dependency>

配置yml文件,注意端口号要匹配好
Spring Cloud笔记 初级篇_第70张图片
主启动类
一定要加服务暴露接口的注解
Spring Cloud笔记 初级篇_第71张图片
Controller层

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

    @RequestMapping(value = "/payment/zk")
    public String paymentzk()
    {
        return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
    }
}

启动测试,访问成功
Spring Cloud笔记 初级篇_第72张图片

思考

zookeeper也是有心跳机制的
服务节点是临时节点还是持久节点?
答案:临时性的,每次重启同一个服务后节点的UUID编号都不一样。所以是临时的。

订单模块入驻

pom,yml内容基本一致
controller层,注意,这里使用了RestTemplate
记得加入配置类
Spring Cloud笔记 初级篇_第73张图片
Spring Cloud笔记 初级篇_第74张图片
主启动类
Spring Cloud笔记 初级篇_第75张图片
启动服务并测试
在这里插入图片描述
查看Zookeeper中注册的服务(没有web端),可以看到两个服务都入驻了
在这里插入图片描述
由于我们Zookeeper是Docker拉来的,不好找文件夹,这里提供一个Big佬的方法
Docker版本查看Zookeeper的入驻服务方法

服务注册与发现—Consul

什么是Consul?

Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发。

提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。

它具有很多优点。包括: 基于 raft 协议,比较简洁; 支持健康检查, 同时支持 HTTP 和 DNS 协议 支持跨数据中心的 WAN 集群 提供图形界面 跨平台,支持 Linux、Mac、Windows

安装与起步

Spring Cloud笔记 初级篇_第76张图片
访问http://localhost:8500/结果页面
Spring Cloud笔记 初级篇_第77张图片

注册入驻Consul演示

和之前的内容差不多,先上新依赖,这个是在pom文件中通用的

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

provider模块8006入驻

yml文件配置

###consul服务端口号
server:
  port: 8006

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

controller

@RestController
public class PaymentController {

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

    @GetMapping("/payment/consul")
    public String paymentInfo(){
        return "springcloud with consul: "+serverPort+"\t\t"+ UUID.randomUUID().toString();
    }
}

主启动类不写了,和上面一样,别问,问就是懒(乐)
启动测试,访问http://localhost:8500
Spring Cloud笔记 初级篇_第78张图片
测试controller接口
Spring Cloud笔记 初级篇_第79张图片

provider模块8006入驻

pom和上面的一样
yml文件

###consul服务端口号
server:
  port: 80

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

主启动类这里省略
Controller层和配置类

@RestController
public class OrderConsulController
{
    public static final String INVOKE_URL = "http://cloud-provider-payment"; //consul-provider-payment

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/payment/consul")
    public String paymentInfo()
    {//这里用了
        String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul", String.class);
        System.out.println("消费者调用支付服务(consule)--->result:" + result);
        return result;
    }
}
@Configuration
public class ApplicationContextBean
{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate()
    {
        return new RestTemplate();
    }
}

启动测试,访问http://localhost:8500
Spring Cloud笔记 初级篇_第80张图片
Spring Cloud笔记 初级篇_第81张图片

三种服务的异同点(CAP理论)

最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:
CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
经典图片,CAP三者不可兼得,只能兼顾两者
Spring Cloud笔记 初级篇_第82张图片
Spring Cloud笔记 初级篇_第83张图片

AP架构(Eureka为主)

AP架构,保证高可用性、容错性
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
也就是不太考虑C,数据差不多就可以了类似点赞数这种,能用就行,数据精确度不高
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
在这里插入图片描述

CP架构(Zookeeper和Consul为主)

CP架构,保证高一致性、容错性
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性
保证数据完全一致,安全,转账这种用的最多,有一个不对就报错
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP
Spring Cloud笔记 初级篇_第84张图片
Spring Cloud笔记 初级篇_第85张图片

服务调用—Ribbon

基础简介

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

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

说白了就是一个管宏观的一个管微观的。以牙疼要去看医生为例,Nginx就是决定把你导向哪个医院,Ribbon就是把你导向医院内部的科室内让哪个医生来为你服务。

一句话概括:Ribbon就是负载均衡+RestTemplate调用

Ribbon负载均衡演示

架构图Spring Cloud笔记 初级篇_第86张图片

Ribbon在工作时分成两步
第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

先看依赖这是原来的老版本依赖

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

写样例时候没有引入spring-cloud-starter-ribbon也可以使用ribbon,因为Eureka-client依赖自带了ribbon
Spring Cloud笔记 初级篇_第87张图片

RestTemplate拓展

之前的getForObject方法不够详细,操作对象返回json串比较单一,没有拓展,返回类似请求头等等参数都没有
返回对象为响应体中数据转化成的对象,基本上可以理解为Json
在这里插入图片描述

getForEntity方法,返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。引入了一些新的方法,对于返回的状态码可以进行判断
Spring Cloud笔记 初级篇_第88张图片
Spring Cloud笔记 初级篇_第89张图片
启动测试一样可以查询到内容。

Ribbon自带的负载均衡规则

负载均衡方法关系图
Spring Cloud笔记 初级篇_第90张图片
IRule:根据特定算法中从服务列表中选取一个要访问的服务
IRule自带方法有哪些
Spring Cloud笔记 初级篇_第91张图片

如何替换负载均衡规则

注意!官方文档明确给出了警告:
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了
也就是说,@ComponentScan能扫描到的地方,都不能放自定义配置类
@ComponentScan在@SpringApplication注解内,点进去就能看到,所以主启动类在的包就不能放了
Spring Cloud笔记 初级篇_第92张图片

改造现有的包名
Spring Cloud笔记 初级篇_第93张图片
改造后,新建规则配置类
Spring Cloud笔记 初级篇_第94张图片
配置类,将出厂的轮询访问微服务改成了随机访问微服务端口
Spring Cloud笔记 初级篇_第95张图片
最后在主启动类上加入@RibbonClient注解
Spring Cloud笔记 初级篇_第96张图片
访问测试,已经改写为随机访问,访问目标一共有8001,8002两个接口,访问时随机接入一个端口
这里我出Bug了,没时间搞,详细解决办法

手撕轮询算法

原理

算法公式:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标
Spring Cloud笔记 初级篇_第97张图片

手写负载算法(CAS+自旋锁的复习)

8001和8002进行改造,两个Controller里加入同一个方法

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

因为之前自己用RestTemplate的方法里有@LoadBalance注解,含有负载均衡的功能
这个时候我要用自己的负载均衡就得把他去掉
面向接口编程
在这里插入图片描述

public interface LoadBalance {
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

接口实现类
Spring Cloud笔记 初级篇_第98张图片
具体实现

@Component
public class MyLb implements LoadBalance {
    /**
     * AtomicInteger原子操作类,避免高并发时不安全
     * 对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。
     * num++解析为num=num+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。
     * 本次是累计访问次数
     */
    private AtomicInteger atomicInteger = new AtomicInteger();

    public final int getAndIncrement(){
        int current;
        int next;
        do {
            //拿到当前的次数
            current = this.atomicInteger.get();
            //如果超了Integer的上限就清零,没超就把当前访问数+1
            next = current >= Integer.MAX_VALUE ? 0 : current + 1;
            //compareAndSet就是CAS自旋锁,比较并交换值。达到期望值就更新,没达到保持原样
        } while (!this.atomicInteger.compareAndSet(current, next));
        System.out.println("第几次访问,次数next: "+next);
        return next;
    }

    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        //rest接口第几次请求数 求余 服务器集群总数量
        int index = getAndIncrement() % serviceInstances.size();
        //按照接口游标位置来取出要用的实例
        return serviceInstances.get(index);
    }
}

controller层应用负载均衡(这里是80订单的controller,使用restTemplate进行服务跳转)

    /**
     *载入可发现的服务
     */
    @Resource
    private DiscoveryClient discoveryClient;
    /**
     * 载入自定义的负载均衡规则
     */
    @Resource
    private MyLb myLb;
    
    @GetMapping("/consumer/payment/lb")
    public String getPaymentLB()
    {
        //从已经注册的服务实例中,拿到服务列表
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
        //服务实例不满足条件的,直接返回null
        if(instances == null || instances.size()<=0) {
            return null;
        }
        //以自定义负载均衡的方式启动服务,自动导向微服务
        ServiceInstance serviceInstance = myLb.instances(instances);
        //拿到uri前缀
        URI uri = serviceInstance.getUri();
        //跳转访问
        return restTemplate.getForObject(uri+"/payment/lb",String.class);
    }

服务测试,访问上面的controllerhttp://localhost/consumer/payment/lb
由于这个接口最终导向出来的是返回接口号,再加上是轮询算法,所以两个接口交替
Spring Cloud笔记 初级篇_第99张图片
Spring Cloud笔记 初级篇_第100张图片
Spring Cloud笔记 初级篇_第101张图片
控制台输出
Spring Cloud笔记 初级篇_第102张图片

服务调用—OpenFeign

什么是OpenFeign

OpenFeign 全称 Spring Cloud OpenFeign,它是 Spring 官方推出的一种声明式服务调用与负载均衡组件,它的出现就是为了替代进入停更维护状态的 Feign。
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。
Feign可以与Eureka和Ribbon组合使用以支持负载均衡
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加一个与Feign相关的注解即可(也可以叫做服务接口绑定器+注解)
Spring Cloud笔记 初级篇_第103张图片

OpenFeign起步

引入依赖

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

主启动类引入新的注解@EnableFeignClients

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

Service接口(OpenFeign)
加入新注解@FeignClient
Spring Cloud笔记 初级篇_第104张图片
controller层
Spring Cloud笔记 初级篇_第105张图片
测试结果,访问同一个地址可以看到端口号不同,来回切换(也就实现了负载均衡)
Spring Cloud笔记 初级篇_第106张图片
Spring Cloud笔记 初级篇_第107张图片

小总结

红色框为微服务名称,蓝色为对应的API路径
Spring Cloud笔记 初级篇_第108张图片

OpenFeign超时控制

模拟超时场景

在8001上添加一个超时接口

	/**
     * 模拟Feign超时接口
     */
    @GetMapping(value = "/feign/timeout")
    public String paymentFeignTimeOut(){
        //延时3s
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("*****paymentFeignTimeOut from port: "+serverPort);
        return serverPort;
    }

回到有OpenFeign服务的80端
service接口
Spring Cloud笔记 初级篇_第109张图片
controller接口
Spring Cloud笔记 初级篇_第110张图片
启动测试http://localhost/consumer/payment/feign/timeout
因为OpenFeign的等待时间默认为1s,刚刚在样例中延时为3s,超时了自然报错
报错页面
Spring Cloud笔记 初级篇_第111张图片
默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

更改超时限制配置

yml文件中开启配置
Spring Cloud笔记 初级篇_第112张图片
重启服务再进行测试,8001访问正常
Spring Cloud笔记 初级篇_第113张图片

OpenFeign日志打印功能

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

日志级别

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

加入配置

Spring Cloud笔记 初级篇_第114张图片

修改yml文件

Spring Cloud笔记 初级篇_第115张图片

重启服务测试

随便访问一个接口可以看到,控制台输出了非常详细的信息包括请求头等等参数,非常方便
Spring Cloud笔记 初级篇_第116张图片

你可能感兴趣的:(笔记,SpringCLoud,spring,cloud,spring,boot,分布式)