Spring Cloud

演进

spring javaEE开发标准

spring几乎连接着我们开发中的一切,他就像一个中央集成一样

但是慢慢的我们发现,他真的轻量吗?

  • 随着开发量不断增多的配合内容

让他变得不再轻量,于是乎新的解决方案诞生了

javaEE新标准

springboot javaEE开发新的标准

他简化了繁琐的配置,自动化的帮助我们干了很多的配置上需要重复干的事情,

给了我们一套默认的解决方案,我们可以把boot理解成一个 spring的plus版,他集成了很多的启动器,从而让springboot逐渐取代了 ssm,springboot慢慢的变成了javaEE最好的解决方案,让无数企业喜爱

不管是新标准还是旧标准,他的特点是统一的:约定大于配置

最开始

我们现在开发的程序 all in one 的形式,所有的模块全在一个jar或者war包下

演进

那么随着架构的演进我们逐渐把功能拆分出来,代码并没有什么变化

但是一旦并发量高起来,这样的架构,我们的机器无法让业务正常实行了

现在

解决方法应运而生,微服务架构

把功能拆分多个设备上去,来解决性能上导致业务无法正常运行的问题

微服务四大核心问题:

  1. 服务很多,用户如何访问,注册中心
  2. 服务中如何通信 : rpc
  3. 这么多服务,服务治理?
  4. 服务挂了怎么办?

对于这些问题,spring cloud是个生态

img

用来解决这四个问题的

  1. Spring cloud netfix 一站式解决方案

    api网关,zuul组件

    Feign —HttpClinet ——Http通信方式

    服务注册发现:Eureka

    熔断机制: Hystrix

    。。。。

  2. Apache Dubbo zookeeper 半自动,需要整合别人的

    API 没有,找第三方组件,或者自己实现

    Dubbo 通信,高性能 rpc框架

    服务注册发现:zookeeper

    熔断机制并没有:借助 Hystrix

  3. Spring Cloud Alibaba 最新的一站式解决方案!

新概念:服务网格!server mesh

概念都是一样的:

  1. API
  2. http rpc
  3. 注册与发现
  4. 熔断机制

为什么会出现这个情况,因为:网络不可靠!

常见面试题

1.1 什么是微服务?

1.2 微服务之间是如何独立通讯的?

1.3 SpringCloud 和 Dubbo有那些区别?

1.4 SpringBoot 和 SpringCloud,请谈谈你对他们的理解

1.5 什么是服务熔断?什么是服务降级?

1.6 微服务的优缺点分别是什么?说下你在项目开发中遇到的坑

1.7 你所知道的微服务技术栈有哪些?列举一二

1.8 Eureka和Zookeeper都可以提供服务注册与发现的功能,请说说两者的区别

微服务

  • 就目前而言微服务,业界没有统一标准的定义
  • 微服务架构是一种架构模式,或者是一种架构风格他提倡单一应用程序划分一组小服务,每个服务运行在自己独立的进程内,服务之间,互相协调,互相配置,为用户提倡最终的价值,体现最终价值,服务之间采用轻量级的通信机制互相沟通,每个服务围绕具体的业务构建,并且能够被独立的部署在生产环境中,另外,尽量避免统一的,集中式管理的服务管理机制,对具体的一个服务而言,根据上下文,选择合适的语言,工具,对齐构建,可以有一个非常轻量的集中式管理来协调业务,可以使用不同的语言编写,也可以用不同的数据储存

我们从技术维度理解一下

就是微服务的作用就是将传统的一个项目解决业务(一站式应用)根据业务拆分成一个一个的服务,去彻底的去耦合,每个微服务提供单个业务的功能服务,一个服务做一个事情,从技术角度来说就是一个小而独立的处理过程,类的进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库

微服务与微服务架构

微服务

强调的是服务的大小 ,他关注的是一个点,是具体解决某一个问题提供落地对服务的应用,就是idea中一个个微服务的工程或者moudel

idea工具里面使用maven建立的一个个独立的小moudle,他具体是使用springboot开发的一个个小模块,专业的事情交给专业的模版来做,一个模块做着一件事情

强调的是一个个的个体,每个个体完成一个具体的任务或者功能!

微服务架构

一钟新的架构形式,Martin Fowler

2014推出

  • 微服务架构是一种架构模式,或者是一种架构风格他提倡单一应用程序划分一组小服务,每个服务运行在自己独立的进程内,服务之间,互相协调,互相配置,为用户提倡最终的价值,体现最终价值,服务之间采用轻量级的通信机制互相沟通,每个服务围绕具体的业务构建,并且能够被独立的部署在生产环境中,另外,尽量避免统一的,集中式管理的服务管理机制,对具体的一个服务而言,根据上下文,选择合适的语言,工具,对齐构建,可以有一个非常轻量的集中式管理来协调业务,可以使用不同的语言编写,也可以用不同的数据储存

微服务的有缺点

优点

  • 单一职责原则
  • 每个服务足够内聚,足够小,代码容易理解,这个能聚焦一个指定的业务功能和业务需求
  • 开发简单,开发效率提高,一个服务可能就是转义的只干一件事情
  • 微服务能够被小团队单独开发,这个小团队是2~5的开发人员组成
  • 微服务是松耦合的,是具有功能意义的服务,无论是在开发阶段或者部署阶段都是独立的
  • 微服务能使用不同的预言开发
  • 易于是第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如jenkins,hudson,bamboo
  • 微服务易于被一个开发人员理解,修改和维护,这样小团队能够更加关注自己的工作成果。无需通过合作才能体现价值
  • 微服务允许你利用融合最新技术
  • 微服务只是业务逻辑的代码,不会喝html,css或其他的界面混合
  • 每个微服务都有自己的储存能力,可以有自己的数据库,也可以有统一数据库

缺点:

  • 开发人员要处理分布式系统的复杂性
  • 多服务运维难度,随着服务的增加,运维压力也在增大
  • 系统部署依赖
  • 服务间通信成本
  • 数据一致性
  • 系统集成测试
  • 性能监控

微服务技术栈

微服务技术条目 落地技术
服务开发 SpringBoot,Spring,SpringMVC
服务配置与管理 netflix公司的archaius和阿里的diamond等
服务注册与发现 eureka,consul,zookeeper
服务调用 rest,rpc,grpc
服务熔断器 Hystrix,Envoy等
负载均衡 RIbbon,nginx等
服务接口调用(服务端调用服务的简化工具) Feign等
消息队列 kafka,rabbitMQ,ActiveMQ
服务配置中心管理 SpringCloudconfig,chef等
服务路由 Zuul,Route
服务监控 zabbix,Nagios,M ertrics,Specatator
全链路追踪 Zipkin,Brave,Dapper
服务部署 DOCKER,openStack,kubernetes
数据操作开发包 Springcloud Stream(封装与rides,rabbit,kafka等发送接收消息)
事件消息总线 springcloud Bus

为什么我们要选择SpringCloud作为微服务架构呢

1、选型依据

  • 整体解决方案和框架成熟度
  • 社区热度
  • 可维护性
  • 学习曲线

2、当前各大公司微服务架构是那些

  • 阿里:dubbo+hfs
  • 京东:jsf
  • 新浪:Motan
  • 当当: bubbox
功能和服务框架 Netflix/springCloud Motan grpc thrift Dubbo/dubbox
功能定位 完整的微服务框架 rpc框架但是整合了zk或者consul可以实现集群环境和基本的服务注册发现 rpc框架 rpc框架 服务框架
支持rest 支持,ribbon支持多种可插拔序列化选择
支持rpc 否(但是可以和dubbo兼容)
支持多语言 支持(rest形式)
负载均衡 支持(服务端zuul+客户端ribbon),zuul服务,动态路由,云端负载均衡,eureka针对中间层服务器 支持(客户端) 是(客户端)
配置服务 netfix archaius spring cloud config Server集中配置 是(zookeeper提供)
服务调用链监 支持,zuul,zuul提供边缘服务,api网关
高可用/容错 支持,服务端hystrix+ribbon 支持(客户端) 支持(客户端)
典型应用案例 Netflix sina google facebook
社区活跃度 一般 一般 2017才开始重新维护,之前中断了五年
学习难度
文档丰富程度 一般 一般 一般
其他 SpringCloud bus为我们的应用带来更多的管理端点 支持降级 netflix内部在开发集成grpc idl定义 实践的公司比较多

#springcloud入门概述

springcloud是什么?

img

img

img

springcloud基于springboot提供了一套微服务解决方案,包括服务注册,发现,配置中心,全链路监控

服务网管,负载均衡,熔断器等组件,除了基于netflix的开源组件做高度抽象封装之外,还有一些选型中立得1开源组件。

springcloud利用springboot的开发便利性,巧妙的简化了分布式系统基础设施额开发,springcloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,他们都可以用springboot的开发风格做到一键启动部署

springboot并没有重复造轮子,他只是将目前各家公司开发的比较成熟经得起实际考研的 服务框架组合起来,通过springboot风格进行封装,屏蔽掉了复杂的配置,和实现原理,最终给开发者留出一套简单易懂,易部署,和易维护的分布式系统开发工具包

springcloud是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶

springboot和springcloud的关系

  • springboot专注于快速方便的开发单个个体微服务
  • springcloud是关注全局微服务协调治框架,他将springboot开发的一个个单体微服务,整合并管理起来,为各个微服务之间提供了配置管理,服务发现,断路器,路由,事件总线全局锁,决策竞选,分布式会话等等集成服务
  • springboot可以离开springcloud独立使用,开发项目,但是springcloud离不开springboot,属于依赖关系
  • springboot专注于快速方便的开发单个个体微服务,springcloud关注全局的服务治理框架

Dubbo和springcloud技术选型

分布式+服务治理Dubbo

目前成熟的互联网架构:应用服务化拆分+消息中间件

img

Dubbo和springcloud对比

dubbo应为停更了之后,社区并不活跃,垂死状态,未来未知

springcloud的社区十分活跃,正值壮年,

对比图:

img

最大区别:springcloud抛弃了Dubbo的rpc通信,采用基于http的rest方式

严格来说,两种方式各有优劣,从一定程度上,后者牺牲了服务调用的性能,但是也比避免了原生rpc带来的问题,rest相比rpc更加的灵活,服务提供方和调用方的依赖只靠一个七月,不存在在吗级别的强依赖,这就是强调快速烟花的微服务环境下,显得更加合适

品牌机和组装机的区别

springcloud(品牌机):

很明显的一点就是,springcloud的功能比dubbo强大的太多,覆盖面更广,而且作为spring的明星项目,他也能够和其他的spring项目完美融合。
dubbo(组装机):

使用dubbo构建微服务架构就像组装电脑,各环节,我们的选择自由度非常高,但是最终结果可能是就是一个内存条不点亮了,总是不让人那么放心,但是如果是一个高手,那这些都不是问题,

springcloud就像品牌机,在spring source的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础足够了解,

社区支持和更新力度

最重要的是,dubbo停止了5年狗熊,虽然17年重启了,对于技术发展的需求,更需要爱发展着自行拓展升级,比如dubbox,对于这很多想要采纳微服务的中小软件组织,显然是不太合适的,中小公司没有那么强大的技术去修改dubbo的源码+周边的一整套解决方案。并不是每个公司都有阿里的大牛+真实线上生产环境测试过

总结

曾风靡国内的rpc框架Dubbo在重启维护后,让很多用户为之雀跃,但是同时也要有质疑的声音,发展迅速的时代,dubbo能否跟上?dubbo和springcloud的差异 ?是否会有相关的举措保证dubbo的后续更新频率

dubbo是一个专注rpc框架,springcloud的目标是微服务架构下的一站式解决方案

设计模式+微服务拆分思想:不一定善于表达的技术人才,你可以领导他,软实力是职场关键的一点,你可能技术没有人才好,但是你的设计思维,架构理解和表达能力让你可以成为只会技术人才的团队leader,

springcloud下载

springcloud的不同版本

img

以伦敦地铁站和字符开头来命名

接下来是需要知道的几个文档

springcloud的中文文档:https://www.springcloud.cc/spring-cloud-dalston.html

社区官网:http://docs.springcloud.cn/

以上的理论内容是和代码挂钩的,很多面试中淡资也是很重要的东西

springcloud的大版本说明

springboot springcloud 关系
1.2.x 天使版angel 兼容boot1.2.x
1.3.x brixton版本 兼容spring1.3,也兼容1.4
1.4.x camden版本 兼容spring1.4,也兼容1.5
1.5.x dalston版本 兼容spring1.5,不兼容2.0.x
1.5.x edgware 兼容spring1.5,不兼容2.0
2.0.x finchley 兼容spring2.0,不兼容1.5
2.1.x greenwich

项目 spring cloud

首先是父项目的打包方式:pom

<!--    打包方式的改变 pom-->        <packaging>pom</packaging>

之后父项目中导入需要的全部依赖

如下:

springcloud使用版本

  • springboot 2.3.12.RELEASE
  • spring cloud Hoxton.SR12

Eureka

贯穿学习思路的规律:

  1. 导入依赖
  2. 编写配置
  3. 注解开启功能
  4. 配置类

简介

注册发现中心

Eureka 来源于古希腊词汇,意为“发现了”。在软件领域, Eureka 是 Netflix 在线影片公司开源的一个服务注册与发现的组件,和其他 Netflix 公司的服务组件(例如负载均衡、熔断器、网关等) 一起,被 Spring Cloud 社区整合为 Spring Cloud Netflix 模块。

Eureka 是 Netflix 贡献给 Spring Cloud 的一个框架!Netflix 给 Spring Cloud 贡献了很多框架,后面我们会学习到!

2.Spring Cloud Eureka Zookeeper 的区别

2.1 什么是 CAP 原则(面试)

在分布式 微服务里面 CAP 定理

问:为什么 zookeeper 不适合做注册中心?

CAP 原则又称 CAP 定理,指的是在一个分布式系统中,

一致性(Consistency)

可用性(Availability)

分区容错性(Partition tolerance)(这个特性是不可避免的)

xxxxxxxxxx22 12    3        4    567    <tx:advice id="txAdvice" transaction-manager="transactionManager">8910        tx:attributes11            <tx:method name="add" propagation="REQUIRED"/>12            <tx:method name="delect" propagation="REQUIRED"/>13            <tx:method name="update" propagation="REQUIRED"/>14        </tx:attributes>15    </tx:advice>16​1718    aop:config19        <aop:pointcut id="txPoinCut" expression="execution(* com.lei.mapper..(..))"/>20        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoinCut"/>21    </aop:config>22xml

2.2 分布式特征

C : 数据的一致性 (A,B,C 里面的数据是一致的)

Zk 注重数据的一致性。

Eureka 不是很注重数据的一致性!

A: 服务的可用性(若 zk 集群里面的 master 挂了怎么办)Paxos(多数派)

在 zk 里面,若主机挂了,则 zk 集群整体不对外提供服务了,需要选一个新的出来(120s左右)才能继续对外提供服务!

Eureka 注重服务的可用性,当 Eureka 集群只有一台活着,它就能对外提供服务

P****:分区的容错性(在集群里面的机器,因为网络原因,机房的原因,可能导致数据不会里面

同步),它在分布式必须需要实现的特性!

Zookeeper 注重数据的一致性,CP zk(注册中心,配置文件中心,协调中心****)

Eureka 注重服务的可用性 AP

eureka (注册中心)

3.Spring Cloud 其他注册中心

Spring Cloud 还有别的注册中心 Consul ,阿里巴巴提供 Nacos 都能作为注册中心,我们

的选择还是很多。

3.1 Consul

https://spring.io/projects/spring-cloud-consulConsulwww

3.2 Nacos

https://nacos.io/zh-cn/

但是我们学习还是选择 Eureka ,因为它的成熟度很高。面试时候问的也是它,不是别人!

eureka nacos

4.Spring Cloud Eu

netfilx在设计eureka遵循的是ap原则,

他是netflix的一个子模块,也是核心模块之一,负责定位服务实现云端中间层服务发现和故障转移,服务注册与发现对于微服务;来说非常重要,功能与zookeeper等注册中心相似

img

原理理解

  • Eureka

    • springcloud封装了netfix公司开发的eureka模块来实现服务注册与发现

    • 采用了C-S的架构设计,EurakaServer作为服务注册中心

    • 系统中的其他为服务,使用客户端连接EurakaServer并且维持心跳连接,spring中的其他模块,比如Zuul可以通过EurekaServer来发现系统中的其他微服务

    • 和Dubbo架构对比

      img

img

  • Eureka包含两个组件:EurakaServer和Eureka Clinet

  • EurakaServer负责注册服务,各个节点启动后,注册,服务注册表中会有存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到

  • Eureka Clinet是java客户端,用于简化Eureka Server的操作,客户端同时内置使轮询负载算法的负载均衡器,在启动后,会三十秒,向服务器发送心跳,如果服务器有多个心跳周期内没有接受到某个节点的心跳,那么服务器就会在注册表中移除节点,默认周期为90秒

  • 三个角色

    • Eureka Server :提供服务的注册于发现
    • Service Provider:将自身注册到服务中,使得消费者可以调用
    • Service consunmer:服务消费方从Eureka中获取服注册服务列表,从而找到消费服务

使用版本

  • springboot 2.3.12.RELEASE
  • spring cloud Hoxton.SR12

创建一个三级目录

springcloud

01-eureka

​ 01-eureka-server

服务端 01-eureka-server

在 01-eureka中创建一个springboot项目01-eureka-server

创建时只需要勾选一个Eureka-server

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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lei</groupId>
    <artifactId>01-eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>01-eureka-server</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <!--   依赖管理 只是管理版本号以及子模块的依赖-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
    </pluginRepositories>

</project>

配置

server:
  port: 8761 #eureka的默认端口
spring:
  application:
    name: eureka-server  # 应用名称 不要使用特殊字符

#eureka的配置分围三类 server client 实例的   
#eureka-server 只需要配置server和实例  eureka-server 即是服务端也是客户端 
eureka:
  server:
    eviction-interval-timer-in-ms: 10000  #服务端间隔多少毫秒做定期删除的操作
    renewal-percent-threshold: 0.85  #续约百分比  超过85%的应用没有和你续约,那么eureka会保护服务 ,不要剔除任何一个服务
  instance:  #实例配置
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #主机名称 :应用名称 :端口号
    hostname: localhost #主机名称或ip
    prefer-ip-address: true #以ip的形式显示具体服务信息
    lease-renewal-interval-in-seconds: 5 # 服务实例的续约时间间隔
    lease-expiration-duration-in-seconds: 20 #表示 eureka server 至上一次收到 client 的心跳之后,等待下一次心跳的超时时
间,在这个时间内若没收到下一次心跳,则将移除该实例

开启注册功能

package com.lei;

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

@SpringBootApplication
@EnableEurekaServer  //开启eureka的注册功能
public class EurekaServerApplication {

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

}

客户端02-eureks-client-a

创建boot项目 勾选web项目和eureka-client

依赖

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

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

配置

server:
 port: 8080
spring:
 application:
  name: eureka-client
eureka:
 client:
  service-url: #eureka 服务端和客户端的交互地址,集群用,隔开
   defaultZone: http://localhost:8761/eureka
  register-with-eureka: true #注册自己
  fetch-registry: true #拉取服务列表
  registry-fetch-interval-seconds: 5 # 表示 eureka-client 间隔多久去拉取服务注册信息
 instance:
  hostname: localhost # 服务主机名称
  instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} # 实例 id
  prefer-ip-address: true # 服务列表以 ip 的形式展示
  lease-renewal-interval-in-seconds: 10 # 表示 eureka client 发送心跳给 server 端的频率
  lease-expiration-duration-in-seconds: 20 #表示 eureka server 至上一次收到 client 的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该实例

开启客户端

package com.lei;

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

@SpringBootApplication
//开启客户端功能
@EnableEurekaClient
public class EurekaClientApplication {

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

}

eureka集群

有主从模式和去中心化模式 两种模式

一般使用去中心化模式 eureka会将数据进行扩散和广播

搭建

服务端

创建三个服务端01-eureka-server 01-eureka-server-b 01-eureka-server-c

01-eureka-server

配置文件

集群
server:
  port: 8761 #eureka的默认端口
spring:
  application:
    name: eureka-server  # 应用名称 不要使用特殊字符

#eureka的配置氛围三类 server client 实例的   eureka-server即是服务端也是客户端
eureka:
  client:
    service-url: #你不写默认8761
      defaultZone: http://peer2:8762/eureka,http://peer3:8763/eureka
  instance:
    hostname: peer1
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #主机名称 :应用名称 :端口号
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5
01-eureka-server-b

配置文件

server:
  port: 8762 #eureka的默认端口
spring:
  application:
    name: eureka-server  # 应用名称 不要使用特殊字符

#eureka的配置氛围三类 server client 实例的   eureka-server即是服务端也是客户端
eureka:
  client:
    service-url: #你不写默认8761
      defaultZone: http://peer1:8761/eureka,http://peer3:8763/eureka
  instance:
    hostname: peer2
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #主机名称 :应用名称 :端口号
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5
01-eureka-server-3

配置文件

server:
  port: 8763 #eureka的默认端口
spring:
  application:
    name: eureka-server  # 应用名称 不要使用特殊字符

#eureka的配置氛围三类 server client 实例的   eureka-server即是服务端也是客户端
eureka:
  client:
    service-url: #你不写默认8761
      defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka
  instance:
    hostname: peer3
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port} #主机名称 :应用名称 :端口号
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5

客户端

server:
  port: 8080
spring:
  application:
    name: eureka-client-a

#注册 将自己的信息(什么信息)发过去(发到哪里)
#指定注册地址
eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka,http://peer2:8762/eureka,http://peer2:8763/eureka
    register-with-eureka: true #应用是否往eureka-server注册
    fetch-registry: true #应用是否是否拉去服务列表去本地
    registry-fetch-interval-seconds:  10  # 设置拉去服务列表的间隔时间  为了缓解服务列表的脏读问题 时间越短,脏读越小,性能消耗越大
  instance:
    hostname: localhost #应用主机名称 最好写ip
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
    prefer-ip-address: true #显示ip
    lease-renewal-interval-in-seconds: 10 #续约时间

终极方案

只需要见一个服务端项目

配置文件

#集群的终极方案
server:
  port: 8761 #eureka的默认端口
spring:
  application:
    name: eureka-server  # 应用名称 不要使用特殊字符

#eureka的配置氛围三类 server client 实例的   eureka-server即是服务端也是客户端
eureka:
  client:
    fetch-registry: true #是否拉取服务列表
    register-with-eureka: true #是否注册自己(集群需要注册自己和拉取服务)
    service-url: #你不写默认8761
      defaultZone: http://peer1:8761/eureka,
        http://peer2:8762/eureka,http://peer3:8763/eureka
  instance:
#    hostname: peer1
    instance-id: ${spring.application.name}:${server.port} #主机名称 :应用名称 :端口号
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5
    

然后在复制两个启动项只需要改掉端口号

![屏幕截图 2023-01-05 001423](D:\java笔记\spring\屏幕截图 2023-01-05 001423.png)

深入理解

如果是主从模式 主机如何选择?

如果是主从模式 数据怎么同步的?

服务发现(源头)

**** 什么是服务发现

根据服务名称发现服务的实例过程

客户端会在本地缓存服务端的列表

拉取列表是有间隔周期的 (导致服务上线 客户端不能第一时间感知到 (可以容忍))

其实每次做服务发现 都是从本地的列表来进行的

a 服务里面做服务发现

package com.bjpowernode.controller;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.client.discovery.DiscoveryClient;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;



@RestController

public class TestController {

*/***

*** *注入服务发现组件,我们的* *eureka* *已经实现了这个接口,所以* *IOC* *里面有这个对象*

**/*

@Autowired

private DiscoveryClient discoveryClient;

*/***

*** *服务发现*

** @param* *serviceId*


**/*

@GetMapping("find")

public String find(String serviceId) {

*//**调用服务发现*

List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);

instances.forEach(System.*out*::print);

return instances.toString();

}

注意:eureka 客户端会把服务列表缓存到本地 为了提高性能但是有脏读问题,当你启动一个新的应用的时候 不会被老的应用快速发现

Spring Cloud Ribbon

Ribbon 概述

Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。通过 Spring Cloud 的封装,可以让我们轻松地将面向服务的 REST 模版请求自动转换成客户端负载均衡的服务调用。 轮询 hash 权重 ...

简单的说 Ribbon 就是 netfix 公司的一个开源项目,主要功能是提供客户端负载均衡算法和服务调用。Ribbon 客户端组件提供了一套完善的配置项,比如连接超时,重试等。

在 Spring Cloud 构建的微服务系统中, Ribbon 作为服务消费者的负载均衡器,有两种使用方式,一种是和 RestTemplate相结合,另一种是和 OpenFeign 相结合。OpenFeign 已经默认集成了 Ribbon,关于 OpenFeign 的内容将会在后面。Ribbon 有很多子模块,但很多模块没有用于生产环境!

https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#spring-integration

RestTemplate

定义接口

package com.lei.controller;

import com.lei.domain.User;
import org.springframework.web.bind.annotation.*;

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class TestController {
    @GetMapping("testGet")
    public String get(String name){
        System.out.println(name);
        return "ok";
    }
//post 有两种传参  json  表单
    //接收json
    @PostMapping("testPost1")
    public String post1(@RequestBody User  user){
        System.out.println(user);
        return "ok";
    }
    //接收表单参数
    @PostMapping("testPost2")
    public String post2( User  user){
        System.out.println(user);
        return "ok";
    }
}

测试

package com.lei;

import com.lei.domain.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;

@SpringBootTest
class ApplicationTests {

   @Test
   void contextLoads() {
      //在java代码中发一个请求,请求一个页面
      RestTemplate restTemplate = new RestTemplate();
      //如果访问一个页面 会返回html代码
      String url ="https://www.baidu.com";
      String result = restTemplate.getForObject(url,String.class);
      System.out.println(result);
   }
   @Test
   void testGet(){
      RestTemplate restTemplate = new RestTemplate();
      String url = "http://localhost:8080/testGet?name=lei";
//    System.out.println(restTemplate.getForObject(url, String.class));
      ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
      System.out.println(responseEntity);

   }
   @Test
   void tesPost(){
      RestTemplate restTemplate = new RestTemplate();
      String url = "http://localhost:8080/testPost1";
      User user = new User("lei",20,10000D);
      //发送post 而且是json参数 因为web默认使用jackson,他会把你的对象转成json字符串
      String s = restTemplate.postForObject(url, user, String.class);
      System.out.println(s);

   }
   @Test
   void tesPost2(){
      RestTemplate restTemplate = new RestTemplate();
      String url = "http://localhost:8080/testPost2";
      LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
      map.add("name","l");
      map.add("age",26);
      map.add("price",80000D);
      String s = restTemplate.postForObject(url, map, String.class);
      System.out.println(s);

   }

}

负载均衡

负载均衡,英文名称为 Load Balance(LB)http:// lb://(负载均衡协议) ,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如 Web 服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。

负载均衡构建在原有网络结构之上,它提供了一种透明且廉价有效的方法扩展服务器和网络设备的带宽、加强网络数据处理能力、增加吞吐量、提高网络的可用性和灵活性。

服务器的负载均衡

Nginx,F5

Ribbon 快速入门

本次调用设计图

springcloud01

项目搭建

consumer provider-a **provider-b **都是 eureka-client

注意这三个依赖是 eureka-client

注意 provider-1 provider-2 spring.application.name=provider

注意启动类的注解和配置文件的端口以及服务名称

创建提供者provider-a和provider-b

依赖

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

配置文件

provider-a

server:
  port: 8080
spring:
  application:
    name: provider
eureka:
  client:
    service-url:
      defaulZone: http://peer1:8761/eureka
  instance:
    hostname: localhost
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

provider-b

server:
  port: 8081
spring:
  application:
    name: provider
eureka:
  client:
    service-url:
      defaulZone: http://peer1:8761/eureka
  instance:
    hostname: localhost
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

controller

ProviderController

@RestController
public class ProviderController {
    @GetMapping("hello")
    public String hello(){
        return "我是提供者aaa的接口";
    }
}
@RestController
public class ProviderController {
    @GetMapping("hello")
    public String hello(){
        return "我是提供者bbb的接口";
    }
}

创建consumer 消费者

依赖

<!--       添加ribbon依赖-->
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.cloud</groupId>
         <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>

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

配置yml

server:
  port: 8082
spring:
  application:
    name: consumer
eureka:
  client:
    service-url:
      defaulZone: http://peer1:8761/eureka
  instance:
    hostname: localhost
    prefer-ip-address: true
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

编写启动类

package com.lei;

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

@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {

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

   /**
    * 添加@LoadBalanced这个注解 他就会被ribbon操作
    * @return
    */
   @Bean
   @LoadBalanced
   public RestTemplate restTemplate(){
      return new RestTemplate();
   }

}

controller

package com.lei.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class ConsumerController {
    @Autowired
    private RestTemplate restTemplate;

    /**
     * 思考ribbon是怎么将 http://provider/hello 路径请求成功的
     * 1. 拦截该请求;
     * 2. 获取该请求的 URL 地址:http://provider/hello
     * 3. 截取 URL 地址中的 provider
     * 4. 从服务列表中找到 key 为 provider 的服务实例的集合(服务发现)
     * 5. 根据负载均衡算法选出一个符合的实例
     * 6. 拿到该实例的 host 和 port,重构原来 URL 中的 provider
     * 7. 真正的发送 restTemplate.getForObject(“http://ip:port/hello”,String.class)
     * @param serviceName
     * @return
     */
    @GetMapping("testRibbon")
    public String testRibbon(String serviceName){
        //http://provider/hello
        String forObject = restTemplate.getForObject("http://" + serviceName + "/hello", String.class);
        return forObject;

    }
}

ribbon一些配置

ribbon:
  eager-load:
    enabled: false #ribbon它只有·自己的话 能不能做服务发现靠eureka ribbon需要去eureka中获取服务列表 如果false就懒加载
  eureka:
    enabled: true
  http:  #我们使用ribbon 用的restTemplate发请求  很方便 但不支持连接池
    client:  #发送请求的工具很多 httpClient 它支持连接池 效率更好  如果想改这个请求工具,要加这个的依赖
      enabled: false
  okhttp: #这个也是请求工具 移动端应用比较多轻量级
    enabled: false

ribbon总结

Ribbon 客户端实现负载均衡的远程调用组件,用法简单

Ribbon 源码核心:

ILoadBalancer 接口:起到承上启下的作用

1. 承上:从 eureka 拉取服务列表

2. 启下:使用 IRule 算法实现客户端调用的负载均衡

设计思想:每一个服务提供者都有自己的 ILoadBalancer

userService---》客户端有自己的 ILoadBalancer

TeacherService---》客户端有自己的 ILoadBalancer

在客户端里面就是 Map<String,ILoadBalancer> iLoadBalancers

Map<String,ILoadBalancer> iLoadBalancers 消费者端

服务提供者的名称 value (服务列表 算法规则 )

如何实现负载均衡的呢?

iloadBalancer loadbalance = iloadBalancers.get(“user-service”)

List servers = Loadbalance.getReachableServers();//缓存起来

Server server = loadbalance .chooseServer(key) //key 是区 id,--》****IRule 算法

chooseServer 下面有一个 IRule 算法

IRule 下面有很多实现的负载均衡算法

Spring Cloud OpenFeign

我 们 使用Ribbon 做 了 负 载 均 衡 , 用 Eureka-Client 来 做 服 务 发 现 , 通 过

RestTemplate 来完成服务调用,但是这都不是我们的终极方案,终极方案是使用 OpenFeign

1 OpenFeign简介

Feign 是声明性(注解)Web 服务客户端。它使编写 Web 服务客户端更加容易。要使用 Feign,

请创建一个接口并对其进行注解。它具有可插入注解支持,包括 Feign 注解和 JAX-RS 注解。

Feign 还支持可插拔编码器和解码器。Spring Cloud 添加了对 Spring MVC 注解的支持,并

支持使用 HttpMessageConverters,Spring Web 中默认使用的注解。Spring Cloud 集成

了 Ribbon 和 Eureka 以及 Spring Cloud LoadBalancer,以在使用 Feign 时提供负载平衡

的 http 客户端。

Feign 是一个远程调用的组件 (接口,注解) http 调用的

Feign 集成了 ribbon ribbon 里面集成了 eureka

2 OpenFeign入门

2.1结构

springcloud02

springcloud03

2.2 启动一个 eureka-server 服务,这里不重复演示,参考 eureka

文档

2.3 先创建provider- order-service 提供者,

选择依赖


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

**** provider-order-service 修改配置文件

server:
  port: 8080
spring:
  application:
    name: order-service
eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka

provider- order-service修改启动类增加一个访问接口**

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class OrderController {

    @GetMapping("doOrder")
    public String doOrder(){
       return "油条奶茶" ;
    }
}

2.4 consumer-user-service,消费者

选择依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency

配置文件

server:
  port: 8081

spring:
  application:
    name: user-service
eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka
#feign只是帮你封装了远程调用功能 底层还是ribbon 所以需要修改ribbon的时间
ribbon:
  ReadTimeout: 3000 #给3秒超时时间
  ConnectTimeout: 3000 #链接服务超时时间

创建一个接口UserOrderFeign(重点)

package com.lei.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author lei
 * @verson:1.8
 *
 * @FeignClient(value = "order-service")
 * value 就是提供者的名称
 *
 */
@FeignClient(value = "order-service")
public interface UserOrderFeign {


    /**
     * 调那个controller接口 就写它的方法签名
     * 方法签名(就是包含一个方法的所有属性)
     */

    @GetMapping("doOrder")
     String doOrder();
}

创建 controller

package com.lei.controller;

import com.lei.feign.UserOrderFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class UserController {

    @Autowired


    /**
     * 总结
     * 浏览器(前端) ------->user-service(/userDOOrder)---------RPC(feign)---->order-service(/doOrder)
     */
    private UserOrderFeign userOrderFeign ;
    @GetMapping("userDOOrder")
    public String userDOOrder(){
        System.out.println("有用户进来了");
        //这里发起远程调用
        String s = userOrderFeign.doOrder();
        return s;
    }
}

consumer-user-service 修改启动类

package com.lei;

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

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients //开启feign的客户端 才可以发起调用
public class UserServiceApplication {

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

}

访问localhost:8081/userDOOrder

本次调用总结

consumer-user-service---》 /userDoOrder ---》通过 feign 调用 /doOrder ---

provider-order-service 下单成功

调用超时设置

因 为 ribbon 默 认 调 用 超 时 时 长 为 1s , 可 以 修 改 , 超 时 调 整 可 以 查 看

DefaultClientConfigImpl

#feign只是帮你封装了远程调用功能 底层还是ribbon 所以需要修改ribbon的时间
ribbon:
  ReadTimeout: 3000 #给3秒超时时间
  ConnectTimeout: 3000 #链接服务超时时间

OoenFeign调用参数处理(开发重点)

Feign 传参确保消费者和提供者的参数列表一致 包括返回值 方法签名要一致

1. 通过 URL 传参数,GET请求,参数列表使用@PathVariable(“”)

2. 如果是 GET 请求,每个基本参数必须加@RequestParam(“”)

3. 如果是 POST 请求,而且是对象集合等参数,必须加@Requestbody或者@RequestParam**

修改provider-order-service

创建order类

package com.lei.domain;

import java.util.Date;
/**
 * @author lei
 * @verson:1.8
 */
public class Order {
    private Integer id;
    private String name;
    private Double price;
    private Date time;
    public Order() {
    }
    public Order(Integer id, String name, Double price, Date time) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.time = time;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

创建ParamController

package com.lei.controller;

import com.lei.domain.Order;
import org.springframework.web.bind.annotation.*;

/**
 * @author lei
 * @verson:1.8
 *
 * url  /doOrder/热干面/add/油条/aaa
 * get传递一个参数
 * get传递多个参数
 * post传递一个对象+一个基本参数
 *
 */
@RestController
public class ParamController {

    @GetMapping("testUrl/{name}/and/{age}")
    public String testUrl(@PathVariable("name")String name ,@PathVariable("age") Integer age){
        System.out.println(name+":"+ age);
        return "ok";
    }
    @GetMapping("oneParam")
    public String oneParam(@RequestParam(required = false) String name ){
        System.out.println(name);
        return "ok";
    }
    @GetMapping("twoParam")
    public String twoParam(@RequestParam(required = false) String name,@RequestParam(required = false) Integer age ){
        System.out.println(name);
        System.out.println(age);
        return "ok";
    }
    @PostMapping("oneObj")
    public String oneObj(@RequestBody Order order){
        System.out.println(order);

        return "ok";
    }
    @PostMapping("oneObjOneParam")
    public String oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name){
        System.out.println(order);
        System.out.println(name);

        return "ok";
    }
}

修改 consumer-user-service

Order 类拷贝过来,后面会抽到公共模块里

修改 UserOrderFeign 接口

package com.lei.feign;


import com.lei.domain.Order;
import org.springframework.cloud.openfeign.FeignClient;

import org.springframework.web.bind.annotation.*;

/**
 * @author lei
 * @verson:1.8
 *
 * @FeignClient(value = "order-service")
 * value 就是提供者的名称
 *
 */
@FeignClient(value = "order-service")
public interface
UserOrderFeign {


    /**
     * 调那个controller接口 就写它的方法签名
     * 方法签名(就是包含一个方法的所有属性)
     */

    @GetMapping("doOrder")
     String doOrder();


    @GetMapping("testUrl/{name}/and/{age}")
    public String testUrl(@PathVariable("name")String name , @PathVariable("age") Integer age);

    @GetMapping("oneParam")
    public String oneParam(@RequestParam(required = false) String name );


    @GetMapping("twoParam")
    public String twoParam(@RequestParam(required = false) String name,@RequestParam(required = false) Integer age );


    @PostMapping("oneObj")
    public String oneObj(@RequestBody Order order);


    @PostMapping("oneObjOneParam")
    public String oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name);
}

在 UserController调用

package com.lei.controller;

import com.lei.domain.Order;
import com.lei.feign.UserOrderFeign;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class UserController {
    @Autowired
    /**
     * 总结
     * 浏览器(前端) ------->user-service(/userDOOrder)---------RPC(feign)---->order-service(/doOrder)
     */
    private UserOrderFeign userOrderFeign ;
    @GetMapping("userDOOrder")
    public String userDOOrder(){
        System.out.println("有用户进来了");
        //这里发起远程调用
        String s = userOrderFeign.doOrder();
        return s;
    }
    @GetMapping("testParam")
    public String testParam(){
        String lei = userOrderFeign.testUrl("lei", 18);
        System.out.println(lei);
        String l = userOrderFeign.oneParam("老罗");
        System.out.println(l);

        String s = userOrderFeign.twoParam("磊哥", 20);
        System.out.println(s);

        Order order = new Order(1,"牛排",188D,new Date());
        userOrderFeign.oneObj(order);

        userOrderFeign.oneObjOneParam(order,"击歌");
        return "ok";
    }
}

时间日期参数问题

使用 feign 远程调用时,传递 Date 类型,接收方的时间会相差 14 个小时,是因为时区造成

处理方案:

  1. 使用字符串传递参数,接收方转换成时间类型(推荐使用)不要单独传递时间

  2. 使用 JDK8 的 LocalDate(日期) 或 LocalDateTime(日期和时间,接收方只有秒,没有毫秒)

  3. 自定义转换方法

传参总结:

get 请求只用来传递基本参数 而且加注解****@RequestParam

post 请求用来传递对象参数 并且加注解****@RequestBody

OpenFeign 总结

OpenFeign 主要基于接口和注解实现了远程调用

源码总结:面试

1. OpenFeign 用过吗?它是如何运作的?

在主启动类上加上****@EnableFeignClients 注解后,启动会进行包扫描,把所有加了

@FeignClient(value=xxx-service)注解的接口进行创建代理对象通过代理对象,使用

ribbon 做了负载均衡和远程调用

2. 如何创建的代理对象?

当 项 目 在 启 动 时 , 先 扫 描 , 然 后 拿 到 标 记 了 @FeignClient 注 解 的 接 口 信 息 , 由

ReflectiveFeign 类的 newInstance 方法创建了代理对象 JDK 代理

3. OpenFeign 到底是用什么做的远程调用?

使用的是 HttpURLConnection java.net)

4. OpenFeign 怎么和 ribbon 整合的?

在代理对象执行调用的时候

Spring Cloud Hystrix

1.前言

1.1 什么是服务雪崩

Springcloud04

Springcloud05

服务雪崩的本质:线程没有及时回收。

不管是调用成功还是失败,只要线程可以及时回收,就可以解决服务雪崩

1.2 服务雪崩怎么解决

1.2.1 修改调用的超时时长(不推荐)

将服务间的调用超时时长改小,这样就可以让线程及时回收,保证服务可用

优点:非常简单,也可以有效的解决服务雪崩

缺点:不够灵活,有的服务需要更长的时间去处理(写库,整理数据)

1.2.2 设置拦截器

Springcloud06

2.Spring Cloud Hystrix 简介

熔断器,也叫断路器!(正常情况下 断路器是关的 只有出了问题才打开)用来保护微服务不雪崩的方法。思想和我们上面画的拦截器一样。

Hystrix 是 Netflix 公司开源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。Hystrix 是通过隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统的弹性。微博 弹性云扩容 Docker K8s

3 Hystrix 入门

当有服务调用的时候,才会出现服务雪崩,所以 Hystrix 常和 OpenFeign,Ribbon 一起出现

OpenFeign 中使用 Hystrix(重点)

创建提供者rent-car-service

和provider- order-service 提供者创建一模一样

修改 consumer-user-service

添加Hystrix依赖

<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
   </dependency>
    <!--Hystrix依赖-->
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
   </dependency>
创建 UserFeignHystrix实现 UserOrderFeign
package com.lei.feign.hystrix;

import com.lei.feign.UserOrderFeign;
import org.springframework.stereotype.Component;

/**
 * @author lei
 * @verson:1.8
 */
@Component
public class UserFeignHystrix implements UserOrderFeign {
    /**
     * 这个是备选方案
     * @return
     */

    @Override
    public String doOrder() {
        return "我是备胎";
    }
}

修改 OrderServiceFeign 增加一个 fallback
@FeignClient(value = "order-service",fallback = UserFeignHystrix.class)
public interfaceUserOrderFeign {

在配置文件配置

feign:
  hystrix:
    enabled: true  #开启断路器的使用

4 hystrix 常用配置

feign:
  hystrix: #开启熔断
    enabled: true  #在cloud的F班之前默认开启 因为有了其他熔断组件 ,默认关闭
hystrix: #hystrix 的全局控制
  command:
     default: #default 是全局控制,也可以换成单个方法控制,把 default 换成方法名即可
        circuitBreaker:
          enabled: true #开启断路器
          requestVolumeThreshold: 3 #失败次数(阀值)
          sleepWindowInMilliseconds: 20000 #窗口时间
          errorThresholdPercentage: 60 #失败率
        execution:
          isolation:
               Strategy: thread #隔离方式 thread 线程隔离集合和 SEMAPHORE 信号量隔离级别
               thread:
                 timeoutInMilliseconds: 3000 #调用超时时长
               ribbon:
                 ReadTimeout: 5000 #要结合 feign 的底层 ribbon 调用的时长
                 ConnectTimeout: 5000
        fallback:
          isolation:
            semaphore:
              maxConcurrentRequests: 1000 #信号量隔离级别最大并发数
#隔离方式 两种隔离方式 thread 线程池 按照 group(10 个线程)划分服务提供者,用户请求的线程和做远程的线程不一样
# 好处 当 B 服务调用失败了 或者请求 B 服务的量太大了 不会对 C 服务造成影响 用户访问比较大的情况下使用比较好 异步的方式
# 缺点 线程间切换开销大,对机器性能影响
# 应用场景 调用第三方服务 并发量大的情况下
# SEMAPHORE 信号量隔离 每次请进来 有一个原子计数器 做请求次数的++ 当请求完成以后 --
# 好处 对 cpu 开销小
# 缺点 并发请求不易太多 当请求过多 就会拒绝请求 做一个保护机制
# 场景 使用内部调用 ,并发小的情况下
# 源码入门 HystrixCommand AbstractCommand HystrixThreadPool

5 Feign 的工程化实例

  • eureka
  • ribbon
  • openfeign
  • hystrix

springcloud07

springcloud08

创建父项目 feign

feign依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lei</groupId>
    <artifactId>03-feign-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>project-domain</module>
        <module>api</module>
        <module>user-center</module>
        <module>order-center</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <!-- 打包方式-->
    <packaging>pom</packaging>
    <!--    全局版本号控制的地方-->
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <!--全局依赖  这里的依赖子项目也有    -->
    <dependencies>

    </dependencies>

    <!--加载这里的依赖不会被子项目引入,只是做一个版本控制 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--打包 仓库等配置 -->
    <build>

    </build>

</project>

创建子项目 module

projiect-domain 公共类

api

order-center 提供者

user-center消费者

projiect-domain 公共类

依赖

<?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>03-feign-project</artifactId>
        <groupId>com.lei</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>project-domain</artifactId>

</project>

创建一个实体类order

package com.lei.domain;

/**
 * @author lei
 * @verson:1.8
 */
public class Order {
    private Integer orderId;
    private String name;
    private Double price;

    public Order(Integer orderId, String name, Double price) {
        this.orderId = orderId;
        this.name = name;
        this.price = price;
    }

    public Order() {

    }



    public Integer getOrderId() {
        return orderId;
    }

    public void setOrderId(Integer orderId) {
        this.orderId = orderId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

api

依赖

引入project-domain依赖

<?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>03-feign-project</artifactId>
        <groupId>com.lei</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api</artifactId>

     <dependencies>
         <dependency>
             <groupId>com.lei</groupId>
             <artifactId>project-domain</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-openfeign</artifactId>
         </dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
         </dependency>
     </dependencies>


</project>

在com.lei.feign 创建UserOrderFeign 接口

package com.lei.feign;

import com.lei.domain.Order;
import com.lei.feign.hystrix.UserOrderFeignHystrix;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @author lei
 * @verson:1.8
 */
@FeignClient(value = "order-service",fallback = UserOrderFeignHystrix.class)
public interface UserOrderFeign {


    //查询订单
    @GetMapping("/order/getOrderByUserId")
    Order getOrderUserId(@RequestParam Integer userId);
}

创建 UserOrderFeignHystrix实现 UserOrderFeign

实现熔断

package com.lei.feign.hystrix;

import com.lei.domain.Order;
import com.lei.feign.UserOrderFeign;
import org.springframework.stereotype.Component;

/**
 * @author lei
 * @verson:1.8
 */
@Component
public class UserOrderFeignHystrix implements UserOrderFeign {
    /**、
     * 一般远程方案调用的熔断可以直接返回null
     * @param userId
     * @return
     */
    @Override
    public Order getOrderUserId(Integer userId) {
        return null;
    }
}

order-center 提供者

依赖

引用api依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>03-feign-project</artifactId>
        <groupId>com.lei</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order-center</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.lei</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

配置

server:
  port: 8082
spring:
  application:
    name: order-service
eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

创建OrderController实现UserOrderFeign

package com.lei.controller;

import com.lei.domain.Order;
import com.lei.feign.UserOrderFeign;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class OrderController implements UserOrderFeign {


    @Override
    public Order getOrderUserId(Integer userId) {
        System.out.println(userId);
        Order order = new Order(1, "鱼香肉丝", 20D);
        return order;
    }
}

启动类

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

/**
 * @author lei
 * @verson:1.8
 */
@SpringBootApplication
@EnableEurekaClient
public class OrderServiceApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApp.class,args);
    }
}

user-center消费者

依赖

引用api依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>03-feign-project</artifactId>
        <groupId>com.lei</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user-center</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.lei</groupId>
            <artifactId>api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

    </dependencies>
        <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

配置

server:
  port: 8083
spring:
  application:
    name: user-service
eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
feign:
  hystrix:
    enabled: true #开启熔断

编写UserController

package com.lei.controller;

import com.lei.domain.Order;
import com.lei.feign.UserOrderFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author lei
 * @verson:1.8
 */
@RestController
public class UserController {
    @Autowired
    private UserOrderFeign userOrderFeign;
    @GetMapping("find")
    public Order findOrder(){
        return userOrderFeign.getOrderUserId(1);
    }
}

启动类

package com.lei;

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

/**
 * @author lei
 * @verson:1.8
 */
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class UserServiceApp {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApp.class,args);
    }

}

**Spring Cloud Sleuth **

1.链路追踪就是:追踪微服务的调用路径

2.链路追踪的由来

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个请求都会开成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引导起整个请求最后的失败。(不建议微服务中链路调用超过 3次)

3.分布式链路调用的监控

sleuth+zipkin(zipkin 就是一个可视化的监控控制台)

Zipkin 是 Twitter 的一个开源项目,允许开发者收集 Twitter 各个服务上的监控数据,并提供查询接口。该系统让开发者可通过一个 Web 前端轻松的收集和分析数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈

4.Zipkin(可视化平台)

查看 zipkin 的控制台http://localhost:9411

添加依赖

consumer-user-service 和 provider-order-service 都要加

org.springframework.cloud

spring-cloud-starter-zipkin

修改配置文件

spring:
  application:
    name: user-service
  zipkin:
    base-url: http://localhost:9411
  sleuth:
      sampler:
        probability: 1 #采集率 默认比例0.1   0-1之间 1表示所有采集
        rate: 10  #为了使用速率限制采样器,选择每秒间隔接受的 trace 量,最小数字为 0,最大值为 2,147,483,647(最大 int) 默认为 10。

建议:远程调用的次数不要超过 3 2 层最好

Admin

Spring Boot Admin 是一个管理和监控Spring Boot 应用程序的开源软件。每个应用都认为是一个客户端,通过HTTP或者使用 Eureka注册到admin server中进行展示,Spring Boot Admin UI部分使用AngularJs将数据展示在前端。

创建admin-service

依赖

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.3.12.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lei</groupId>
<artifactId>admain</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>admin</name>
<description>Demo project for Spring Boot</description>
<properties>
   <java.version>1.8</java.version>
   <spring-boot-admin.version>2.3.0</spring-boot-admin.version>
   <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>de.codecentric</groupId>
      <artifactId>spring-boot-admin-starter-server</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
   </dependency>
</dependencies>

在启动类添加注解

@EnableAdminServer 开启admin

package com.lei;

import de.codecentric.boot.admin.server.config.EnableAdminServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
@EnableAdminServer
public class AdmainApplication {

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

}

配置

server:
  port: 10086
spring:
  application:
    name: admin-service

eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
management:
  endpoints:
    web:
      exposure:
        include: '*' #暴露所在监控点

在别的项目中添加这个依赖 admin就可以监控

<!--         暴露自身检查端点-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>

        </dependency>

Spring Cloud Gateway

1.什么是网关

网关是微服务最边缘的服务直接暴露给用户,用来做用户和微服务的桥梁

springcloud09

  1. 没有网关:客户端直接访问我们的微服务,会需要在客户端配置很多的 ip:port,如果user-service 并发比较大,则无法完成负载均衡

  2. 有网关:客户端访问网关,网关来访问微服务,(网关可以和注册中心整合,通过服务名称找到目标的

    ip:prot)这样只需要使用服务名称即可访问微服务,可以实现负载均衡,可以实现 token 拦截,权限验证,限流等操作

2 Spring Cloud Gateway 简介

你们项目里面 用的什么网关? gateway zuul

它是 Spring Cloud 官方提供的用来取代 zuul(netflix)的新一代网关组件

(zuul:1.0 , 2.0 ,zuul 的本质,**一组过滤器,根据自定义的过滤器顺序来执行,本质就是web**组件 web 三大组件(监听器 过滤器 servlet拦截 springmvc

Zuul1.0 使用的是 BIO(Blocking IO) tomcat7.0 以前都是 BIO 性能一般Zuul2.0 性能好 NIO

AIO 异步非阻塞 io a+nio = aio = async + no blocking io 它基于 spring5.x,springboot2.x 和 ProjectReactor 等技术。

它的目地是让路由更加简单,灵活,还提供了一些强大的过滤器功能,例如:熔断、限流、重试,自义定过滤器等 token 校验 ip 黑名单等

SpringCloud Gateway作为Spring Cloud生态的网关,目标是替代Zuul,在SpringCloud2.0以上的版本中,没有对新版本的 zuul2.0 以上的最新高性能版本进行集成,仍然还是使用的zuul1.x[可以看项目依赖找到]非 Reactor 模式的老版本。而为了提升网关的性能,SpringCloud Gateway 是基于 webFlux 框架实现的,而 webFlux 框架底层则使用了高性能的 Reactor 模式通信框架的 Netty

NIO(非阻塞式 io) BIO 你只需要了解网关能做什么? 网关里面写什么代码 就可以了

Spring Cloud Gateway具有以下特点:

  • 基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0 构建
  • 能够匹配任何请求属性上的路由。
  • 谓词和筛选器特定于路由。
  • hystrix断路器集成。
  • spring cloud DiscoveryClient集成
  • 易于编写的谓词和过滤器
  • 请求速率限制
  • 路径重写

3.Spring Cloud Gateway 工作流程

image-20230112221502740

客户端向 springcloud Gateway 发出请求,然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。

Handler 再通过指定的过滤器来将请求发送到我们实际的服务的业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送爱丽请求之前【pre】或之后【post】执行业务逻辑,对其进行加强或处理。

Filter 在 【pre】 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等

在【post】 类型的过滤器中可以做响应内容、响应头的修改、日志的输出,流量监控等有着非常重要的作用。

总结:Gateway 的核心逻辑也就是 路由转发 + 执行过滤器链

4.Spring Cloud Gateway 三大核心概念

4.1 Route(路由)(重点 和eureka结合做动态路由)

路由信息的组成:

由一个 ID、一个目的 URL、一组断言工厂、一组 Filter 组成。

如果路由断言为真,说明请求 URL 和配置路由匹配。

4.2 **Predicate(断言)(就是一个返回bool **的表达式)

Java 8 中的断言函数。 lambda 四大接口 供给形,消费性,函数型,断言型

​ spring Cloud Gateway 中 的 断 言 函 数 输 入 类 型 是 Spring 5.0 框 架 中 的ServerWebExchange

Spring Cloud Gateway 的断言函数允许开发者去定义匹配来自于Http Request 中的任何信息比如请求头和参数/

4.3 Filter(过滤) (重点)

一个标准的 Spring WebFilter。 Web 三大组件(servlet listener filter) mvc interceptor

Spring Cloud Gateway 中的 Filter 分为两种类型的 Filter,分别是 Gateway Filter 和Global Filter。过滤器 Filter 将会对请求和响应进行修改处理。

一个是针对某一个路由(路径)的 filter 对某一个接口做限流

一个是针对全局的 filter token ip 黑名单

5.Nginx Gateway 的区别

Nginx 在做路由,负载均衡,限流之前,都有修改 nginx.conf 的配置文件,把需要负载均衡,路由,限流的规则加在里面。Eg:使用 nginx 做 tomcat 的负载均衡

但是 gateway 不同,gateway 自动的负载均衡和路由,gateway 和 eureka 高度集成,实现自动的路由,和 Ribbon 结合,实现了负载均衡(lb),gateway 也能轻易的实现限流和权限验证。

Nginx(c)比 gateway(java)的性能高一点。

本质的区别呢?

Nginx (更大 服务器级别的)

Gateway (项目级别的)

springcloud11

6.Gateway 路由快速入门

6.1 本次访问流程

springcloud12

6.2 新建项目选择依赖(不要选 web)

依赖

<dependencies>
   <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
   </dependency>
   
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
   </dependency>
</dependencies>

配置

server:
  port: 80 #网关一般80
spring:
  application:
    name: geteway-service
  cloud:
    gateway:
      enabled: true  #开启网关
      routes: #设置路由,注意是数组,可以设置多个,按照 id 做隔离
        - id: login-service-route  #路由id 保持唯一
          uri: http://localhost:8081   #设置真正的服务 ip:port  URI(统一资源标识符) URL(统一资源定位符)
          predicates: #断言匹配
            - Path=/doLogin  #匹配规则 只要路径path匹配上了 就往uri转发

7.Gateway 集群

基础服务设施 (eureka,gateway configserver auth-server)

springcloud13

8.Gateway 的两种路由配置方式

8.1 代码路由方式(掌握)

官网给出的配置类,我们照葫芦画瓢

package com.lei.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

/**
 * @author lei
 * @verson:1.8
 */
public class RouteConfig {
    /**
     * 是代码的路由  和yml 不冲突
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
               .route("test-id",r->r.path("/projects").uri("https://spring.io/"))
                .build();
    }
}

8.2 使用 yml 方式(重点)

和上面的快速入门一样,使用 yml 的方式,在开发中是常用的

server:
  port: 80 #网关一般80
spring:
  application:
    name: geteway-service
  cloud:
    gateway:
      enabled: true  #开启网关
      routes: #设置路由,注意是数组,可以设置多个,按照 id 做隔离
        - id: login-service-route  #路由id 保持唯一
          uri: http://localhost:8081   #设置真正的服务 ip:port  URI(统一资源标识符) URL(统一资源定位符)
          predicates: #断言匹配
            - Path=/doLogin  #匹配规则 只要路径path匹配上了 就往uri转发

9.Gateway 微服务名动态路由,负载均衡

9.1 概述

从之前的配置里面我们可以看到我们的 URL 都是写死的,这不符合我们微服务的要求,我们

微服务是只要知道服务的名字,根据名字去找,而直接写死就没有负载均衡的效果了

默认情况下 Gateway 会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路

由进行转发,从而实现动态路由的功能

需要注意的是 uri 的协议为 lb(load Balance),表示启用 Gateway 的负载均衡功能。

lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri

协议:就是双方约定的一个接头暗号 http://

9.2.1 修改 gateway 配置

server:
  port: 80 #网关一般80
spring:
  application:
    name: geteway-service
  cloud:
    gateway:
      enabled: true  #开启网关
      routes: #设置路由,注意是数组,可以设置多个,按照 id 做隔离
        - id: login-service-route  #路由id 保持唯一
          #uri: http://localhost:8081  #设置真正的服务 ip:port  URI(统一资源标识符) URL(统一资源定位符)
          uri: lb://login-service  #这个也是动态路由 写服务名称
          predicates: #断言匹配
            - Path=/doLogin  #匹配规则 只要路径path匹配上了 就往uri转发
      discovery:
        locator:
          enabled: true #开启动态路由  开通应用名称 找到服务的功能
          lower-case-service-id: true  # 开启服务名称小写

eureka:
  client:
    service-url:
      defaultZone: http://peer1:8761/eureka
    registry-fetch-interval-seconds: 3 #网关拉去服务列表时间缩短
  instance:
    hostname: localhost
    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

访问localhost/longin-service/doLogin

10. Predicate 断言工厂的使用【了解】

在 gateway 启动时会去加载一些路由断言工厂(判断一句话是否正确 一个 boolean 表达式 )

10.1 什么是断言,Gateway 里面有哪些断言

断言就是路由添加一些条件(丰富路由功能的)

通俗的说,断言就是一些布尔表达式,满足条件的返回 true,不满足的返回 false。

Spring Cloud Gateway 将路由作为 Spring WebFlux HandlerMapping 基础架构的一部分

进行匹配。Spring Cloud Gateway 包括许多内置的路由断言工厂。所有这些断言都与 HTTP

请求的不同属性匹配。您可以将多个路由断言可以组合使用

Spring Cloud Gateway 创建对象时,使用 RoutePredicateFactory 创建 Predicate 对象,

Predicate 对象可以赋值给 Route。

springcloud14

不能限制动态路由

spring:
  application:
    name: geteway-service
  cloud:
    gateway:
      enabled: true  #开启网关
      routes: #设置路由,注意是数组,可以设置多个,按照 id 做隔离
        - id: login-service-route  #路由id 保持唯一
          #uri: http://localhost:8081  #设置真正的服务 ip:port  URI(统一资源标识符) URL(统一资源定位符)
          uri: lb://login-service  #这个也是动态路由   写服务名称
          predicates: #断言
            - Path=/doLogin  #匹配规则 只要路径path匹配上了 就往uri转发
            - After=2023-01-13T22:55:54.492+08:00[Asia/Shanghai]  #时间限制
            - Method=Get,Post
            -

11. Filter 过滤器工厂(重点)

11.1 概述

gateway 里面的过滤器和 Servlet 里面的过滤器,功能差不多,路由过滤器可以用于修改进入

Http 请求和返回 Http 响应

11.2 分类

11.2.1 按生命周期分两种

pre 在业务逻辑之前

post 在业务逻辑之后

11.2.2 按种类分也是两种

  • GatewayFilter 针对一个路由 需要配置某个路由,才能过滤。如果需要使用全局路由,需要配置 DefaultFilters

    ​ 针对一个路由:1 记录接口的访问次数 2 限流操作

  • GlobalFilter 全局过滤器,不需要配置路由,系统初始化作用到所有路由上

​ 全局过滤器 :1.统计请求次数 2.限流 3.token 的校验 4.ip 黑名单校验 跨域本质(filter)

11.3 官方文档查看过滤器

11.3.1 单一过滤器(31 个)

https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories

11.3.2 **全局过滤器(9 **个)

springcloud15

  1. Combined Global Filter and GatewayFilter Ordering
  2. Forward Routing Filter
  3. LoadBalancerClient Filter
  4. Netty Routing Filter
  5. Netty Write Response Filter
  6. RouteToRequestUrl Filter
  7. Websocket Routing Filter
  8. Gateway Metrics Filter
  9. Marking An Exchange As Routed

GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由

11.4 自定义网关过滤器(重点)

11.4.1 自定义全局过滤器

全局过滤器的优点的初始化时默认挂到所有路由上,我们可以使用它来完成 IP 过滤,限流等

功能

11.4.2 创建配置类 GlobalFilterConfig

package com.lei.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author lei
 * @verson:1.8
 * 定义过滤器
 */
@Component
public class GlobalFilterConfig implements GlobalFilter, Ordered {
    /**
     * 这个是过滤的方法
     * 责任链模式(过滤器链模式)
     * 网关里面有使用  mybatis的 二级缓存有变种责任模式
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //针对请求的过滤 拿到请求 header url 参数....
        ServerHttpRequest request = exchange.getRequest();
        //HttpServletRequest 是web里面的
        //ServerHttpRequest 是webFlux里面 响应式里面的
        String path = request.getURI().getPath();
        System.out.println(path);
        HttpMethod method = request.getMethod();
        System.out.println(method);
        String hostName = request.getRemoteAddress().getHostName();
        System.out.println(hostName);
        String ip = request.getHeaders().getHost().getHostName();
        System.out.println(ip);
        //响应相关的数据
        ServerHttpResponse response = exchange.getResponse();
        //用了微服务 肯定是前后端分离 一般通过json数据
        //{"code":200,"msg":"ok"}
        //设置编码 响应头里面设置
        response.getHeaders().set("content-type","application/json;charset=utf-8");
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", HttpStatus.UNAUTHORIZED.value());
        map.put("msg","未授权");
        ObjectMapper objectMapper = new ObjectMapper();
        //把一个map转成一个字节
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //同buffer工厂将字节数组包装成 一个数据包
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
        //放行到下一个过滤器
//        return chain.filter(exchange);
    }

    /**
     * 指定顺序的方法
     * 越小越先执行
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

实现ordered做排序 如果有很多的过滤器需要排序

12. IP 认证拦截实战

12.1 创建 IPGlobalFilter

package com.lei.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author lei
 * @verson:1.8
 * 网关 拦截器
 * ip拦截
 * 请求都有一个源头
 * 电话144 027
 * 请求---》gateway---->service
 * 黑名单 black_list
 * 白名单 white_list
 * 具体业务服务一般黑名单,数据库一般白名单
 */
@Component
public class IPGlobalFilter implements GlobalFilter, Ordered {
    /**
     * 网关的并发量比较高 不要再网关里面直接操作mysql
     * 后台系统可以查询数据库 用户量 并发不大
     * 如果并发量大 可以查redis 或在内存中写好
     */
    public static final List<String> BLACk_LIST= Arrays.asList("127.0.0.2");
    /**
     * 1.拿到ip
     * 2. 校验IP是否符合规范
     * 3  拦截或放行
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String ip = request.getHeaders().getHost().getHostString();
        //查询数据库 看这个IP是否存在在黑名单里面
        if (!BLACk_LIST.contains(ip)){
            return chain.filter(exchange);
        }
        //拦截
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("content-type","application/json;charset=utf-8");
        HashMap<String, Object> map = new HashMap<>(4);
        map.put("code",438);
        map.put("msg","你是黑名单");
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));


    }

    @Override
    public int getOrder() {
        return -5;
    }
}

token校验

流程

springcloud16

posted @   磊尘  阅读(86)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
点击右上角即可分享
微信分享提示