目录
1. SpringCloud Eureka(服务注册与发现组件)
2. SpringCloud Ribbon(负载均衡与服务调用组件)
3. SpringCloud OpenFeign(负载均衡与服务调用组件)
SpringCloud:用于开发高度可扩展、高性能的分布式微服务系统。
前言
微服务(MicroServices)
微服务
1. 服务
项目中的功能模块(帮助用户解决某一个或一组问题),在开发过程中表现为 IDE(集成开发环境,例如 Eclipse 或 IntelliJ IDEA)中的一个工程或Moudle。
2. 微
服务体积小、复杂度低(一个微服务通常只提供单个业务功能的服务,代码较少);微服务团队所需成员少(只需要开发人员2到5名即可完成从设计、开发、测试到运维的全部工作)。
微服务架构(一种系统架构风格)
与传统的单体式架构不同,微服务架构提倡将一个单一的应用程序拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间使用轻量级通信机制(通常是 HTTP RESTFUL API)进行通讯。
通常情况下,这些小型服务都是围绕着某个特定的业务进行构建的,每一个服务只专注于完成一项任务并把它做好 ,即“专业的人做专业的事”。
每个服务都能够独立地部署到各种环境中,例如开发环境、测试环境和生产环境等,每个服务都能独立启动或销毁而不会对其他服务造成影响。
这些服务之间的交互是使用标准的通讯技术进行的,因此不同的服务可以使用不同数据存储技术,甚至使用不同的编程语言。
微服务架构 VS 单体架构
单体架构是微服务架构出现之前业界最经典的软件架构类型,许多早期的项目采用的也都是单体架构。单体架构将应用程序中所有业务逻辑都编写在同一个工程中,最终经过编译、打包,部署在一台服务器上运行。
在项目的初期,单体架构无论是在开发速度还是运维难度上都具有明显的优势。但随着业务复杂度的不断提高,单体架构的许多弊端也逐渐凸显出来,主要体现在以下 3 个方面:
1. 随着业务复杂度的提高,单体应用(采用单体架构的应用程序)的代码量也越来越大,导致代码的可读性、可维护性以及扩展性下降。
2. 随着用户越来越多,程序所承受的并发越来越高,而单体应用处理高并发的能力有限。
3. 单体应用将所有的业务都集中在同一个工程中,修改或增加业务都可能会对其他业务造成一定的影响,导致测试难度增加。
由于单体架构存在这些弊端,因此许多公司和组织都开始将将它们的项目从单体架构向微服务架构转型。
不同点 | 微服务架构 | 单体架构 |
---|---|---|
团队规模 | 微服务架构可以将传统模式下的单个应用拆分为多个独立的服务,每个微服务都可以单独开发、部署和维护。每个服务从设计、开发到维护所需的团队规模小,团队管理成本小。 | 单体架构的应用程序通常需要一个大型团队,围绕一个庞大的应用程序工作,团队管理的成本大。 |
数据存储方式 | 不同的微服务可以使用不同的数据存储方式,例如有的用 Redis,有的使用 MySQL。 | 单一架构的所有模块共享同一个公共数据库,存储方式相对单一。 |
部署方式 | 微服务架构中每个服务都可以独立部署,也可以独立于其他服务进行扩展。如果部署得当,基于微服务的架构可以帮助企业提高应用程序的部署效率。 | 采用单体架构的应用程序的每一次功能更改或 bug 修复都必须对整个应用程序重新进行部署。 |
开发模式 | 在采用微服务架构的应用程序中,不同模块可以使用不同的技术或语言进行开发,开发模式更加灵活。 | 在采用单体架构的应用程序中,所有模块使用的技术和语言必须相同,开发模式受限。 |
故障隔离 | 在微服务架构中,故障被隔离在单个服务中,避免系统的整体崩溃。 | 在单体架构中,当一个组件出现故障时,故障很可能会在进程中蔓延,导致系统全局不可用。 |
项目结构 | 微服务架构将单个应用程序拆分为多个独立的小型服务,每个服务都可以独立的开发、部署和维护,每个服务都能完成一项特定的业务需求。 | 单体架构的应用程序,所有的业务逻辑都集中在同一个工程中。 |
微服务特点:
1. 服务按照业务来划分,每个服务通常只专注于某一个特定的业务、所需代码量小,复杂度低、易于维护。
2. 每个微服都可以独立开发、部署和运行,且代码量较少,因此启动和运行速度较快。
3. 每个服务从设计、开发、测试到维护所需的团队规模小,一般 8 到 10 人,团队管理成本小。
4. 采用单体架构的应用程序只要有任何修改,就需要重新部署整个应用才能生效,而微服务则完美地解决了这一问题。在微服架构中,某个微服务修改后,只需要重新部署这个服务即可,而不需要重新部署整个应用程序。
5. 在微服务架构中,开发人员可以结合项目业务及团队的特点,合理地选择语言和工具进行开发和部署,不同的微服务可以使用不同的语言和工具。
6. 微服务具备良好的可扩展性。随着业务的不断增加,微服务的体积和代码量都会急剧膨胀,此时我们可以根据业务将微服务再次进行拆分;除此之外,当用户量和并发量的增加时,我们还可以将微服务集群化部署,从而增加系统的负载能力。
7. 微服务能够与容器(Docker)配合使用,实现快速迭代、快速构建、快速部署。
8. 微服务具有良好的故障隔离能力,当应用程序中的某个微服发生故障时,该故障会被隔离在当前服务中,而不会波及到其他微服务造成整个系统的瘫痪。
9. 微服务系统具有链路追踪的能力。
微服务框架
微服务架构是一种系统架构风格和思想,想要真正地搭建一套微服务系统,则需要微服务框架的支持。随着微服务的流行,很多编程语言都相继推出了它们的微服务框架。
1. Java微服务框架
1. SpringCloud:它能够基于 REST 服务来构建服务,帮助架构师构建出一套完整的微服务技术生态链。
2. Dropwizard:用于开发高性能和 Restful 的 Web 服务,对配置、应用程序指标、日志记录和操作工具都提供了开箱即用的支持。
3. Restlet: 该框架遵循 RST 架构风格,可以帮助 Java 开发人员构建微服务。
4. Spark:最好的 Java 微服务框架之一,该框架支持通过 Java 8 和 Kotlin 创建微服务架构的应用程序。
5. Dubbo:由阿里巴巴开源的分布式服务治理框架。
2. Go 语言微服务框
Go 语言中的微服务框架较少,使用的较多的是 GoMicro,它是一个 RPC 框架,具有负载均衡、服务发现、同步通信、异步通讯和消息编码等功能。
3. Phyton 微服务框架
Phyton 中的微服务框架主要有 Flask、Falcon、Bottle、Nameko 和 CherryPy 等。
4. NodeJS微服务框架
Molecular 是一种使用 NodeJS 构建的事件驱动架构,该框架内置了服务注册表、动态服务发现、负载均衡、容错功能和内置缓存等组件。
SpringCloud(一款基于SpringBoot实现的微服务框架)
SpringCloud源自Spring社区,主要由Pivotal、Netflix(美国的一个在线视频网站,微服务界的翘楚)两大公司提供技术迭代和维护。Netflix的开源组件已经在其大规模分布式微服务环境中经过了多年的生产实战验证,成熟且可靠。
针对不同场景的微服务架构解决方案:
1. 服务治理:阿里巴巴开源的Dubbo、当当网在其基础上扩展出来的DubboX、Netflix的Eureka、Apache的Consul等。
2. 分布式配置管理:百度的Disconf、Netflix的Archaius、360的QConf、携程的Apollo、SpringCloud的Config等。
3. 批量任务:当当网的Elastic-Job、LinkedIn的Azkaban、SpringCloud的Task等。
4. 服务跟踪:京东的Hydra、SpringCloud的Sleuth、Twitter的Zipkin等。
以上微服务框架(解决方案)都具有以下2个特点:
1. 对于同一个微服务问题,各互联网公司给出的解决方案各不相同。
2. 一个微服务框架或解决方案都只能解决微服务中的某一个或某几个问题,对于其他问题则无能为力。
这种情况下,搭建一套微分布式微服务系统,就需要针对这些问题从诸多的解决方案中做出选择,这使得我们不得不将大量的精力花费在前期的调研、分析以及实验上。
SpringCloud被称为构建分布式微服务系统的“全家桶”(分布式微服务架构的一站式解决方案),它并不是某一门技术,而是一系列微服务解决方案或框架的有序集合。它将市面上成熟的、经过验证的微服务框架整合起来,并通过SpringBoot的思想进行再封装,屏蔽调其中复杂的配置和实现原理,最终为开发人员提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。
SpringCloud中包含了spring-cloud-config、spring-cloud-bus、spring-cloud-gateway等近 20 个子项目(服务组件),提供了服务治理、服务网关、智能路由、负载均衡、断路器、监控跟踪、分布式消息队列、配置管理等领域的解决方案。
SpringCloud 并不是一个拿来即可用的框架,它是一种微服务规范,共有以下2代实现:
1. 第一代实现:Spring Cloud Netflix(主要包括:Eureka、Ribbon、Feign、Hystrix)
2. 第二代实现:Spring Cloud Alibaba(主要包括:Nacos、Sentinel、Seata)。
SpringCloud组件 | 描述 |
---|---|
Spring Cloud Netflix Eureka | 服务注册与发现组件。 |
Spring Cloud Netflix Ribbon | 服务调用与客户端负载均衡组件。 |
Spring Cloud Netflix Feign | 基于Ribbon和Hystrix的声明式服务调用与客户端负载均衡组件。 |
Spring Cloud Netflix Hystrix | 服务熔断与降级组件。 |
Spring Cloud Netflix Zuul | Spring Cloud Netflix 中的网关组件,提供了智能路由、访问过滤等功能。 |
Spring Cloud Gateway | 一个基于Spring 5.0、SpringBoot2.0、ProjectReactor等技术开发的网关框架(安全、监控/指标、限流),使用Filter链的方式。 |
Spring Cloud Config | 分布式配置组件,支持使用Git存储配置内容 实现应用配置的外部化存储,并支持在客户端对配置进行刷新、加密、解密等操作。 |
Spring Cloud Bus | SpringCloud的事件和消息总线,主要用于在集群中传播事件或状态变化,以触发后续的处理(如:动态刷新配置)。 |
Spring Cloud Stream | SpringCloud的消息中间件组件,它集成了 Apache Kafka 和 RabbitMQ 等消息中间件,并通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件之间的隔离。通过向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种不同的消息中间件实现,就能轻松地发送和接收消息。 |
Spring Cloud Sleuth | SpringCloud分布式链路跟踪组件,能够完美的整合Twitter的Zipkin。 |
SpringBoot和SpringCloud的区别与联系
1. 分工不同。
SpringBoot是一个基于Spring的快速开发框架。在微服务开发中,SpringBoot专注于快速、方便地开发单个微服务。
SpringCloud是微服务架构下的一站式解决方案。SpringCloud专注于全局微服务的协调和治理工作。换句话说,SpringCloud相当于微服务的大管家,负责将 Spring Boot 开发的一个个微服务管理起来,并为它们提供配置管理、服务发现、断路器、路由、微代理、事件总线、决策竞选以及分布式会话等服务。
2. SpringCloud是基于SpringBoot实现的。
SpringCloud也提供了一系列Starter(使用SpringBoot思想对各个微服务框架进行再封装的产物),它们屏蔽了这些微服务框架中复杂的配置和实现原理,使开发人员能够快速、方便地使用 Spring Cloud 搭建一套分布式微服务系统。
3. 依赖项数量不同。
SpringBoot属于一种轻量级的框架,构建SpringBoot工程所需的依赖较少。
SpringCloud是一系列微服务框架技术的集合体,它的每个组件都需要一个独立的依赖项(Starter POM),因此想要构建一套完整的SpringCloud工程往往需要大量的依赖项。
4. SpringCloud不能脱离SpringBoot单独运行。
SpringCloud 是基于SpringBoot 实现的,它不能独立创建工程或模块,更不能脱离SpringBoot独立运行。
虽然SpringBoot能够用于开发单个微服务,但它并不具备管理和协调微服务的能力,因此它只能算是一个微服务快速开发框架,而非微服务框架。
SpringCloud版本
SpringCloud包含了许多子项目(组件),这些子项目都是独立进行内容更新和迭代的,各自都维护着自己的发布版本号。为了避免SpringCloud的版本号与其子项目的版本号混淆,SpringCloud没有采用常见的数字版本号,而是通过{version.name}.{version.number}方式定义版本信息。
说明:
1. version.name(版本名)
采用英国伦敦地铁站的站名来命名,并按照字母表的顺序(即从 A 到 Z)来对应 Spring Cloud 的版本发布顺序,例如第一个版本为 Angel,第二个版本为 Brixton(英国地名),然后依次是 Camden、Dalston、Edgware、Finchley、Greenwich、Hoxton 等。
2. version.number(版本号)
每一个版本的 Spring Cloud 在更新内容积累到一定的量级或有重大 BUG 修复时,就会发布一个“service releases”版本,简称 SRX 版本,其中 X 为一个递增的数字,例如 Hoxton.SR8 就表示 Hoxton 的第 8 个 Release 版本。
SpringCloud版本选择(需要根据项目中SpringBoot版本来决定SpringCloud版本,否则会出现一些错误)
Spring Cloud | Spring Boot 版本对应关系 |
---|---|
2020.0.x (Ilford) | 2.4.x, 2.5.x (从 Spring Cloud 2020.0.3 开始) |
Hoxton | 2.2.x, 2.3.x (从 Spring Cloud SR5 开始) |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
Spring Cloud 官方已经停止对 Dalston、Edgware、Finchley 和 Greenwich 的版本更新。
获取 Spring Cloud 与 Spring Boot 的版本对应关系(JSON 版)
SpringCloudAlibaba(SpringCloud第二代实现,用来替代SpringCloudNetflix)
阿里巴巴结合自身丰富的微服务实践而推出的微服务开发一站式解决方案(基于SpringCloudNetflix的核心架构思想进行了高性能改进)。
SpringCloudAlibaba组件
1. Nacos(服务的注册与发现、配置的动态刷新)
2. Sentinel(流量控制)
3. Seata(分布式事务解决方案)
3. RocketMQ:Apache RocketMQ 是一款基于Java 的高性能、高吞吐量的分布式消息和流计算平台。
4. Dubbo:Apache Dubbo 是一款高性能的 Java RPC 框架。
5. Alibaba Cloud OSS:阿里云对象存储服务器(Object Storage Service,简称OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。
7. Alibaba Cloud Schedulerx:分布式调度产品,支持周期性的任务与固定时间点触发任务。
使用场景:
1. 大型复杂的系统(如:大型电商系统)。
2. 高并发系统(如:大型门户网站、商品秒杀系统)。
3. 需求不明确,且变更很快的系统(如:创业公司业务系统)。
SpringCloud两代实现组件的对比
Spring Cloud 第一代实现(Netflix) | 状态 | Spring Cloud 第二代实现(Alibaba) | 状态 |
---|---|---|---|
Ereka | 2.0 孵化失败 | Nacos Discovery | 性能更好,感知力更强 |
Ribbon | 停更进维 | Spring Cloud Loadbalancer | Spring Cloud 原生组件,用于代替 Ribbon |
Hystrix | 停更进维 | Sentinel | 可视化配置,上手简单 |
Zuul | 停更进维 | Spring Cloud Gateway | 性能为 Zuul 的 1.6 倍 |
Spring Cloud Config | 搭建过程复杂,约定过多,无可视化界面,上手难点大 | Nacos Config | 搭建过程简单,有可视化界面,配置管理更简单,容易上手 |
Spring Cloud、Spring Cloud Alibaba 以及 Spring Boot 之间版本依赖关系
Spring Cloud 版本 | Spring Cloud Alibaba 版本 | Spring Boot 版本 |
---|---|---|
Spring Cloud 2020.0.1 | 2021.1 | 2.4.2 |
Spring Cloud Hoxton.SR12 | 2.2.7.RELEASE | 2.3.12.RELEASE |
Spring Cloud Hoxton.SR9 | 2.2.6.RELEASE | 2.3.2.RELEASE |
Spring Cloud Greenwich.SR6 | 2.1.4.RELEASE | 2.1.13.RELEASE |
Spring Cloud Hoxton.SR3 | 2.2.1.RELEASE | 2.2.5.RELEASE |
Spring Cloud Hoxton.RELEASE | 2.2.0.RELEASE | 2.2.X.RELEASE |
Spring Cloud Greenwich | 2.1.2.RELEASE | 2.1.X.RELEASE |
Spring Cloud Finchley | 2.0.4.RELEASE(停止维护,建议升级) | 2.0.X.RELEASE |
Spring Cloud Edgware | 1.5.1.RELEASE(停止维护,建议升级) | 1.5.X.RELEASE |
SpringCloudAlibaba 组件版本关系
Spring Cloud Alibaba 版本 | Sentinel 版本 | Nacos 版本 | RocketMQ 版本 | Dubbo 版本 | Seata 版本 |
---|---|---|---|---|---|
2.2.7.RELEASE | 1.8.1 | 2.0.3 | 4.6.1 | 2.7.13 | 1.3.0 |
2.2.6.RELEASE | 1.8.1 | 1.4.2 | 4.4.0 | 2.7.8 | 1.3.0 |
2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE | 1.8.0 | 1.4.1 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE | 1.8.0 | 1.3.3 | 4.4.0 | 2.7.8 | 1.3.0 |
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE | 1.7.1 | 1.2.1 | 4.4.0 | 2.7.6 | 1.2.0 |
2.2.0.RELEASE | 1.7.1 | 1.1.4 | 4.4.0 | 2.7.4.1 | 1.0.0 |
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE | 1.7.0 | 1.1.4 | 4.4.0 | 2.7.3 | 0.9.0 |
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE | 1.6.3 | 1.1.1 | 4.4.0 | 2.7.3 | 0.7.1 |
1. SpringCloud Eureka(服务注册与发现组件)
SpringCloud将Netflix开发的开源服务组件(如:Eureka、Ribbon、Feign、Hystrix 等)一起整合进SpringCloudNetflix模块中。
Eureka是SpringCloudNetflix模块的子模块,它是SpringCloud对NetflixEureka的二次封装,主要负责SpringCloud的服务注册与发现功能。SpringCloud使用SpringBoot思想为Eureka增加了自动化配置,开发人员只需要引入相关依赖和注解,就能将SpringBoot构建的微服务轻松地与Eureka进行整合。
Eureka采用CS(Client/Server,客户端/服务器) 架构,它包括以下两大组件:
1. EurekaServer:Eureka服务注册中心,主要用于提供服务注册功能。当微服务启动时,会将自己的服务注册到EurekaServer。EurekaServer维护了一个可用服务列表(在EurekaServer的管理界面中可看到),存储了所有注册到EurekaServer的可用服务的信息。
2. EurekaClient:Eureka客户端,通常指的是微服务系统中各个微服务,主要用于和EurekaServer进行交互。在微服务应用启动后,EurekaClient会向EurekaServer发送心跳(默认周期为30秒)。若EurekaServer在多个心跳周期内没有接收到某个EurekaClient的心跳,则将它从可用服务列表中移除(默认90秒)。 “心跳”指的是一段定时发送的自定义信息,让对方知道自己“存活”,以确保连接的有效性。大部分 CS 架构的应用程序都采用了心跳机制,服务端和客户端都可以发心跳。通常情况下是客户端向服务器端发送心跳包,服务端用于判断客户端是否在线。
- Eureka 服务注册与发现
共涉及3个角色:
1. 服务注册中心(Register Service)EurekaServer(用于提供服务注册和发现功能)
服务提供者和服务消费者之间的桥梁。服务提供者只有将自己的服务注册到服务注册中心才可能被服务消费者调用,而服务消费者也只有通过服务注册中心获取可用服务列表后,才能调用所需的服务。
2. 服务提供者(Provider Service)EurekaClient(用于提供服务)。
将自己提供的服务注册到服务注册中心,以供服务消费者发现。
3. 服务消费者(Consumer Service)EurekaClient(用于消费服务)
从服务注册中心获取服务列表,调用所需的服务。
Eureka实现服务注册与发现的流程:
1. 搭建一个EurekaServer作为服务注册中心;
2. 服务提供者EurekaClient启动时,会把当前服务器的信息以服务名(spring.application.name)的方式注册到服务注册中心;
3. 服务消费者EurekaClient启动时,也会向服务注册中心注册;
4. 服务消费者还会获取一份可用服务列表,该列表中包含了所有注册到服务注册中心的服务信息(包括服务提供者和自身的信息);在获得了可用服务列表后,服务消费者通过HTTP或消息中间件远程调用服务提供者提供的服务。
示例(使用多模块的Maven项目来演示)
===》1. sql
create database test character set utf8 collate utf8_general_ci;
use Test;
create table users(
id int primary key,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
);
insert into users(id,name,password,email,birthday) values(1,'zhansan','123456','[email protected]','2220-12-04');
insert into users(id,name,password,email,birthday) values(2,'lisi','123456','[email protected]','2222-12-04');
insert into users(id,name,password,email,birthday) values(3,'wangwu','123456','[email protected]','2222-12-04');
===》2. spring-cloud-parent(主工程项目)
1. 创建MavenProject。
2. 修改pom.xml文件如下
4.0.0
com.sst.cx
spring-cloud-parent
0.0.1-SNAPSHOT
pom
spring-cloud-parent
http://maven.apache.org
org.springframework.boot
spring-boot-starter-parent
2.7.5
8
8
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
org.springframework.cloud
spring-cloud-dependencies
2021.0.5
pom
import
microservicecloud
src/main/resources
true
org.apache.maven.plugins
maven-resources-plugin
$
spring-cloud-api
spring-cloud-eureka-7001
spring-cloud-provider-user-8001
===》3. spring-cloud-api(公共子模块)
包含了一些其他子模块共有的内容(如:实体类、公共工具类、公共依赖项等)。
1. 创建Maven Moudle,选择父模块为spring-cloud-parent。
2. 修改pom.xml如下
4.0.0
spring-cloud-api
spring-cloud-api
com.sst.cx
spring-cloud-parent
0.0.1-SNAPSHOT
8
8
org.projectlombok
lombok
3. 创建User.java
package com.sst.cx.domain;
import java.io.Serializable;
import java.util.Date;
import lombok.*;
import lombok.experimental.Accessors;
@Getter
@Setter
@NoArgsConstructor // 提供:类的无参构造函数
@Accessors(chain = true)
public class User implements Serializable {
private static final long serialVersionUID = 1949383130841612064L;
private Integer id;
private String name;
private String password;
private String email;
private Date birthday;
}
===》4. spring-cloud-eureka-7001 (服务注册中心)
1. 创建Maven Moudle,选择父模块为spring-cloud-parent。
2. 修改pom.xml文件
4.0.0
com.sst.cx
spring-cloud-parent
0.0.1-SNAPSHOT
spring-cloud-eureka-7001
spring-cloud-eureka-7001
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
3. 创建application.yml配置文件(类路径下)
server:
port: 7001 #端口号
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己就是注册中心,职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版服务注册中心
4. 在主启动类上使用 @EnableEurekaServer 注解开启服务注册中心功能,接受其他服务的注册
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // 开启 Eureka server,接受其他微服务的注册
public class ServiceCloudEureka7001Application {
public static void main( String[] args ){
SpringApplication.run(ServiceCloudEureka7001Application.class, args);
}
}
5. 主启动类右键|Run JavaApplication启动spring-cloud-eureka-7001,在浏览器中输入http://localhost:7001/可访问Eureka服务注册中心主页。
===》spring-cloud-provider-user-8001 (服务提供者)
1. 创建Maven Moudle,选择父模块为spring-cloud-parent。
2. 修改pom.xml如下
4.0.0
com.sst.cx
spring-cloud-parent
0.0.1-SNAPSHOT
spring-cloud-provider-user-8001
spring-cloud-provider-user-8001
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.springframework.boot
spring-boot-starter-test
test
com.sst.cx
spring-cloud-api
${project.version}
junit
junit
mysql
mysql-connector-java
ch.qos.logback
logback-core
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.2.2
org.springframework
springloaded
1.2.8.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-actuator
org.mybatis.generator
mybatis-generator-maven-plugin
1.4.1
src/main/resources/mybatis-generator/generatorConfig.xml
true
true
mysql
mysql-connector-java
8.0.31
org.mybatis.generator
mybatis-generator-core
1.4.0
org.springframework.boot
spring-boot-maven-plugin
3. 创建application.yml配置文件(类路径下)
server:
port: 8001 #服务端口号
spring:
application:
name: springCloudProviderUser #对外暴露的微服务名称
jackson:
serialization:
FAIL_ON_EMPTY_BEANS: false
########## JDBC配置 ############
datasource:
username: root #数据库用户名
password: 12345678 #数据库密码
url: jdbc:mysql://127.0.0.1:3306/test #数据库URL
driver-class-name: com.mysql.cj.jdbc.Driver #数据库驱动
########## 不检查 spring.config.import=configserver: #########
# cloud:
# config:
# enabled: false
########## MyBatis配置 ############
mybatis:
#指定mapper.xml的位置
mapper-locations: classpath:mybatis/mapper/*.xml
#扫描实体类的位置,在此处指明扫描实体类的包路径,在mapper.xml中就可以不写实体类的全路径名
type-aliases-package: com.sst.cx.domain
configuration:
#默认开启驼峰命名法,可以不用设置该属性
map-underscore-to-camel-case: true
######### SpringCloud自定义服务名称和ip地址 #############
eureka:
client: #将客户端注册到eureka服务列表内
service-url:
defaultZone: http://localhost:7001/eureka #该地址为 7001注册中心在application.yml中暴露出来的注册地址 (单机版)
instance:
instance-id: spring-cloud-provider-8001 #自定义服务名称信息
prefer-ip-address: true #显示访问路径的ip地址
########### SpringCloud使用 SpringBootActuator 监控完善信息 ########
# SpringBoot2.50对 actuator监控屏蔽了大多数的节点,只暴露了heath节点,本段配置(*)就是为了开启所有的节点
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号
info:
app.name: spring-cloud-provider-user-8001
company.name: com.sst.cx
build.aetifactId: @project.artifactId@
build.version: @project.version@
4. 创建UserMapper.java(接口)
package com.sst.cx.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.sst.cx.domain.User;
@Mapper
public interface UserMapper {
// 根据主键获取数据
User selectByPrimaryKey(Integer userId);
// 获取表中的全部数据
List getAll();
}
5. 创建UserMapper.xml(在resources/mybatis/mapper/目录下)
id, name, password
6. 创建UserService.java(服务接口)
package com.sst.cx.service;
import java.util.List;
import com.sst.cx.domain.User;
public interface UserService {
User get(Integer userId);
List selectAll();
}
7. 创建UserServiceImpl.java(服务接口的实现类)
package com.sst.cx.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.sst.cx.domain.User;
import com.sst.cx.mapper.UserMapper;
import com.sst.cx.service.UserService;
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User get(Integer userId) {
return userMapper.selectByPrimaryKey(userId);
}
@Override
public List selectAll() {
return userMapper.getAll();
}
}
8. 创建UserController.java(Controller类)
package com.sst.cx.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import com.sst.cx.domain.User;
import com.sst.cx.service.UserService;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/user/get/{id}", method = RequestMethod.GET)
public User get(@PathVariable("id") int id) {
return userService.get(id);
}
@RequestMapping(value = "/user/list", method = RequestMethod.GET)
public List list() {
return userService.selectAll();
}
}
9. 在主启动类上,使用 @EnableEurekaClient 注解开启Eureka客户端功能,将服务注册到服务注册中心(EurekaServer)
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient // Spring cloud Eureka 客户端,自动将本服务注册到 Eureka Server 注册中心中
public class ServiceCloudProviderUser8001Application {
public static void main(String[] args) {
SpringApplication.run(ServiceCloudProviderUser8001Application.class, args);
}
}
10. 依次启动spring-cloud-eureka-7001、spring-cloud-provider-user-8001,使用浏览器访次问Eureka服务注册中心主页(http://localhost:7001/)
Instances currently registered with Eureka (注册到EurekaServer的服务实例)选项中已经包含了一条服务信息:
1. Application:SPRINGCLOUDPROVIDERUSER。
该取值为 spring-cloud-provider-dept-8001配置文件application.yml中spring.application.name的取值大写。
2. Status: UP (1) - spring-cloud-provider-8001。
UP表示服务在线, (1) 表示有集群中服务的数量,spring-cloud-provider-8001则是 spring-cloud-provider-user-8001配置文件application.yml中eureka.instance.instance-id 的取值。
11. 浏览器中输入http://localhost:8001/user/list
- EurekaServer集群
在微服务架构中,一个系统往往由十几甚至几十个服务组成,若将这些服务全部注册到同一个EurekaServer中,就极有可能导致EurekaServer因不堪重负而崩溃,最终导致整个系统瘫痪。解决这个问题最直接的办法就是部署EurekaServer集群。
在Eureka实现服务注册与发现时一共涉及3个角色:服务注册中心、服务提供者、服务消费者,这三个角色分工明确,各司其职。但是,其实在Eureka中所有服务都既是服务消费者也是服务提供者,服务注册中心EurekaServer也不例外(服务注册中心不能将自己注册到自己身上,但可以将自己作为服务向其他的服务注册中心注册自己)。
服务注册中心A注册到服务注册中心B,B注册到A,这样就形成了一组互相注册的EurekaServer集群。当服务提供者发送注册请求到EurekaServer时,EurekaServer会将请求转发给集群中所有与之相连的EurekaServer上,以实现EurekaServer之间的服务同步。
通过服务同步,服务消费者可以在集群中的任意一台EurekaServer上获取服务提供者提供的服务。这样,即使集群中的某个服务注册中心发生故障,服务消费者仍然可以从集群中的其他EurekaServer中获取服务信息并调用,而不会导致系统的整体瘫痪,这就是EurekaServer集群的高可用性。
示例
在上述示例的基础上,创建spring-cloud-eureka-7002、spring-cloud-eureka-7003,配置同上。
1. 修改spring-cloud-eureka-7001的appliication.yml文件如下:
server:
port: 7001 #端口号
eureka:
instance:
hostname: www.eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己就是注册中心,职责就是维护服务实例,并不需要去检索服务
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版服务注册中心
defaultZone: http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/
2. 修改spring-cloud-eureka-7002、spring-cloud-eureka-7003同理,修改spring-cloud-provider-user-8001的defaultZone为http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/
3. vim /etc/hosts
#SpringCloudEureka集群
127.0.0.1 www.eureka7001.com
127.0.0.1 www.eureka7002.com
127.0.0.1 www.eureka7003.com
4. 在浏览器输入http://www.eureka7001.com:7001/、http://www.eureka7002.com:7002/、http://www.eureka7003.com:7003/
- Eureka自我保护机制
在调试Eureka程序时,Eureka服务注册中心很有可能会出现红色警告:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
这个警告是因为触发了Eureka的自我保护机制而出现的。默认情况下,如果EurekaServer在一段时间内(默认为90秒)没有接收到某个服务提供者(Eureka Client)的心跳,就会将这个服务提供者提供的服务从服务注册表中移除。 这样服务消费者就再也无法从服务注册中心中获取到这个服务了,更无法调用该服务。
但在实际的分布式微服务系统中,健康的服务(Eureka Client)也有可能会由于网络故障(例如网络延迟、卡顿、拥挤等原因)而无法与EurekaServer正常通讯。若此时EurekaServer因为没有接收心跳而误将健康的服务从服务列表中移除,这显然是不合理的。而Eureka的自我保护机制就是来解决此问题的。
Eureka自我保护机制
是一种应对网络异常的安全保护措施。它的架构哲学是:宁可同时保留所有微服务(健康的服务和不健康的服务都会保留)也不盲目移除任何健康的服务。通过Eureka的自我保护机制,可以让EurekaServer集群更加的健壮、稳定。
如果EurekaServer在一段时间内没有接收到EurekaClient的心跳,那么EurekaServer就会开启自我保护模式,将所有的EurekaClient的注册信息保护起来,而不是直接从服务注册表中移除。一旦网络恢复,这些EurekaClient提供的服务还可以继续被服务消费者消费。
默认开启,如果想要关闭,则需要在配置文件中添加以下配置:
eureka:
server:
enable-self-preservation: false # 设为false关闭Eureka的自我保护机制,默认为true开启,不建议设为false。
弊端:
如果在Eureka自我保护机制触发期间,服务提供者提供的服务出现问题,那么服务消费者就很容易获取到已经不存在的服务进而出现调用失败的情况。可以通过客户端的容错机制来解决此问题。
2. SpringCloud Ribbon(客户端负载均衡与服务调用组件)
Ribbon是SpringCloudNetflix模块的子模块,它是SpringCloud对NetflixRibbon的二次封装。通过它,可以将面向服务的REST模板(RestTemplate)请求转换为客户端负载均衡的服务调用。
Ribbon是SpringCloud体系中最核心、最重要的组件之一。它虽然只是一个工具类型的框架,并不像服务注册中心那样需要独立部署,但它几乎存在于每一个使用SpringCloud构建的微服务中。SpringCloud微服务之间的调用、API网关的请求转发、OpenFeign等内容,实际上都是通过SpringCloudRibbon来实现的。
- 服务调用
Ribbon可以与RestTemplate(Rest模板)配合使用,以实现微服务之间的调用。
RestTemplate是Spring家族中的一个用于消费第三方REST服务的请求框架。RestTemplate实现了对 HTTP请求的封装,提供了一套模板化的服务调用方法。通过它,Spring应用可以很方便地对各种类型的HTTP请求进行访问。
RestTemplate针对各种类型的HTTP请求都提供了相应的方法进行处理,如:HEAD、GET、POST、PUT、DELETE等类型的HTTP请求,分别对应RestTemplate中的headForHeaders()、getForObject()、postForObject()、put()、delete()方法。
示例
以之前的示例作基础
===》1. spring-cloud-ribbon-user-80
1. 创建Maven Moudle,选择父模块为spring-cloud-parent。
2. 修改pom.xml如下
4.0.0
com.sst.cx
spring-cloud-parent
0.0.1-SNAPSHOT
spring-cloud-ribbon-user-80
com.sst.cx
spring-cloud-api
${project.version}
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
3. 创建application.yml文件(类路径下)
server:
port: 80 #端口号
eureka:
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: true #本微服务为服务消费者,需要到服务注册中心搜索服务
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/
4. 创建ConfigBean.java配置类,将 RestTemplate 注入到容器中。
package com.sst.cx.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
// 配置类注解
@Configuration
public class ConfigBean {
@Bean // 将RestTemplate注入到容器中
@LoadBalanced // 在客户端使用RestTemplate请求服务端时,开启负载均衡(Ribbon)
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
5. 创建UserController_Consumer.java控制器类(定义请求,用于调用服务端提供的服务)
package com.sst.cx.controller;
import com.sst.cx.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class UserController_Consumer {
// 方式(直接调用服务方法,没用到SpringCloud)
// private static final String REST_URL_PROVIDER_PREFIX =
// "http://localhost:8001/";
// 方式(面向微服务编程,即通过微服务的名称来获取调用地址)
// 使用注册到 Spring Cloud Eureka 服务注册中心中的服务,即 application.name
private static final String REST_URL_PROVIDER_PREFIX = "http://SPRINGCLOUDPROVIDERUSER";
// RestTemplate 是一种简单便捷的访问 restful 服务模板类,是 Spring 提供的用于访问 Rest
// 服务的客户端模板工具集,提供了多种便捷访问远程 HTTP 服务的方法
@Autowired
private RestTemplate restTemplate;
// 获取指定用户信息
@RequestMapping(value = "/consumer/user/get/{id}")
public User get(@PathVariable("id") Integer id) {
return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + "/user/get/" + id, User.class);
}
// 获取用户列表
@RequestMapping(value = "/consumer/user/list")
public List list() {
return restTemplate.getForObject(REST_URL_PROVIDER_PREFIX + "/user/list", List.class);
}
}
6. 创建SpringCloudConsumerUser80Application.java主启动类
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class SpringCloudConsumerUser80Application {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConsumerUser80Application.class, args);
}
}
7. 依次启动服务注册中心spring-cloud-eureka-7001、服务提供者spring-cloud-provider-user-8001、服务消费者spring-cloud-ribbon-user-80。
在浏览器中访问http://www.eureka7001.com:80/consumer/user/list。
- 负载均衡(Load Balance)
负载均衡
将用户的请求平摊分配到多个服务器上运行,以达到扩展服务器带宽、增强数据处理能力、增加吞吐量、提高网络的可用性和灵活性的目的。在任何一个系统中,负载均衡都是一个十分重要且不得不去实施的内容,它是系统处理高并发、缓解网络压力和服务端扩容的重要手段之一。
常见的负载均衡方式:
1. 服务端负载均衡(最常见)
在客户端和服务端之间建立一个独立的负载均衡服务器,该服务器既可以是硬件设备(如:F5),也可以是软件(如:Nginx)。这个负载均衡服务器维护了一份可用服务端清单,然后通过心跳机制来删除故障的服务端节点,以保证清单中的所有服务节点都是可以正常访问的。
当客户端发送请求时,该请求不会直接发送到服务端进行处理,而是全部交给负载均衡服务器,由负载均衡服务器按照某种算法(如:轮询、随机等),从其维护的可用服务清单中选择一个服务端,然后进行转发。
特点:
1. 需要建立一个独立的负载均衡服务器。
2. 负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。
3. 可用服务端清单存储在负载均衡服务器上。
2. 客户端负载均衡
将负载均衡逻辑以代码的形式封装到客户端上(即负载均衡器位于客户端)。客户端通过服务注册中心获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的;
客户端负载均衡也需要心跳机制去维护服务端清单的有效性,这个过程需要配合服务注册中心一起完成。
特点:
1. 负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器。
2. 负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务。
3. 客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的。
Ribbon就是一个基于HTTP和TCP的客户端负载均衡器,当我们将Ribbon和Eureka一起使用时,Ribbon会从服务注册中心中获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的。
区别 | 服务端负载均衡 | 客户端负载均衡 |
---|---|---|
是否需要建立负载均衡服务器 | 需要在客户端和服务端之间建立一个独立的负载均衡服务器。 | 将负载均衡的逻辑以代码的形式封装到客户端上,因此不需要单独建立负载均衡服务器。 |
是否需要服务注册中心 | 不需要服务注册中心。 | 需要服务注册中心。在客户端负载均衡中,所有的客户端和服务端都需要将其提供的服务注册到服务注册中心上。 |
可用服务清单存储的位置 | 可用服务清单存储在位于客户端与服务器之间的负载均衡服务器上。 | 所有的客户端都维护了一份可用服务清单,这些清单都是从服务注册中心获取的。 |
负载均衡的时机 | 先将请求发送到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在多个服务端之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。简单点说就是,先发送请求,再进行负载均衡。 | 在发送请求前,由位于客户端的服务负载均衡器(例如 Ribbon)通过负载均衡算法选择一个服务器,然后进行访问。简单点说就是,先进行负载均衡,再发送请求。 |
客户端是否了解服务提供方信息 | 由于负载均衡是在客户端发送请求后进行的,因此客户端并不知道到底是哪个服务端提供的服务。 | 负载均衡是在客户端发送请求前进行的,因此客户端清楚的知道是哪个服务端提供的服务。 |
Ribbon实现负载均衡
Ribbon是一个客户端的负载均衡器,与Eureka配合使用可轻松地实现客户端的负载均衡。Ribbon会先从EurekaServer(服务注册中心)去获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务端,从而达到负载均衡的目的。
SpringCloudRibbon提供了一个IRule接口(用来定义负载均衡策略),有7个默认实现类(每一个实现类对应一种负载均衡策略)。
根据自身需求切换负载均衡策略(默认使用轮询策略选取服务实例):在服务消费者的配置类中,将IRule的其他实现类注入到容器中即可。
定制负载均衡策略
通常情况下,Ribbon提供的默认负载均衡策略是可以满足需求的,如果有特殊的要求,还可以根据自身需求定制负载均衡策略。
IRule接口的7个实现类 | 负载均衡策略 |
---|---|
RoundRobinRule | 线性轮询策略(按照一定的顺序依次选取服务实例) |
RandomRule | 随机选取一个服务实例 |
RetryRule | 按照RoundRobinRule轮询策略来获取服务,如果获取的服务实例为null或已经失效,则在指定的时间之内(默认的时限为500毫秒)不断地进行轮询重试,如果超过指定时间依然没获取到服务实例则返回null 。 |
WeightedResponseTimeRule | WeightedResponseTimeRule是RoundRobinRule的一个子类(对RoundRobinRule的功能进行了扩展)。根据平均响应时间,来计算所有服务实例的权重,响应时间越短的服务实例权重越高,被选中的概率越大。刚启动时,如果统计信息不足,则使用线性轮询策略,等信息足够时再切换到WeightedResponseTimeRule。 |
BestAvailableRule | 继承自ClientConfigEnabledRoundRobinRule。先过滤掉故障或失效的服务实例,然后再选择并发量最小(连接的消费者数量最少)的服务实例。 |
AvailabilityFilteringRule | 先过滤掉故障或失效的服务实例,然后再选择并发量较小的服务实例。 |
ZoneAvoidanceRule | 默认的负载均衡策略,综合判断服务所在区域的性能和服务的可用性,来选择服务实例。在没有区域的环境下,该策略与轮询策略类似。 |
示例(查看默认的负载均衡策略)
1. 准备数据,创建数据库Test2、Test3,创建表
2. 参照spring-cloud-provider-user-8001创建spring-cloud-provider-user-8002、spring-cloud-provider-user-8003,对端口号、连接的数据库名作相应修改。
3. 依次启动spring-cloud-eureka-7001/7002/7003(服务注册中心集群)、spring-cloud-provider-user-8001/8002/8003(服务提供者集群)、spring-cloud-ribbon-user-80(服务消费者)
在浏览器中访问http://www.eureka7001.com:80/consumer/user/list,点击刷新按钮连续请求查看结果。
【存疑:结果非轮询,手速不够快?】
示例(切换负载均衡策略)
===》client3.0以前
1. 在spring-cloud-ribbon-user-80项目的ConfigBean配置类中,添加
@Bean
public IRule myRule() {
// RandomRule 为随机策略
return new RandomRule();
}
2. 重启spring-cloud-ribbon-user-80(服务消费者)
在浏览器中访问http://www.eureka7001.com:80/consumer/user/list,点击刷新按钮连续请求查看结果。
===》client3.0以后(删除了ribbon依赖,使用自带的LoadBalancer。只有:轮询---默认、随机)
1. 在spring-cloud-ribbon-user-80项目中,创建LoadBalancerConfig配置类(不能加@configuration注解,否则异常)
public class LoadBalancerConfig { // 切换为随机
@Bean
public ReactorLoadBalancer randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory){
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);
}
}
2. 给ConfigBean添加@LoadBalancerClient注解
@LoadBalancerClient(name = "springCloudProviderUser",configuration = {LoadBalancerConfig.class})
3. 重启spring-cloud-ribbon-user-80(服务消费者)
在浏览器中访问http://www.eureka7001.com:80/consumer/user/list,点击刷新按钮连续请求查看结果。
【存疑:结果未发生变化】
示例(定制负载均衡策略)
===》client3.0以前
1. 创建MyRandomRule.java(在com.sst.myrule包路径下)
package com.sst.cx.myrule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
// 自定义Ribbon负载均衡策略
public class MyRandomRule extends AbstractLoadBalancerRule {
private int total = 0; // 总共被调用的次数,目前要求每台被调用5次
private int currentIndex = 0; // 当前提供服务的机器号
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
// 获取所有有效的服务实例列表
List upList = lb.getReachableServers();
// 获取所有的服务实例的列表
List allList = lb.getAllServers();
// 如果没有任何的服务实例则返回 null
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
//与随机策略相似,但每个服务实例只有在调用 3 次之后,才会调用其他的服务实例
if (total < 3) {
server = upList.get(currentIndex);
total++;
} else {
total = 0;
currentIndex++;
if (currentIndex >= upList.size()) {
currentIndex = 0;
}
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
2. 创建MySelfRibbonRuleConfig.java配置类(在com.sst.myrule包路径下)
将定制的负载均衡策略实现类注入到容器中。
package com.sst.myrule;
import com.netflix.loadbalancer.IRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 自定义Ribbon负载均衡策略的配置类(不能在com.sst.cx包及其子包下,否则所有的Ribbon客户端都会采用该策略,无法达到特殊化定制的目的)
@Configuration
public class MySelfRibbonRuleConfig {
@Bean
public IRule myRule() {
// 自定义Ribbon负载均衡策略
return new MyRandomRule(); // 自定义,随机选择某一个微服务,执行五次
}
}
3. 修改ServiceCloudConsumer80Application.java主启动类
package com.sst.cx;
import com.sst.cx.myrule.MySelfRibbonRuleConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableEurekaClient
// 自定义Ribbon负载均衡策略,在主启动类上使用RibbonClient注解,在该微服务启动时,就能自动去加载自定义的Ribbon配置类。name为需要定制负载均衡策略的微服务名称(application name),configuration 为定制的负载均衡策略的配置类,且官方文档中明确提出,该配置类不能在 ComponentScan 注解(SpringBootApplication 注解中包含了该注解)下的包或其子包中,即自定义负载均衡配置类不能在 com.sst.cx 包及其子包下。
@RibbonClient(name = "SPRINGCLOUDPROVIDERUSER", configuration = MySelfRibbonRuleConfig.class)
public class ServiceCloudConsumer80Application {
public static void main(String[] args) {
SpringApplication.run(ServiceCloudConsumer80Application.class, args);
}
}
4. 启动spring-cloud-ribbon-user-80
在浏览器中访问http://www.eureka7001.com/consumer/user/list,点击刷新按钮一直请求。
===》client3.0以后
1. 创建PeachLoadBalancer.java
package com.sst.cx.config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.loadbalancer.core.*;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
public class PeachLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
final AtomicInteger position;// 请求的次数
final String serviceId; // 服务名称 用于提示报错信息的
private int flag = 0; // 自己定义的计数器
// 两个参数的构造方法 需要服务名称和实例提供者 这个在方法中传递进来
public PeachLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider,
String serviceId) {
// 如果不传入请求次数就自己初始化 反正每次都+1
this(new Random().nextInt(1000), serviceId,serviceInstanceListSupplierProvider);
}
public PeachLoadBalancer(int seedPosition, String serviceId, ObjectProvider serviceInstanceListSupplierProvider) {
this.position = new AtomicInteger(seedPosition);
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
}
ObjectProvider serviceInstanceListSupplierProvider;
@Override
public Mono> choose(Request request) {
// 从服务提供者中获取到当前request请求中的serviceInstances并且遍历
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response processInstanceResponse(ServiceInstanceListSupplier supplier,
List serviceInstances) {
Response serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response getInstanceResponse(List instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// pos是当前请求的次数 这样可以自定义负载均衡的切换 这个每次+1的操作是复制的 最好是不删
int pos = Math.abs(this.position.incrementAndGet());
if (pos%4==0){
// 是4的倍数就切换
flag += 1;
}
if (flag >= instances.size()){
flag = 0;
}
// 主要的就是这句代码设置负载均衡切换
ServiceInstance instance = instances.get(flag);
return new DefaultResponse(instance);
}
}
2. 在LoadBalancerConfig中返回自定义的均衡策略
return new PeachLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
3. 启动spring-cloud-ribbon-user-80
在浏览器中访问http://www.eureka7001.com/consumer/user/list,点击刷新按钮一直请求。
【存疑:结果未发生变化】
3. SpringCloud OpenFeign(负载均衡和服务调用组件)
Feign
Feign对Ribbon进行了集成,利用Ribbon维护了一份可用服务清单,并通过Ribbon实现了客户端的负载均衡。
Feign是一种声明式服务调用组件,它在RestTemplate的基础上做了进一步的封装。通过 Feign,我们只需要声明一个接口并通过注解进行简单的配置(类似于Dao接口上面的 Mapper注解一样)即可实现对HTTP接口的绑定。通过Feign,我们可以像调用本地方法一样来调用远程服务,而完全感觉不到这是在进行远程调用。
Feign支持多种注解:Feign自带的注解、JAX-RS注解等,不支持SpringMVC注解。
2019 年 Netflix 公司宣布 Feign 组件正式进入停更维护状态,于是 Spring 官方便推出了一个名为 OpenFeign 的组件作为 Feign 的替代方案。
OpenFeign(全称:Spring Cloud OpenFeign)
Spring官方推出的一种声明式服务调用与负载均衡组件(为了替代停更的Feign)。
1. OpenFeign是SpringCloud对Feign的二次封装,在Feign的基础上增加了对SpringMVC注解的支持。
使用OpenFegin进行远程服务调用的注解:
1. @FeignClient注解
用于通知OpenFeign组件对@RequestMapping注解下的接口进行解析,并通过动态代理的方式产生实现类,实现负载均衡和服务调用。
2. @EnableFeignClients注解
用于开启OpenFeign功能,当SpringCloud应用启动时,OpenFeign会扫描标有@FeignClient注解的接口,生成代理并注册到Spring容器中。
3. @RequestMapping(SpringMVC注解)
使用该注解映射请求,来指定Controller控制器可以处理哪些URL请求,相当于Servlet的web.xml配置。
4. @GetMapping(SpringMVC注解)
用来映射GET请求。相当于@RequestMapping(method = RequestMethod.GET) 。
5. @PostMapping(SpringMVC注解)
用来映射POST请求。相当于@RequestMapping(method = RequestMethod.POST) 。
2. OpenFeign超时控制
OpenFeign客户端的默认超时时间为1秒钟,如果服务端处理请求的时间超过1秒就会报错,需要对OpenFeign客户端的超时时间进行控制。
由于 OpenFeign 集成了 Ribbon ,其服务调用以及负载均衡在底层都是依靠 Ribbon 实现的,因此 OpenFeign 超时控制也是通过 Ribbon 来实现的。
3. OpenFeign日志增强
OpenFeign提供了日志打印功能,可以通过配置调整日志级别,来了解请求的细节。
Feign为每一个 FeignClient 都提供了一个 feign.Logger 实例,通过它可以对 OpenFeign 服务绑定接口的调用情况进行监控。
Logger.Level级别:
1. NONE:不记录任何信息。
2. BASIC:仅记录请求方法、URL 以及响应状态码和执行时间。
3. HEADERS:除了记录 BASIC 级别的信息外,还会记录请求和响应的头信息。
4. FULL:记录所有请求与响应的明细(头信息、请求体、元数据等)。
Feign和OpenFegin的区别
相同点:
1. Feign和OpenFeign都是SpringCloud下的远程服务调用和负载均衡组件。
2. Feign和OpenFeign都对Ribbon进行了集成,都利用Ribbon维护了可用服务清单,并通过Ribbon实现了客户端的负载均衡。
3. Feign和OpenFeign都是在服务消费者(客户端)定义服务绑定接口并通过注解的方式进行配置,以实现远程服务的调用。
不同点:
1. Feign和OpenFeign的依赖项不同,Feign的依赖为 spring-cloud-starter-feign,而 OpenFeign的依赖为spring-cloud-starter-openfeign。
2. Feign和OpenFeign支持的注解不同,Feign支持Feign注解、JAX-RS注解,但不支持 SpringMVC注解;OpenFeign支持Feign注解、JAX-RS注解、SpringMVC注解。
示例
以之前的例子为基础,在主项目中创建spring-cloud-feign-user-80子项目
1. 修改pom.xml文件如下
com.sst.cx
spring-cloud-api
${project.version}
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
2. 创建application.yml文件(在类路径resources目录下)
server:
port: 80 #端口号
eureka:
client:
register-with-eureka: false #本微服务为服务消费者,不需要将自己注册到服务注册中心.
fetch-registry: true #本微服务为服务消费者,需要到服务注册中心搜索服务.
service-url:
defaultZone: http://www.eureka7001.com:7001/eureka/,http://www.eureka7002.com:7002/eureka/,http://www.eureka7003.com:7003/eureka/ #服务注册中心集群
3. 创建UserFeignService.java文件(在com.sst.cx.service包路径下)
package com.sst.cx.service;
import com.sst.cx.domain.*;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
@Component
@FeignClient(value = "SPRINGCLOUDPROVIDERUSER") // 服务提供者提供的服务名称,即application.name
public interface UserFeignService {
// 对应服务提供者(8001、8002、8003)Controller中定义的方法
@RequestMapping(value = "/user/get/{id}", method = RequestMethod.GET)
public User get(@PathVariable("id") int id);
@RequestMapping(value = "/user/list", method = RequestMethod.GET)
public List list();
}
4. 创建UserController_Consumer.java文件(在com.sst.cx.controller包路径下)
package com.sst.cx.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.sst.cx.domain.User;
import com.sst.cx.service.UserFeignService;
import javax.annotation.Resource;
import java.util.List;
@RestController
public class UserController_Consumer {
@Resource
private UserFeignService userFeignService;
@RequestMapping(value = "/consumer/user/get/{id}")
public User get(@PathVariable("id") Integer id) {
return userFeignService.get(id);
}
@RequestMapping(value = "/consumer/user/list")
public List list() {
return userFeignService.list();
}
}
5. 创建SpringCloudConsumerUserFeignApplication.java主启动类
package com.sst.cx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients // 开启OpenFeign功能
public class SpringCloudConsumerUserFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConsumerUserFeignApplication.class, args);
}
}
6. 依次启动spring-cloud-eureka-7001/7002/7003(服务注册中心集群)、spring-cloud-provider-user-8001/8002/8003(服务提供者集群)、spring-cloud-feign-user-80(服务消费者)
在浏览器中访问http://www.eureka7001.com:80/consumer/user/list,点击刷新按钮连续请求查看结果【存疑:是固定的,不是轮询,是因为手速不够快吗】。
示例(测试超时控制)
1. 在所有服务提供者的Controller中添加一个响应时间为5秒的服务:
// 超时测试
@RequestMapping(value = "/user/feign/timeout")
public String DeptFeignTimeout() {
// 暂停5秒
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
2. 在spring-cloud-feign-user-80的UserFeignService中添加
@RequestMapping(value = "/user/feign/timeout")
public String DeptFeignTimeout();
3. 在spring-cloud-feign-user-80的UserController_Consumer中添加
@RequestMapping(value = "/consumer/user/feign/timeout")
public String DeptFeignTimeout() {
// openFeign-ribbon 客户端一般默认等待一秒钟,超过该时间就会报错
return userFeignService.DeptFeignTimeout();
}
4. 重启所有服务提供者、spring-cloud-feign-user-80,
在浏览器中访问http://www.eureka7001.com:80/consumer/user/feign/timeout,此时页面中会提示Error【存疑:成功访问到了,未提示Error,客户端超时时间不是1s?】。
5. 在spring-cloud-feign-user-80的application.yml中添加(设置超时时间为6秒)
ribbon:
ReadTimeout: 6000 #建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ConnectionTimeout: 6000 #建立连接后,服务器读取到可用资源的时间
6. 重启spring-cloud-feign-user-80
在浏览器中访问http://www.eureka7001.com:80/consumer/user/feign/timeout
示例(开启日志)
1. 在application.yml配置文件中添加
logging:
level:
#feign以什么日志级别监控包路径下的@FeignClient注解标注的接口(即服务绑定接口),
com.sst.cx.service.UserFeignService: debug
2. 创建ConfigBean.java文件
package com.sst.cx.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConfigBean {
// OpenFeign日志
@Bean
Logger.Level feginLoggerLevel() {
return Logger.Level.FULL;
}
}
3. 在浏览器中访问http://www.eureka7001.com/consumer/user/list,控制台就会输出相应级别的日志信息。如:
...
2018-11-15 10:50:20.310 DEBUG 1121 --- [p-nio-80-exec-1] com.sst.cx.service.UserFeignService : [UserFeignService#list] keep-alive: timeout=60
...