Java微服务篇2——SpringCloud

Java微服务篇2——SpringCloud

1、微服务架构

1.1、单体应用架构

的⽤户量、数据量规模都⽐较⼩,项目所有的功能模块都放在一个工程中编码、 编译、打包并且部署在一个Tomcat容器中的架构模式就是单体应用架构,这样的架构既简单实 ⽤、便于维护,成本⼜低,成为了那个时代的主流架构⽅式。

Java微服务篇2——SpringCloud_第1张图片
Java微服务篇2——SpringCloud_第2张图片
优点

  • 高效开发:项⽬前期开发节奏快,团队成员少的时候能够快速迭代
  • 架构简单:MVC架构,只需要借助IDE开发、调试即可
  • 易于测试:只需要通过单元测试或者浏览器完成
  • 易于部署:打包成单⼀可执⾏的jar或者打成war包放到容器内启动

缺点

  • 可靠性差: 某个应用Bug,例如死循环、内存溢出等, 可能会导致整个应用的崩溃
  • 复杂性高: 以一个百万行级别的单体应用为例,整个项目包含的模块多、模块的边界模糊、 依赖 关系不清晰、 代码质量参差不齐、 混乱地堆砌在一起。使得整个项目非常复杂
  • 扩展能力受限: 单体应用只能作为一个整体进行扩展,无法根据业务模块的需要进行伸缩。例如, 应用中有的模块是计算密集型的,它需要强劲的CPU; 有的模块则是IO密集型的,需要更大的内 存。 由于这些模块部署在一起,不得不在硬件的选择上做出妥协

业务量上涨之后,单体应用架构进一步丰富变化,比如应用集群部署、使用Nginx进行负载均衡、增加 缓存服务器、增加文件服务器、数据库集群并做读写分离等,通过以上措施增强应对高并发的能力、应 对一定的复杂业务场景,但依然属于单体应用架构

Java微服务篇2——SpringCloud_第3张图片

1.2、垂直应用架构

为了避免上⾯提到的那些问题,开始做模块的垂直划分,做垂直划分的原则是基于拉勾现有的业务 特性来做,核心目标标第⼀个是为了业务之间互不影响,第⼆个是在研发团队的壮⼤后为了提⾼效率, 减少组件之间的依赖

Java微服务篇2——SpringCloud_第4张图片
优点

  • 系统拆分实现了流量分担,解决了并发问题
  • 可以针对不同模块进⾏优化
  • ⽅便⽔平扩展,负载均衡,容错率提⾼
  • 系统间相互独⽴,互不影响,新的业务迭代时更加⾼效

缺点

  • 服务之间相互调⽤,如果某个服务的端⼝或者ip地址发⽣改变,调⽤的系统得⼿动改变
  • 搭建集群之后,实现负载均衡⽐较复杂,如:内⽹负载,在迁移机器时会影响调⽤⽅的路 由,导致 线上故障
  • 服务之间调⽤⽅式不统⼀,基于 httpclient 、 webservice ,接⼝协议不统⼀
  • 服务监控不到位:除了依靠端⼝、进程的监控,调⽤的成功率、失败率、总耗时等等这些监 控指标 是没有的

1.3、SOA应用架构

在做了垂直划分以后,模块随之增多,维护的成本在也变⾼,⼀些通⽤的业务和模块重复的越来越 多,为了解决上⾯提到的接⼝协议不统⼀、服务⽆法监控、服务的负载均衡,引⼊了阿⾥巴巴开源的 Dubbo ,⼀款⾼性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成。它提供了三⼤核⼼能 ⼒:⾯向接⼝的远程⽅法调⽤,智能容错和负载均衡,以及服务⾃动注册和发现

SOA (Service-Oriented Architecture),即面向服务的架构。根据实际业务,把系统拆分成合适 的、独立部署的模块,模块之间相互独立(通过Webservice/Dubbo等技术进行通信)
Java微服务篇2——SpringCloud_第5张图片
优点

  • 分布式
  • 松耦合
  • 扩展灵活
  • 可重用

缺点

  • 服务抽取粒度较大
  • 服务调用方和提供方耦合度较高(接口耦合度)

1.4、微服务应用架构

微服务架构可以说是SOA架构的一种拓展,这种架构模式下它拆分粒度更小、服务更独立。把应用 拆分成为一个个微小的服务,不同的服务可以使用不同的开发语言和存储,服务之间往往通过Restful等 轻量级通信。微服务架构关键在于微小、独立、轻量级通信

微服务是在 SOA 上做的升华粒度更加细致,微服务架构强调的⼀个重点是业务需要彻底的组件化和服务化

Java微服务篇2——SpringCloud_第6张图片
微服务架构和SOA架构很明显的一个区别就是服务拆分粒度的不同,但是对于拉勾的架构发展来 说,我们所看到的SOA阶段其实服务拆分粒度相对来说已经比较细了(超前哦!),所以上述拉勾SOA 到拉勾微服务,从服务拆分上来说变化并不大,只是引入了相对完整的新一代Spring Cloud微服务技 术。自然,上述我们看到的都是拉勾架构演变的阶段结果,每一个阶段其实都经历了很多变化,服务拆分其实也是走过了从粗到细,并非绝对的一步到位

2、微服务架构的核心概念

2.1、API网关

API请求调用统一接入API网关层,由网关转发请求,API网关更专注在安全、路由、流量等问题的处理上(微服务团队专注于处理业务逻辑即可)

  • 统一接入(路由)
  • 安全防护(统一鉴权,负责网关访问身份认证验证,与“访问认证中心”通信,实际认证业务逻辑 交移“访问认证中心”处理)
  • 黑白名单(实现通过IP地址控制禁止访问网关功能,控制访问)
  • 协议适配(实现通信协议校验、适配转换的功能)
  • 流量管控(限流)
  • 长短链接支持
  • 容错能力(负载均衡)

2.2、服务注册与发现

服务提供者将所提供服务的信息(服务器IP和端口、服务访问协议等)注册/登记到注册中心

服务消费者能够从注册中心获取到较为实时的服务列表,然后根究一定的策略选择一个服务访问

Java微服务篇2——SpringCloud_第7张图片
Java微服务篇2——SpringCloud_第8张图片

2.3、通信

服务间通信采用的方式,例如:http,rpc

2.4、熔断

熔断即断路保护,微服务架构中,如果下游服务因访问压力过大而响应变慢或失败,上游服务为了 保护系统整体可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断

2.5、链路追踪

微服务架构越发流行,一个项目往往拆分成很多个服务,那么一次请求就需要涉及到很多个服务。不同 的微服务可能是由不同的团队开发、可能使用不同的编程语言实现、整个项目也有可能部署在了很多服 务器上(甚至百台、千台)横跨多个不同的数据中心。所谓链路追踪,就是对一次请求涉及的很多个服 务链路进行日志记录、性能监控
Java微服务篇2——SpringCloud_第9张图片

3、SpringCloud

3.1、SpringCloud是什么

Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分 布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等, 都可以用 Spring Boot的开发风格做到一键启动和部署。Spring Cloud并没有重复制造轮子,它只是将 目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封 装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统 开发工具包

3.2、SpringCloud可以做什么

  • Distributed/versioned configuration (分布式/版本化配置
  • Service registration and discovery (服务注册和发现)
  • Routing (智能路由) Service-to-service calls (服务调用)
  • Load balancing (负载均衡)
  • Circuit Breakers (熔断器)
  • Global locks (全局锁)
  • Leadership election and cluster state(选举与集群状态管理)
  • Distributed messaging (分布式消息传递平台)

3.3、Spring Cloud 架构

Java微服务篇2——SpringCloud_第10张图片

4、微服务过程存在的问题

4.1、商品案例

数据库准备

CREATE DATABASE springcloud;
USE springcloud;
CREATE TABLE commodity(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(50), #商品名称
	price DOUBLE,	#商品价格
	DESC VARCHAR(100), #商品描述
	images VARCHAR(400), #商品图片
	stock INT, #商品库存
	TYPE VARCHAR(20), #商品类型
	deteled TINYINT(1) #逻辑删除
);

4.2、目录结构

Java微服务篇2——SpringCloud_第11张图片

4.3、父项目


<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.0modelVersion>
    <modules>
        <module>productmodule>
        <module>consumemodule>
    modules>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.5.3version>
        <relativePath/> 
    parent>
    <groupId>cn.winktogroupId>
    <artifactId>springcloudartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>springcloudname>
    <description>Demo project for Spring Clouddescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    
    <packaging>pompackaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-loggingartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>8source>
                    <target>8target>
                    <encoding>UTF-8encoding>
                configuration>
            plugin>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackagegoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    build>

project>

4.4、生产者(product)

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>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>
    <artifactId>productartifactId>

    <dependencies>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.2.0version>
        dependency>
    dependencies>
project>

application.yaml

mybatis:
  type-aliases-package: cn.winkto.bean
  mapper-locations: classpath:mapper/*.xml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: blingbling123.
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
  application:
    name: eureka-product
server:
  port: 9000

实体类

public class Commodity {
    private int id;
    private String name;
    private double price;
    private String desc;
    private String images;
    private int stock;
    private String type;
    private boolean deteled;
}

mapper

@Repository
public interface CommodityMapper {
    public Commodity selectCommodityById(int id);
}

service

public interface CommodityService {
    public Commodity selectCommodityById(int id);
}
@Service
public class CommodityServiceImpl implements CommodityService {
    @Autowired
    CommodityMapper commodityMapper;
    @Override
    public Commodity selectCommodityById(int id) {
        return commodityMapper.selectCommodityById(id);
    }
}

controller

@RestController
public class CommodityController {
    @Autowired
    CommodityService commodityService;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("生产者被调用");
        return commodityService.selectCommodityById(id);
    }
}

启动类

@SpringBootApplication
@MapperScan("cn.winkto.mapper")
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}

4.5、消费者(consume)

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>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>consumeartifactId>


project>

application.yaml

server:
  port: 9100
spring:
  application:
    name: consume

bean

public class Commodity {
    private int id;
    private String name;
    private double price;
    private String desc;
    private String images;
    private int stock;
    private String type;
    private boolean deteled;
}

配置类

@Configuration
public class WinktoConfig {
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

controller

@RestController
public class CommodityController {
    @Autowired
    RestTemplate restTemplate;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("消费者被调用");
        return restTemplate.getForObject("http://localhost:9000/selectCommodityById/1", Commodity.class);
    }
}

启动类

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

4.6、测试

http://localhost:9000/selectCommodityById/1http://localhost:9100/selectCommodityById/1

4.7、问题分析

  • 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
  • 服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡
  • 在服务消费者中,不清楚服务提供者的状态
  • 服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常页面?
  • RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?
  • 这么多的微服务统一认证如何实现?
  • 配置文件每次都修改好多个很麻烦?

解决方案

  • 服务管理:自动注册与发现、状态监管
  • 服务负载均衡
  • 熔断
  • 远程过程调用
  • 网关拦截、路由转发
  • 统一认证
  • 集中式配置管理,配置信息实时自动更新

5、SpringCloud一代组件学习

版本号问题

springcloud使用的版本号是英文方式,而不是传统的数字版本,因为springcloud是微服务的解决方案,他会有很多子项目,每个子项目都维护这自己的版本号,为了避免冲突,就使用了伦敦地铁站的名字作为版本号。以首字母作为顺序,a,b,c,d…排列

现有版本号:Angel、Brixton、Camden、Daston、Edgware、Finchley、GreenWich、Hoxton

  • Base:设计阶段。只有相应的设计没有具体的功能实现
  • Alpha:软件的初级版本。基本功能已经实现,但存在较多的bug
  • Bate:相对于Alpha已经有了很大的进步,消除了严重的BUG,但还存在一些潜在的BUG,还需要不断测试
  • RELEASE:最终版本,没有太大的问题

后缀名

  • BUILD-XXX:开发版,开发团队内部使用,不是很稳定
  • GA:稳定版,相比于开发版,基本上可以使用了
  • PRE(M1、M2):里程碑版,主要是修复了一些BUG的版本,一个GA后通常有多个里程碑版
  • RC:候选发布版,该阶段的软件类似于最终版的一个发行观察期,基本只修复比较严重的BUG
  • SR:正式发布版

一定要注意版本对应!!!

5.1、Eureka服务注册中心

Eureka 工作模式

Java微服务篇2——SpringCloud_第12张图片
Eureka 基础架构
Java微服务篇2——SpringCloud_第13张图片
Eureka 交互流程及原理
Java微服务篇2——SpringCloud_第14张图片

  • 图中Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注 册事件会在集群和分区中进行数据同步,Application Client作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用
  • 微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒,默认Eureka Server 90S会将还没有续约的给剔除)以续约自己的信息
  • Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务节点(默认90秒)
  • 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步
  • Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者

Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性

5.1.1、搭建eureka-server

目录结构

![]](https://img-blog.csdnimg.cn/f9f6bc4de18d47ee843e72d457885cda.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2d5ZmdoaA==,size_16,color_FFFFFF,t_70)
父依赖中导入springcloud


<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-dependenciesartifactId>
            <version>Hoxton.SR12version>
            <type>pomtype>
            <scope>importscope>
        dependency>
    dependencies>
dependencyManagement>

模块依赖


<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>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>eurekaartifactId>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
        dependency>
    dependencies>
project>

application.yaml

server:
  port: 8099
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    # 是否向eureka注册自己,自己就是服务端,所以不用注册
    register-with-eureka: false
    # 是否需要从Eureka Server获取服务信息,己就是服务端,所以不用拉取
    fetch-registry: false
  instance:
    hostname: localhost

启动器

@SpringBootApplication
//开启euraka服务
@EnableEurekaServer
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

最终效果

http://localhost:8099/

Java微服务篇2——SpringCloud_第15张图片
Java微服务篇2——SpringCloud_第16张图片
可以将自己注册进eureka里,测试一下效果

server:
  port: 8099
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    # 是否向eureka注册自己,自己就是服务端,所以不用注册
    register-with-eureka: true
    # 是否需要从Eureka Server获取服务信息,己就是服务端,所以不用拉取
    fetch-registry: true
  instance:
    hostname: localhost
    #使用ip注册,否则会使用主机名注册
    prefer-ip-address: true
    #自定义实例显示格式
    instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@

Java微服务篇2——SpringCloud_第17张图片

5.1.2、将微服务注册进eureka

生产者依赖修改


<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>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>
    <artifactId>productartifactId>

    <dependencies>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.2.0version>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
    dependencies>
project>

生产者application.yaml

mybatis:
  type-aliases-package: cn.winkto.bean
  mapper-locations: classpath:mapper/*.xml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: blingbling123.
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
  application:
    name: product
server:
  port: 9000
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map
      defaultZone: http://localhost:8099/eureka/
  instance:
    #使用ip注册,否则会使用主机名注册
    prefer-ip-address: true
    #自定义实例显示格式
    instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@

消费者依赖修改


<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>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>consumeartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
    dependencies>
project>

消费者application.yaml

server:
  port: 9100
spring:
  application:
    name: consume
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map
      defaultZone: http://localhost:8099/eureka/
  instance:
    #使用ip注册,否则会使用主机名注册
    prefer-ip-address: true
    #自定义实例显示格式
    instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@

生产者和消费者启动器都添加/@EnableDiscoveryClient注解

  • @EnableEurekaClient:仅用于Eureka
  • @EnableDiscoveryClient:可以用为所有注册中心

最终效果
Java微服务篇2——SpringCloud_第18张图片

5.1.3、eureka-server集群

拷贝Eureka模块,修改端口,互相注册启动服务
Java微服务篇2——SpringCloud_第19张图片
最终效果
Java微服务篇2——SpringCloud_第20张图片

5.1.4、通过eureka-server解除硬编码

消费者修改启动类

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

消费者消费过程修改

@RestController
public class CommodityController {
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("消费者被调用");
        // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
        
        // 通过服务id(服务名)获取服务列表
        List<ServiceInstance> product = discoveryClient.getInstances("product");
        // http://192.168.1.16:9000
        System.out.println(product.get(0).getUri());
        return restTemplate.getForObject(product.get(0).getUri()+"/selectCommodityById/"+id, Commodity.class);
    }
}

5.1.5、eureka元数据

  • 标准元数据:主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用
  • 自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这 些元数据可以在远程客户端中访问

添加元数据

mybatis:
  type-aliases-package: cn.winkto.bean
  mapper-locations: classpath:mapper/*.xml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: blingbling123.
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
  application:
    name: pruduct
server:
  port: 9000
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map
      defaultZone: http://127.0.0.1:8099/eureka/,http://127.0.0.1:8199/eureka/
  instance:
    #使用ip注册,否则会使用主机名注册
    prefer-ip-address: true
    #自定义实例显示格式
    instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
    metadata-map:
      name: winkto
      password: blingbling

消费者获取元数据

@RestController
public class CommodityController {
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("消费者被调用");
        // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);

        // 通过服务id(服务名)获取服务列表
        List product = discoveryClient.getInstances("pruduct");
        // http://192.168.1.16:9000
        System.out.println(product.get(0).getMetadata());
        return restTemplate.getForObject(product.get(0).getUri()+"/selectCommodityById/"+id, Commodity.class);
    }
}

最终效果
Java微服务篇2——SpringCloud_第21张图片

5.1.6、服务续约(心跳检测)

服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期, 然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测

  • Eureka Client :30S续约一次,在Eureka Server更新自己的状态 (Client端进行配置)
  • Eureka Server:90S还没有进行续约,将该微服务实例从服务注册表(Map)剔除 (Client端进行 配置)
  • Eureka Client: 30S拉取服务最新的注册表并缓存到本地 (Client端进行配置)

当需要修改时,需要修改下配置即可

server:
  port: 8099
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map(互相注册)
      defaultZone: http://127.0.0.1:8199/eureka/
    # 是否向eureka注册自己,自己就是服务端,所以不用注册
    register-with-eureka: false
    # 是否需要从Eureka Server获取服务信息,己就是服务端,所以不用拉取
    fetch-registry: false
  instance:
    #使用ip注册,否则会使用主机名注册
    prefer-ip-address: true
    #自定义实例显示格式
    instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
    # 续约间隔时间,默认30秒
    lease-renewal-interval-in-seconds: 30
    # 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
    lease-expiration-duration-in-seconds: 90

5.1.7、eureka服务下线

  • 服务下线
    • 当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer
    • 服务中心接受到请求后,将该服务置为下线状态
  • 失效剔除
    • Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检 查,如果发现实例在在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-inseconds定义,默认值为90s)内没有收到心跳,则会注销此实例

5.1.8、eureka自我保护机制

自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的 健壮、稳定的运行

如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么 Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制

  • Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务

  • Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用

  • 当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中

Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪
Java微服务篇2——SpringCloud_第22张图片

5.2、Ribbon负载均衡

负载均衡一般分为服务器端负载均衡和客户端负载均衡

  • 服务器端负载均衡,比如Nginx、F5这些,请求到达服务器之后由这些负载均衡器根据一定的 算法将请求路由到目标服务器处理

  • 客户端负载均衡,比如我们要说的Ribbon,服务消费者客户端会有一个服务器地址列表,调用 方在请求前通过一定的负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在请求客户端进行
    Java微服务篇2——SpringCloud_第23张图片

5.2.1、Ribbon快速入门

无需额外依赖

复制一份product为product-duplicatet(端口号为9001),启动eureka-server

修改生产者controller(打印一下端口号)

@RestController
public class CommodityController {
    @Value("${server.port}")
    int port;
    @Autowired
    CommodityService commodityService;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println(port+"生产者被调用");
        return commodityService.selectCommodityById(id);
    }
}

修改消费者(添加@LoadBalanced表负载均衡)

@Configuration
public class WinktoConfig {
    // 旧版注册
    // @Bean
    // @LoadBalanced
    // public RestTemplate restTemplate(){
    //     return new RestTemplate();
    // }
    
    // 新版注册
    @Bean
    @LoadBalanced
    public RestOperations restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}
@RestController
public class CommodityController {
    @Autowired
    RestOperations restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("消费者被调用");
        // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);

        // 通过服务id(服务名)获取服务列表
        // List product = discoveryClient.getInstances("product");
        // http://192.168.1.16:9000
        // System.out.println(product.get(0).getMetadata());
        // 无需ip与端口号,直接填写服务名称即可
        return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    }
}

启动生产者消费者
Java微服务篇2——SpringCloud_第24张图片

http://localhost:9100/selectCommodityById/1

Java微服务篇2——SpringCloud_第25张图片

5.2.2、Ribbon负载均衡策略

Ribbon内置了多种负载均衡策略,负载均衡的顶级接口为 com.netflix.loadbalancer.IRule,其结构图为

Java微服务篇2——SpringCloud_第26张图片

负载均衡策略 描述
RoundRobinRule 轮询策略,默认超过10次获取到的server都不可用,会返回一个空的server
RandomRule 随机策略,如果随机到的server为null或者不可用的话,会while不停的循环选取
RetryRule 重试策略 一定时限内循环重试。默认继承RoundRobinRule,也支持自定 注入,RetryRule会在每次选取之后,对选举的server进行判断,是 否为null,是否alive,并且在500ms内会不停的选取判断。而 RoundRobinRule失效的策略是超过10次,RandomRule是没有失效时间的概念,只要serverList没都挂
BestAvailableRule 最小 连接数策略 遍历serverList,选取出可用的且连接数最小的一个server。该算法里面有一个LoadBalancerStats的成员变量,会存储所有server的 运行状况和连接数。如果选取到的server为null,那么会调用 RoundRobinRule重新选取
AvailabilityFilteringRule 可用过滤策略 扩展了轮询策略,会先通过默认的轮询选取一个server,再去判断该server是否超时可用,当前连接数是否超限,都成功再返回
ZoneAvoidanceRule 区域权衡策略(默认策略) 扩展了轮询策略,继承了2个过滤器:ZoneAvoidancePredicate和 AvailabilityPredicate,除了过滤超时和链接数过多的server,还会过滤掉不符合要求的zone区域里面的所有节点, 在一个区域/机房 内的服务实例中轮询。先过滤再轮询

轮询策略的修改

# 服务名称
product:
  ribbon:
  # 负载均衡策略
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

Java微服务篇2——SpringCloud_第27张图片

5.3、Hystrix

5.3.1、雪崩

服务雪崩效应:是一种因“服务提供者的不可用”(原因)导致“服务调用者不可用”(结果),并将不可用逐渐放大的现象

  • 扇入:代表着该微服务被调用的次数,扇入大,说明该模块复用性好
  • 扇出:该微服务调用其他微服务的个数,扇出大,说明业务逻辑复杂
    Java微服务篇2——SpringCloud_第28张图片
    Java微服务篇2——SpringCloud_第29张图片
    Java微服务篇2——SpringCloud_第30张图片
    Java微服务篇2——SpringCloud_第31张图片

服务雪崩原因

  • 服务提供者不可用
    • 硬件故障
    • 程序bug
    • 缓存击穿
    • 请求量过大
  • 重试加大请求流量
    • 用户重试
    • 代码逻辑重试
  • 服务调用者不可用
    • 同步资源耗尽

雪崩的解决方案

  • 服务熔断:在微服务架构中,当扇出链路的某个微服务不可用或者响应时间太长时,将熔断该节点微服务的调用,进行服务的降级,快速返回错误的响应信息,当检测到该节点微服务调用响应正常后,恢复调用链路
  • 服务降级:就是当某个服务熔断之后,服务器将不再被调用,此刻客户端可以自 己准备一个本地的fallback回调,返回一个缺省值,这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强
  • 服务限流:服务降级是当服务出问题或者影响到核心流程的性能时,暂时将服务屏蔽掉,待高峰或者问题解决后再打开

5.3.2、Hystrix核心内容

  • 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑。 页面静态化微服务方法 (@HystrixCommand 添加Hystrix控制)
  • 跳闸机制:当某服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)。如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等
  • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态(探测服务是否可用,如还是不可用, 再次退回打开状态)

5.3.3、Hystrix快速入门

生产者改造(添加延迟)

@RestController
public class CommodityController {
    @Value("${server.port}")
    int port;
    @Autowired
    CommodityService commodityService;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println(port+"生产者被调用");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return commodityService.selectCommodityById(id);
    }
}

消费者改造(对controller中熔断策略进行配置,在启动类上开启熔断)

@RestController
public class CommodityController {
    @Autowired
    RestOperations restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;

    // @RequestMapping("/selectCommodityById/{id}")
    // public Commodity selectCommodityById(@PathVariable int id){
    //     System.out.println("消费者被调用");
    //     // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
    //
    //     // 通过服务id(服务名)获取服务列表
    //     // List product = discoveryClient.getInstances("product");
    //     // http://192.168.1.16:9000
    //     // System.out.println(product.get(0).getMetadata());
    //     // 无需ip与端口号,直接填写服务名称即可
    //     return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    // }

    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就共用了
            threadPoolKey = "selectCommodityById",
            // 线程池细节属性配置
            threadPoolProperties = {
                    // 线程数
                    @HystrixProperty(name="coreSize",value = "1"),
                    // 等待队列长度
                    @HystrixProperty(name="maxQueueSize",value="20")
            },
            commandProperties = {
                    // 每一个属性都是一个HystrixProperty
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000")
            }
    )
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("消费者被调用");
        return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    }
}
@SpringBootApplication
@EnableDiscoveryClient
//开启熔断服务
@EnableCircuitBreaker
public class ConsumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumeApplication.class, args);
    }
}

效果
Java微服务篇2——SpringCloud_第32张图片

5.3.4、服务降级

修改一下消费者controller(添加失败回调)

@RestController
public class CommodityController {
    @Autowired
    RestOperations restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;

    // @RequestMapping("/selectCommodityById/{id}")
    // public Commodity selectCommodityById(@PathVariable int id){
    //     System.out.println("消费者被调用");
    //     // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
    //
    //     // 通过服务id(服务名)获取服务列表
    //     // List product = discoveryClient.getInstances("product");
    //     // http://192.168.1.16:9000
    //     // System.out.println(product.get(0).getMetadata());
    //     // 无需ip与端口号,直接填写服务名称即可
    //     return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    // }

    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就共用了
            threadPoolKey = "selectCommodityById",
            // 线程池细节属性配置
            threadPoolProperties = {
                    // 线程数
                    @HystrixProperty(name="coreSize",value = "1"),
                    // 等待队列长度
                    @HystrixProperty(name="maxQueueSize",value="20")
            },
            commandProperties = {
                    // 每一个属性都是一个HystrixProperty
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000")
            },
            fallbackMethod = "selectCommodityByIdFallBack"
    )
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("消费者被调用");
        return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    }
    public Commodity selectCommodityByIdFallBack(int id){
        return new Commodity();
    }
}

最终效果
Java微服务篇2——SpringCloud_第33张图片

5.3.5、Hystrix舱壁模式

其实就是线程池隔离策略

如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程),那么这样的话会导致问题, 这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的,如果方法A的请求把10个 线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用
Java微服务篇2——SpringCloud_第34张图片

threadPoolKey参数即表明开启舱壁模式

5.3.6、Hystrix熔断机制

Java微服务篇2——SpringCloud_第35张图片

  • 当调用出现问题时,开启一个时间窗(10s)
  • 在这个时间窗内,统计调用次数是否达到最小请求数?
    • 如果没有达到,则重置统计信息,回到第1步
    • 如果达到了,则统计失败的请求数占所有请求数的百分比,是否达到阈值? 如
      • 果达到,则跳闸(不再请求对应服务)
      • 如果没有达到,则重置统计信息,回到第1步
  • 如果跳闸,则会开启一个活动窗口(默认5s),每隔5s,Hystrix会让一个请求通过,到达那个问题服 务,看是否调用成功,如果成功,重置断路器回到第1步,如果失败,回到第3步

修改HystrixCommand的commandProperties属性

@RestController
public class CommodityController {
    @Autowired
    RestOperations restTemplate;
    @Autowired
    DiscoveryClient discoveryClient;

    // @RequestMapping("/selectCommodityById/{id}")
    // public Commodity selectCommodityById(@PathVariable int id){
    //     System.out.println("消费者被调用");
    //     // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
    //
    //     // 通过服务id(服务名)获取服务列表
    //     // List product = discoveryClient.getInstances("product");
    //     // http://192.168.1.16:9000
    //     // System.out.println(product.get(0).getMetadata());
    //     // 无需ip与端口号,直接填写服务名称即可
    //     return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    // }

    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就共用了
            threadPoolKey = "selectCommodityById",
            // 线程池细节属性配置
            threadPoolProperties = {
                    // 线程数
                    @HystrixProperty(name="coreSize",value = "1"),
                    // 等待队列长度
                    @HystrixProperty(name="maxQueueSize",value="20")
            },
            commandProperties = {
                    // 每一个属性都是一个HystrixProperty
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),

                    // 统计窗口时间的设置
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
                    // 统计窗口内的最小请求数
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
                    // 统计窗口内错误请求阈值的设置 50%
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
                    // 自我修复的活动窗口时间
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
            },
            fallbackMethod = "selectCommodityByIdFallBack"
    )
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println("消费者被调用");
        return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    }
    public Commodity selectCommodityByIdFallBack(int id){
        return new Commodity();
    }
}

application.yaml开放健康检查接口

# springboot中暴露健康检查等断点接口
management:
  endpoints:
    web:
      exposure:
        include: "*"
  # 暴露健康接口的细节
  endpoint:
    health:
      show-details: always

Java微服务篇2——SpringCloud_第36张图片

HystrixCommand的commandProperties属性也可以使用yaml方式书写

# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
        forceOpen: false
        # 触发熔断错误比例阈值,默认值50%
        errorThresholdPercentage: 50
        # 熔断后休眠时长,默认值5秒
        sleepWindowInMilliseconds: 3000
        # 熔断触发最小请求次数,默认值是20
        requestVolumeThreshold: 2
      execution:
        isolation:
          thread:
            # 熔断超时设置,默认为1秒
            timeoutInMilliseconds: 2000

5.3.7、Hystrix扩展

当我们来调整maxQueueSize属性,也需要调整queueSizeRejectionThreshold(队列最大阈值,超出的请求将会被拒绝)两个属性必须同时配置

5.4、 Feign

Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用的), 是以Java接口注解的方式调用Http请求

SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解(OpenFeign)

5.4.1、Feign快速入门

导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
dependencies>

启动类修改(启用feign功能)

@SpringBootApplication
@EnableDiscoveryClient
//开启熔断服务
// @EnableCircuitBreaker
//开启Feign远程调用(自带熔断)
@EnableFeignClients
public class ConsumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumeApplication.class, args);
    }
}

FeignClient接口(尽量与controller保持一致)

@FeignClient(name = "product")
public interface CommondityFeign {
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id);
}

修改controller

@RestController
public class CommodityController {
    // @Autowired
    // RestOperations restTemplate;
    // @Autowired
    // DiscoveryClient discoveryClient;

    @Autowired
    private CommondityFeign commondityFeign;

    // @RequestMapping("/selectCommodityById/{id}")
    // public Commodity selectCommodityById(@PathVariable int id){
    //     System.out.println("消费者被调用");
    //     // return restTemplate.getForObject("http://localhost:9000/selectCommodityById/"+id, Commodity.class);
    //
    //     // 通过服务id(服务名)获取服务列表
    //     // List product = discoveryClient.getInstances("product");
    //     // http://192.168.1.16:9000
    //     // System.out.println(product.get(0).getMetadata());
    //     // 无需ip与端口号,直接填写服务名称即可
    //     return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    // }

    // @HystrixCommand(
    //         // 线程池标识,要保持唯一,不唯一的话就共用了
    //         threadPoolKey = "selectCommodityById",
    //         // 线程池细节属性配置
    //         threadPoolProperties = {
    //                 // 线程数
    //                 @HystrixProperty(name="coreSize",value = "1"),
    //                 // 等待队列长度
    //                 @HystrixProperty(name="maxQueueSize",value="20")
    //         },
    //         commandProperties = {
    //                 // 每一个属性都是一个HystrixProperty
    //                 @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),
    //
    //                 // 统计窗口时间的设置
    //                 @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
    //                 // 统计窗口内的最小请求数
    //                 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
    //                 // 统计窗口内错误请求阈值的设置 50%
    //                 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
    //                 // 自我修复的活动窗口时间
    //                 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
    //         },
    //         fallbackMethod = "selectCommodityByIdFallBack"
    // )
    // @RequestMapping("/selectCommodityById/{id}")
    // public Commodity selectCommodityById(@PathVariable int id){
    //     System.out.println("消费者被调用");
    //     return restTemplate.getForObject("http://product/selectCommodityById/"+id, Commodity.class);
    // }
    // public Commodity selectCommodityByIdFallBack(int id){
    //     return new Commodity();
    // }

    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就共用了
            threadPoolKey = "selectCommodityById",
            // 线程池细节属性配置
            threadPoolProperties = {
                    // 线程数
                    @HystrixProperty(name="coreSize",value = "1"),
                    // 等待队列长度
                    @HystrixProperty(name="maxQueueSize",value="20")
            },
            commandProperties = {
                    // 每一个属性都是一个HystrixProperty
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),

                    // 统计窗口时间的设置
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
                    // 统计窗口内的最小请求数
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
                    // 统计窗口内错误请求阈值的设置 50%
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
                    // 自我修复的活动窗口时间
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
            },
            fallbackMethod = "selectCommodityByIdFallBack"
    )
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        return commondityFeign.selectCommodityById(id);
    }
    public Commodity selectCommodityByIdFallBack(int id){
        return new Commodity();
    }
}

application.yaml添加ribbon.eureka.enabled=true(要不然集群的时候会爆服务找不到)

ribbon:
  eureka:
    enabled: true

5.4.2、Feign对负载均衡的支持

Feign 本身已经集成了Ribbon依赖和自动配置,因此我们不需要额外引入依赖,可以通过 ribbon.xx 来进 行全局配置,也可以通过服务名.ribbon.xx 来对指定服务进行细节配置配置

Feign默认的请求处理超时时长1s,有时候我们的业务确实执行的需要一定时间,那么这个时候,我们就 需要调整请求处理超时时长,Feign自己有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准

product:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    #对所有操作都进行重试
    OkToRetryOnAllOperations: true
    #对当前选中实例重试次数,不包括第一次调用
    MaxAutoRetries: 0
    #切换实例的重试次数
    MaxAutoRetriesNextServer: 0

5.4.3、Feign对熔断器的支持

默认Feign关闭熔断器

当有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最小值来进行的,即处理时长超过最短的那个超时时间了就熔断进入回退降级逻辑

feign:
  hystrix:
    enabled: true

此时注意之前的服务降级已经失效,需要重新配置

新建服务降级类实现CommondityFeign,书写默认方法

@Component("commindityFallback")
public class CommindityFallback implements CommondityFeign {
    @Override
    public Commodity selectCommodityById(int id) {
        return new Commodity();
    }
}

在CommondityFeign设置回调class

@FeignClient(name = "product",fallback = CommindityFallback.class)
public interface CommondityFeign {
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id);
}

Java微服务篇2——SpringCloud_第37张图片

5.4.4、Feign对请求压缩和响应压缩的支持

Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数 即可开启请求与响应的压缩功能

feign:
  hystrix:
    enabled: true
  compression:
    request:
      enabled: true #默认不开启
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,此处也是默认值
      min-request-size: 2048 # 设置触发压缩的大小下限,此处也是默认值
    response:
      enabled: true #默认不开启

5.5、GateWay

Spring Cloud GateWay不仅提供统一的路由方式(反向代理)并且基于 Filter(定义过滤器对请求过滤,完成一些功能) 链的方式提供了网关基本的功能,例如:鉴权、流量控制、熔断、路径重写、日志监控等
Java微服务篇2——SpringCloud_第38张图片

5.5.1、GateWay工作流程

客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配 的路由,将其发送到GateWay Web Handler;Handler再通过指定的过滤器链来将请求发送到我们实际 的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前 (pre)或者之后(post)执行业务逻辑。 Filter在“pre”类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post” 类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等。
Java微服务篇2——SpringCloud_第39张图片

5.5.2、GateWay快速入门

导入依赖


<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>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>gatewayartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-commonsartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-gatewayartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webfluxartifactId>
        dependency>
    dependencies>
project>

application.yaml

server:
  port: 9300
spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          # 去除uri第一个参数,如/commondity/selectCommodityById/1 ---> /selectCommodityById/1
          filters:
            - StripPrefix=1
  main:
    web-application-type: reactive
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map
      defaultZone: http://127.0.0.1:8099/eureka/,http://127.0.0.1:8199/eureka/
  instance:
    #使用ip注册,否则会使用主机名注册
    prefer-ip-address: true
    #自定义实例显示格式
    instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@

启动类

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

5.5.3、路由规则详解

Java微服务篇2——SpringCloud_第40张图片

时间前

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]

时间后

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

时间间

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Between=2017-01-20T17:42:47.789-07:00[America/Denver]

cookie

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Cookie=chocolate, ch.p

header

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Header=X-Request-Id, \d+

host

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Host=**.somehost.org,**.anotherhost.org

method

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Method=GET,POST

请求路径正则匹配

  • 上面的案例就是

请求参数

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Query=green

请求参数且值符合正则表达式

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - Query=red, gree.

远程地址匹配

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径
          uri: http://127.0.0.1:9100
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - RemoteAddr=192.168.1.1/24

5.5.4、动态路由

spring:
  cloud:
    gateway:
      routes:
        - id: commondity
          # 匹配的路径(动态路由:lb://+服务名称)
          uri: lb://product
          # 匹配规则
          predicates:
            - Path=/commondity/**
          filters:
            - StripPrefix=1

5.5.5、过滤器

过滤器生命周期来分类

  • pre:器在请求被路由之前调用,可利用这种过滤器实现身份验证、在集群中选 择 请求的微服务、记录调试信息等
  • post:在路由到微服务以后执行,可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等

过滤器范围来分类

  • GateWayFilter:应用到单个路由路由上
  • GlobalFilter:应用到所有的路由上(常用)

书写WinktoFilter实现GlobalFilter(完成过滤器功能),OrderedFilter(过滤器排序)

@Component
public class WinktoFilter implements GlobalFilter, OrderedFilter {

    private static List<String> blackList = new ArrayList<>();

    static {
        blackList.add("127.0.0.1");
        blackList.add("0:0:0:0:0:0:0:1");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String ip= Objects.requireNonNull(request.getRemoteAddress()).getHostString();
        for (int i = 0; i < blackList.size(); i++) {
            if (ip.equals(blackList.get(i))){
                return response.writeWith(Mono.just(response.bufferFactory().wrap("Black household".getBytes())));
            }
        }
        // 放行
        return chain.filter(exchange);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

    }

    // 返回值表示当前过滤器的顺序(优先级),数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

Java微服务篇2——SpringCloud_第41张图片

5.6、Config 分布式配置中心

分布式集群环境中可能有很多个微服务,我们不可能一个一个去修改配置 然后重启生效,在一定场景下我们还需要在运行期间动态调整配置信息,比如:根据各个微服务的负载 情况,动态调整数据源连接池大小,我们希望配置内容发生变化的时候,微服务可以自动更新。
在这里插入图片描述

  • Server 端:提供配置文件的存储、以接口的形式将配置文件的内容提供出去,通过使用 @EnableConfigServer注解在 Spring boot 应用中非常简单的嵌入
  • Client 端:通过接口获取配置数据并初始化自己的应用

5.6.1、github上不去解决方案

进入https://github.com.ipaddress.com/获取ip
Java微服务篇2——SpringCloud_第42张图片

进入https://fastly.net.ipaddress.com/github.global.ssl.fastly.net获取ip
Java微服务篇2——SpringCloud_第43张图片

打开C:\Windows\System32\drivers\etc,新建文件github

140.82.114.4 github.com
199.232.5.194 github.global.ssl.fastly.net

打开cmd,输入ipconfig /flushdns,更新DNS缓存

5.6.2、SpringCloud Config快速入门

Java微服务篇2——SpringCloud_第44张图片

新建config模块

导入依赖


<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>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>configartifactId>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-config-serverartifactId>
        dependency>
    dependencies>
project>

application.yaml

server:
  port: 9400
eureka:
  client:
    service-url:
      # 客户端与EurekaServer交互的地址,是一个map
      defaultZone: http://127.0.0.1:8099/eureka/,http://127.0.0.1:8199/eureka/
  instance:
    #使用ip注册,否则会使用主机名注册
    prefer-ip-address: true
    #自定义实例显示格式
    instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
spring:
  application:
    name: config
  cloud:
    config:
      server:
        git:
          # 项目地址:https://github.com/winkto/winkto.git
          uri: https://github.com/winkto/winkto.git
          username: winkto
          password: ******
          search-paths: winkto
      # 分支
      label: main

启动类

@SpringBootApplication
@EnableDiscoveryClient
// 开启配置服务器功能
@EnableConfigServer
public class ConfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigApplication.class, args);
    }
}

测试

http://localhost:9400/main/application-dev.yaml

Java微服务篇2——SpringCloud_第45张图片

5.6.3、SpringClou Config改造消费者

消费者导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-config-clientartifactId>
    dependency>
dependencies>

修改application.yaml为bootstrap.yaml,并添加分布式配置中心的地址(bootstrap.yml是系统级别的,优先级比application.yml高,应用启动时会检查这个配置文件, 在这个配置文件中指定配置中心的服务地址,会自动拉取所有应用配置并且启用)

spring:
  application:
    name: consume
  cloud:
    #config客户端配置,和ConfigServer通信,并告知ConfigServer希望获取的配置信息在哪个文件中
    config:
      # ConfigServer配置中心地址
      uri: http://localhost:9400
      name: application
      profile: dev
      label: main

contrller

@RestController
public class CommodityController {
    @Autowired
    private CommondityFeign commondityFeign;

    @Value("${person.name}")
    private String name;

    @Value("${person.age}")
    private int age;

    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就共用了
            threadPoolKey = "selectCommodityById",
            // 线程池细节属性配置
            threadPoolProperties = {
                    // 线程数
                    @HystrixProperty(name="coreSize",value = "1"),
                    // 等待队列长度
                    @HystrixProperty(name="maxQueueSize",value="20")
            },
            commandProperties = {
                    // 每一个属性都是一个HystrixProperty
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),

                    // 统计窗口时间的设置
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
                    // 统计窗口内的最小请求数
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
                    // 统计窗口内错误请求阈值的设置 50%
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
                    // 自我修复的活动窗口时间
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
            },
            fallbackMethod = "selectCommodityByIdFallBack"
    )
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        return commondityFeign.selectCommodityById(id);
    }
    @RequestMapping("/person")
    public String person(){
        return name+"=="+age;
    }
}

Java微服务篇2——SpringCloud_第46张图片

5.6.4、Config配置手动刷新

当我们修改GitHub上面的值时,服务端(Config Server)能 实时获取最新的值,但客户端(Config Client)读的是缓存,无法实时获取最新值

修改github上的配置文件
Java微服务篇2——SpringCloud_第47张图片

访问config查看文件
Java微服务篇2——SpringCloud_第48张图片

访问客户端,查看配置数据
Java微服务篇2——SpringCloud_第49张图片

发现数据并没有更新

开启手动刷新

消费者bootstrap.yml中添加配置

management:
  endpoints:
    web:
      exposure:
        # include: refresh
        include: "*"

消费者controller上加上手动刷新的标识

@RestController
@RefreshScope
public class CommodityController {
    @Autowired
    private CommondityFeign commondityFeign;

    @Value("${person.name}")
    private String name;

    @Value("${person.age}")
    private int age;

    @HystrixCommand(
            // 线程池标识,要保持唯一,不唯一的话就共用了
            threadPoolKey = "selectCommodityById",
            // 线程池细节属性配置
            threadPoolProperties = {
                    // 线程数
                    @HystrixProperty(name="coreSize",value = "1"),
                    // 等待队列长度
                    @HystrixProperty(name="maxQueueSize",value="20")
            },
            commandProperties = {
                    // 每一个属性都是一个HystrixProperty
                    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value= "5000"),

                    // 统计窗口时间的设置
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
                    // 统计窗口内的最小请求数
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
                    // 统计窗口内错误请求阈值的设置 50%
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
                    // 自我修复的活动窗口时间
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
            },
            fallbackMethod = "selectCommodityByIdFallBack"
    )
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        return commondityFeign.selectCommodityById(id);
    }
    @RequestMapping("/person")
    public String person(){
        return name+"=="+age;
    }
}

由于上github经常卡,程序拉不到最新的文件,于是我换了gitee,和之前的操作一样,当前gitee上的配置文件
Java微服务篇2——SpringCloud_第50张图片

config和comsume拿到的配置文件如下
Java微服务篇2——SpringCloud_第51张图片
Java微服务篇2——SpringCloud_第52张图片

修改gitee文件
Java微服务篇2——SpringCloud_第53张图片

刷新config文件(秒到有没有!!!,唯一体验不好的就是广告太多了)
Java微服务篇2——SpringCloud_第54张图片

此时consume仍是之前的缓存
Java微服务篇2——SpringCloud_第55张图片

我们手动让它刷新一下(在线发送一个post请求,注意不能是get)

http://localhost:9100/actuator/refresh

Java微服务篇2——SpringCloud_第56张图片

回到consume,刷新,发现配置已经更新
Java微服务篇2——SpringCloud_第57张图片

5.6.5、Config配置自动刷新

在微服务架构中,可以结合消息总线(Bus)实现分布式配置的自动更新(Spring Cloud Config + Spring Cloud Bus)

消息总线Bus,即使用MQ消息代理构建一个共用的Topic,通过这个Topic连接各个 微服务实例,MQ广播的消息会被所有在注册中心的微服务实例监听和消费。
Java微服务篇2——SpringCloud_第58张图片

Spring Cloud Bus(基于MQ的,支持RabbitMq/Kafka) 是Spring Cloud中的消息总线方案, Spring Cloud Config + Spring Cloud Bus 结合可以实现配置信息的自动更新

首先要保证有一个可以正常使用的MQ服务器
Java微服务篇2——SpringCloud_第59张图片

config和consume导入依赖

<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-bus-amqpartifactId>
dependency>

config和consume配置rabbitmq信息

spring:
  rabbitmq:
    host: 106.54.85.216
    port: 5672
    username: winkto
    password: blingbling

config配置暴露bus-refresh端口

management:
  endpoints:
    web:
      exposure:
        # include: bus-refresh
        include: "*"

修改gitee上的配置文件
Java微服务篇2——SpringCloud_第60张图片

config中心已经刷新
Java微服务篇2——SpringCloud_第61张图片

访问config,通知消息队列配置文件已经更新

http://127.0.0.1:9400/actuator/bus-refresh

Java微服务篇2——SpringCloud_第62张图片

consume更新成功
Java微服务篇2——SpringCloud_第63张图片

6、SpringCloud二代组件学习

Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服 务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服 务解决方案,通过阿里中间件来迅速搭建分布式应用系统
Java微服务篇2——SpringCloud_第64张图片

6.1、Nacos

6.1.1、Nacos安装使用

Nacos就是注册中心+配置中心的组合(Nacos=Eureka + Config + Bus)

Nacos 官方:https://github.com/alibaba/nacos/releases

Nacos 百度云:https://pan.baidu.com/s/1LLC30x5k6lxh865oxkQu1Q提取码:6i9j

安装方式:直接解压即可

启动方式

  • linux
sh startup.sh -m standalone
  • windows
cmd startup.cmd -m standalone

管理端

http://106.54.85.216:8848/nacos/index.html#/login

默认账号密码为nacos:nacos
Java微服务篇2——SpringCloud_第65张图片Java微服务篇2——SpringCloud_第66张图片

6.1.2、微服务注册到Nacos

我们将项目回退到4.7的代码

父项目


<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.0modelVersion>
    <packaging>pompackaging>
    <modules>
        <module>productmodule>
        <module>product-duplicatemodule>
    modules>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.12.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>cn.winktogroupId>
    <artifactId>springcloudaliartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>springcloudaliname>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Hoxton.SR12version>
                <type>pomtype>
                <scope>importscope>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-alibaba-dependenciesartifactId>
                <version>2.2.4.RELEASEversion>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-loggingartifactId>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <configuration>
                    <source>8source>
                    <target>8target>
                    <encoding>UTF-8encoding>
                configuration>
            plugin>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackagegoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    build>

project>

生产者

导入依赖


<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>springcloudaliartifactId>
        <groupId>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>productartifactId>

    <dependencies>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.2.0version>
        dependency>
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
    dependencies>

project>

实体类

public class Commodity {
    private int id;
    private String name;
    private double price;
    private String desc;
    private String images;
    private int stock;
    private String type;
    private boolean deteled;
}

mapper

@Repository
public interface CommodityMapper {
    public Commodity selectCommodityById(int id);
}

mapper映射文件


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.winkto.mapper.CommodityMapper">
    <select id="selectCommodityById" parameterType="int" resultType="Commodity">
        select * from commodity where id=#{id}
    select>
mapper>

service

public interface CommodityService {
    public Commodity selectCommodityById(int id);
}
@Service
public class CommodityServiceImpl implements CommodityService {
    @Autowired
    CommodityMapper commodityMapper;
    @Override
    public Commodity selectCommodityById(int id) {
        return commodityMapper.selectCommodityById(id);
    }
}

controller

@RestController
public class CommodityController {
    @Value("${server.port}")
    int port;
    @Autowired
    CommodityService commodityService;
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        System.out.println(port+"生产者被调用");
        return commodityService.selectCommodityById(id);
    }
}

启动类

@SpringBootApplication
@MapperScan("cn.winkto.mapper")
public class ProductduplicateApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductduplicateApplication.class, args);
    }
}

application.yaml

mybatis:
  type-aliases-package: cn.winkto.bean
  mapper-locations: classpath:mapper/*.xml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: blingbling123.
    url: jdbc:mysql://localhost:3306/springcloud?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
  application:
    name: product
  cloud:
    nacos:
      discovery:
        server-addr: 106.54.85.216:8848
server:
  port: 9001

将生产者拷贝一份为product-duplicate

消费者

导入依赖


<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>springcloudaliartifactId>
        <groupId>cn.winktogroupId>
        <version>0.0.1-SNAPSHOTversion>
    parent>
    <modelVersion>4.0.0modelVersion>

    <artifactId>consumeartifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-openfeignartifactId>
        dependency>
        <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
        dependency>
    dependencies>
project>
public class Commodity {
    private int id;
    private String name;
    private double price;
    private String desc;
    private String images;
    private int stock;
    private String type;
    private boolean deteled;
}

config

@Configuration
public class WinktoConfig {
    // 旧版注册
    // @Bean
    // @LoadBalanced
    // public RestTemplate restTemplate(){
    //     return new RestTemplate();
    // }

    // 新版注册
    @Bean
    @LoadBalanced
    public RestOperations restTemplate(RestTemplateBuilder builder) {
        return builder.build();
    }
}

feign

@FeignClient(name = "product",fallback = CommindityFallback.class)
public interface CommondityFeign {
    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id);
}
@Component("commindityFallback")
public class CommindityFallback implements CommondityFeign {
    @Override
    public Commodity selectCommodityById(int id) {
        return new Commodity();
    }
}

controller

@RestController
public class CommodityController {
    // @Autowired
    // RestOperations restTemplate;
    // @Autowired
    // DiscoveryClient discoveryClient;

    @Autowired
    private CommondityFeign commondityFeign;

    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        return commondityFeign.selectCommodityById(id);
    }
}

启动类

@SpringBootApplication
@EnableDiscoveryClient
//开启熔断服务
// @EnableCircuitBreaker
//开启Feign远程调用(自带熔断)
@EnableFeignClients
public class ConsumeApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumeApplication.class, args);
    }
}

application.yaml

server:
  port: 9100
spring:
  application:
    name: consume
  cloud:
    nacos:
      discovery:
        server-addr: 106.54.85.216:8848
# springboot中暴露健康检查等断点接口
management:
  endpoints:
    web:
      exposure:
        # include: refresh
        include: "*"
  # 暴露健康接口的细节
  endpoint:
    health:
      show-details: always

效果
Java微服务篇2——SpringCloud_第67张图片

6.1.3、保护阈值

保护阈值:可以设置为0-1之间的浮点数,它其实是一个比例值(当前服务健康实例数/当前服务总实例数)

nacos是服务注册中心,服务消费者要从nacos获取某一个服务的可用实例信息,对于 服务实例有健康/不健康状态之分,nacos在返回给消费者实例信息的时候,会返回健康实例。这个时候在一些高并发、大流量场景下会存在一定的问题

如果服务A有100个实例,98个实例都不健康了,只有2个实例是健康的,如果nacos只返回这两个 健康实例的信息的话,那么后续消费者的请求将全部被分配到这两个实例,流量洪峰到来,2个健康的实例也扛不住了,整个服务A就扛不住,上游的微服务也会导致崩溃,产生雪崩效应

保护阈值的意义在于 当服务A健康实例数/总实例数 < 保护阈值 的时候,说明健康实例真的不多了,这个时候保护阈值会被触发(状态true)

nacos将会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样也比造成雪崩要好,牺牲了一些请求,保证了整个系统的一个可用。

6.1.4、负载均衡

Nacos客户端引入的时候,会关联引入Ribbon的依赖包,我们使用OpenFiegn的时候也会引入Ribbon的依赖,Ribbon包括Hystrix都按原来方式进行配置即可

消费者application.yaml

server:
  port: 9100
spring:
  application:
    name: consume
  cloud:
    nacos:
      discovery:
        server-addr: 106.54.85.216:8848
# springboot中暴露健康检查等断点接口
management:
  endpoints:
    web:
      exposure:
        # include: refresh
        include: "*"
  # 暴露健康接口的细节
  endpoint:
    health:
      show-details: always
# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。默认false关闭的
        forceOpen: false
        # 触发熔断错误比例阈值,默认值50%
        errorThresholdPercentage: 50
        # 熔断后休眠时长,默认值5秒
        sleepWindowInMilliseconds: 3000
        # 熔断触发最小请求次数,默认值是20
        requestVolumeThreshold: 2
      execution:
        isolation:
          thread:
            # 熔断超时设置,默认为1秒
            timeoutInMilliseconds: 2000
product:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
    #对所有操作都进行重试
    OkToRetryOnAllOperations: true
    #对当前选中实例重试次数,不包括第一次调用
    MaxAutoRetries: 0
    #切换实例的重试次数
    MaxAutoRetriesNextServer: 0

6.1.5、数据模型

Java微服务篇2——SpringCloud_第68张图片

Namespace:命名空间,对不同的环境进行隔离,比如隔离开发环境、测试环境和生产环境

Group:分组,将若干个服务或者若干个配置集归为一组,通常习惯一个系统归为一个组

Service:某一个服务,比如商品微服务

DataId:配置集或者可以认为是一个配置文件

概念 描述
Namespace 代表不同的环境,如开发dev、测试test、生产环境prod
Group 代表某项目
Service 某个项目中具体xxx服务
DataId 某个项目中具体的xxx配置文件

Java微服务篇2——SpringCloud_第69张图片

6.1.6、配置中心

使用Nacos之后,分布式配置就简单很多,Github不需要了(配置信息直接配置在Nacos server中),Bus也不需要了(依然可以完成动态刷新)

dataid完整格式

${prefix}-${spring.profile.active}.${file-extension}
  • prefix 默认为spring.application.name
  • spring.profile.active即为当前环境对应的 profile
  • file-extension为配置内容的数据格式,可以通过配置项

添加配置文件
Java微服务篇2——SpringCloud_第70张图片
Java微服务篇2——SpringCloud_第71张图片

导入依赖

<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>

application.yaml更名为bootstrap.yaml,并添加nacos地址

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 106.54.85.216:8848
      config:
        server-addr: 106.54.85.216:8848
        # 命名空间,不指定默认为public
        namespace: 03f3f10e-9154-41df-8bbb-5a79e743bf40
        # 分组,不指定默认为DEFAULT_GROUP
        group: DEFAULT_GROUP
        file-extension: yaml

controller添加接口

@RestController
@RefreshScope
public class CommodityController {
    // @Autowired
    // RestOperations restTemplate;
    // @Autowired
    // DiscoveryClient discoveryClient;

    @Autowired
    private CommondityFeign commondityFeign;
    @Value("${message}")
    private String message;

    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id){
        return commondityFeign.selectCommodityById(id);
    }
    @RequestMapping("/hello")
    public String hello(){
        return message;
    }
}

Java微服务篇2——SpringCloud_第72张图片

编辑配置文件
Java微服务篇2——SpringCloud_第73张图片
刷新接口页面
Java微服务篇2——SpringCloud_第74张图片

6.1.7、多配置文件

若一个微服务希望从配置中心Nacos server中获取多个dataId的配置信息,则只需扩展多个 dataId即可

添加配置文件
Java微服务篇2——SpringCloud_第75张图片

修改bootstrap.yaml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 106.54.85.216:8848
      config:
        server-addr: 106.54.85.216:8848
        # 命名空间,不指定默认为public
        namespace: 03f3f10e-9154-41df-8bbb-5a79e743bf40
        # 分组,不指定默认为DEFAULT_GROUP
        group: DEFAULT_GROUP
        file-extension: yaml
        extension-configs:
          - data-id: m1
            refresh: true
          - data-id: m2
            refresh: true

访问接口
在这里插入图片描述
修改m1
Java微服务篇2——SpringCloud_第76张图片
刷新页面
Java微服务篇2——SpringCloud_第77张图片

6.2、Sentinel

Sentinel是一个面向云原生微服务的流量控制、熔断降级组件

替代Hystrix,针对问题:服务雪崩、服务降级、服务熔断、服务限流

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器 秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 # 锁定server端的配置文件(读取它的配置项) namespace: 07137f0a-bf66-424b-b910-20ece612395a # 命名空间id group: DEFAULT_GROUP # 默认分组就是DEFAULT_GROUP,如果使用默认分组可以不配置 file-extension: yaml #默认properties # 根据规则拼接出来的dataId效果:lagou-service-page.yaml ext-config[0]: data-id: abc.yaml group: DEFAULT_GROUP refresh: true #开启扩展dataId的动态刷新 ext-config[1]: data-id: def.yaml group: DEFAULT_GROUP refresh: true #开启扩展dataId的动态刷新
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口,可以通过实现扩展接口来快 速地定制逻辑,例如定制规则管理、适配动态数据源等。

6.2.1、Sentinel-dashboard

Sentinel-dashboard1.8.2 官方:https://github.com/alibaba/Sentinel/releases

Sentinel-dashboard1.8.2 百度云:https://pan.baidu.com/s/1j6Lht2cZRxrgkQjiHezakA提取码:r9pf

端口:8080

访问地址:http://localhost:8080/

账号密码:sentinel/sentinel
Java微服务篇2——SpringCloud_第78张图片

6.2.2、Sentinel快速入门

消费者导入依赖


<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>

bootstrap.yaml修改(配置sentinel dashboard,删除原有hystrix配置,删除 原有OpenFeign的降级配置)

server:
  port: 9100
spring:
  application:
    name: consume
  cloud:
    nacos:
      discovery:
        server-addr: 106.54.85.216:8848
      config:
        server-addr: 106.54.85.216:8848
        # 命名空间,不指定默认为public
        namespace: 03f3f10e-9154-41df-8bbb-5a79e743bf40
        # 分组,不指定默认为DEFAULT_GROUP
        group: DEFAULT_GROUP
        file-extension: yaml
        extension-configs:
          - data-id: m1
            refresh: true
          - data-id: m2
            refresh: true
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
        port: 8719
# springboot中暴露健康检查等断点接口
management:
  endpoints:
    web:
      exposure:
        include: "*"
  # 暴露健康接口的细节
  endpoint:
    health:
      show-details: always

启动静态化微服务,使用 Sentinel 监控静态化微服务,发现控制台没有任何变化,因为懒加载,我们只需要发起一次请求触发即可
Java微服务篇2——SpringCloud_第79张图片

6.2.3、流控规则快速体验

Java微服务篇2——SpringCloud_第80张图片
Java微服务篇2——SpringCloud_第81张图片
Java微服务篇2——SpringCloud_第82张图片

6.2.4、通过线程数限流

修改消费者controller

@RestController
@RefreshScope
public class CommodityController {
    // @Autowired
    // RestOperations restTemplate;
    // @Autowired
    // DiscoveryClient discoveryClient;

    @Autowired
    private CommondityFeign commondityFeign;

    @Value("${message}")
    private String message;

    @Value("${message1}")
    private String message1;

    @Value("${message2}")
    private String message2;

    @RequestMapping("/selectCommodityById/{id}")
    public Commodity selectCommodityById(@PathVariable int id) throws InterruptedException {
        Thread.sleep(10000);
        return commondityFeign.selectCommodityById(id);
    }
    @RequestMapping("/hello")
    public String hello(){
        return message+"==="+message1+"==="+message2;
    }
}

新增线程规则
Java微服务篇2——SpringCloud_第83张图片

使用并发软件测试即可

6.2.5、流控关联规则

当其他服务达到阈值,当前服务失败
Java微服务篇2——SpringCloud_第84张图片

6.2.6、降级

Sentinel 降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这 个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后, 在接下来的降级时间窗口之内,对该资源的调用都自动熔断,这里的降级其实是Hystrix中的熔断

你可能感兴趣的:(ReJava,java)