SpringCloudNetflix入门
一、应用架构的演变
- 伴随互联网的发展,使用互联网的人群越来越多,软件应用的体量越来越大和复杂。
- 而传统单体应用 可能不足以支撑大数据量以及发哦并发场景
- 应用的框架也随之进行演变
- 从最开始的单体应用架构到分布式(SOA)架构到 今天比较火的微服务框架,以及微服务网格架构。
单体–>分布式–>SOA–>微服务–>…
1、单体应用
1.1 什么是单体应用
- 传统的应用就是单体架构,即所有的模块、组件等都在一个应用中,最终打包(war,jar),使用一个容器(如Tomcat)进行部署,且通常一个应用享用一个数据库。
- 将单体应用分为三个组成部分:持久层、业务层、表现层。
- 单体应用在初期:业务少没有问题
- 单体应用需求不断增多,也伴随着业务逻辑、业务组件等日益扩张,应用将会变得越来越臃肿往后的开发和维护就会变得特别麻烦,
- 再着越来越大访的问量,并发越来越高,面对海量的用户无论从应用性能还是从数据库方面都有吃不消的时候。
- 所以,单体应用在数据量、并发量到一定程度时一定会遇到瓶颈。
1.2 单体框架的优缺点
1.2.1 单体项目缺点
- 代码臃肿不方便开发维护(代码可读性差)
- 代码编译系统启动变慢
- 系统扩展性能变差(牵一发而动全身)
- 无法针对某一个业务做扩展(集群)
- 对大数据量,高并发量的处理不占优势
- 技术选型单一
- 模块/业务耦合度高
1.2.2 单体项目的优点
- 易于开发 :架构简单,技术成本低
- 易于测试 :所有功能在一个项目,方便测试
- 易于部署 :一个Tomcat就可以实现部署,简单方便
1.3 单体应用于集群
- 问题引入:
- 在单体架构中,为提升应用的并发 和 防止单节点故障(一个Tomcat挂,应用就挂了),通常就会做集群。
1.3.1 什么是集群
- 集群:多个单体项目合成一个集群
- 这里的集群指:复制多个相同应用共同工作来提高作业能力,多个应用做相同的事。
1.3.2 单体应用于集群带来的问题
- 当应用做集群,就会存在多个应用节点,这些节点将会暴露多个访问地址(ip:port),那么客户端是不知道该访问那个应用节点的
- 为解决客户端不知道访问那个应用节点,就需要有一个请求分发的功能的组件(负载均衡器)将将客户端的请求相对平均的分发多个应用节点上,这就是负载均衡,而这个做请求分发的组件就是负载均衡器。
-
这里的Nginx就是一个负载均衡器,它可按某种算法(轮询、ip_hash等)清请求路由分发到不同的后端服务器上,同类型负载均衡器有 HAproxy、LVS等。
-
负载均衡算法如:轮询、随机、权重。
-
每个功能模块使用并发数量不同,有些模块使用特别多,有些模块使用的量少
- 解决思路:将重复的功能提取成模块,可根据需求量做集群,这些模块组合成一个完整的项目。
- 上图描述
- 每个模块就是一个项目
- 每个项目都可以单独的做技术选型、部署、运维、维护
- 其实该图就是一个简单的微服务了
1.4 单体项目的举例
- 前端分离
2.分布式于SOA
2.1 分布式架构
2.2 面向服务的架构SOA
3. 微服务架构
3.1 什么是微服务
- 微服务架构可以认识为 是一种SOA架构上的一种发展,最早由“Martin Fowler”提出:
就目前而言,对于微服务业界并没有一个统一的、标准的定义(While there is no precise definition of this architectural style ) 。但通常而言,`微服务架构是一种架构模式或者说是一种架构风格,它提倡将单一应用程序划分成一组小的服务,每个服务运行独立的自己的进程中,服务之间互相协调、互相配合,为用户提供最终价值。服务之间采用轻量级的通信机制互相沟通(通常是基于 HTTP 的 RESTful API )` 。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等。
另外,应尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建,可以有一个非常轻量级的集中式管理来协调这些服务。可以使用不同的语言来编写服务,也可以使用不同的数据存储。
-
从“Martin Fowler”提出观点来看,微服务架构 和 SOA 架构很像。总体来说,微服务:就是把单体应用进行进行细粒度的拆分,分成多个小(微)的服务,每个服务独立运行,每个服务只需要专注一个业务即可,并且每个服务都可以有自己的数据库(分库),服务之间互协调配合完成整个系统的业务。
-
简单的来说,微服务:就是将一个单体项目根据需求,拆分为多个模块,每个模块就是一个项目,每个项目都可以单独的做
技术选型、部署、运维、维护。再根据每个模块的并发量做集群。
-
微服务机构图
-
微服务做集群
-
微服务特点
- 由多个服务组成完整的系统
- 每个服务都是独立的,有自己的进程
- 服务之间使用HTTP协议通信
- 不同的服务可以使用不同的编程语言
- 不同的服务的数据库可以多样化选择
- 微服务是一个分布式系统
-
微服务拆分做集群带来的问题
- 模块之间需要通讯
- 对于集群服务,需要负载均衡
- 事务 怎么解决
- session怎么处理,识别当前的用户问题
-
解决问题的办法
- SpringCloud
- SpringCloud Alibaba
- 它们是解决微服务拆分带来的一系列问题
3.2 微服务的优缺点
- 优点
- 单个服务业务简单,代码简单方便开发维护
- 服务之间无耦合,服务之间升级维护互不影响
- 轻量级HTTP通信机制,使得的不同的服务可以采用不同的编程语言
- 微服务有极强的扩展能力,业务量大的服务可以再次拆分服务,也可以进行集群部署,剔除服务也很方便
- 更大的系统负载能力和容错能力(集群)
- 对于开发人员来说,通常只需要关注单一服务,新员工上手也比较快
- 微服务架构对现在流行的敏捷开发支持优化
- 缺点
- 分布式事务 :服务通信机制增加了事务的复杂性,架构师要选择合适的分布式方案(CAP理论)
- 部署麻烦 :微服务众多,部署麻烦,需要借助容器技术和自动化部署工具,这又增加了开发人员的学习成本。
- 技术成本高 :微服务架构本身比较复杂,技术成本高,开发人员需要花更多的时间学习相关技术。
- 服务通信对性能的损耗 : 微服务架构一定要考虑服务通信延迟对服务调用性能的损耗问题,开发人员需要选择合适的通信方式解决这一问题。
3.3 如何选型
- 大型项目选择微服务,如电商,物流,售票等系统我们可以选择使用微服务架构
- 中小型项目,选择单体
二、SpringCloud 介绍
1、认识SpringCloud
- SpringCloud 是一个基于SpringBoot实现的服务治理工具包,用于微服务架构中 管理和协调服务的。
- Spring Cloud是一系列框架的有序集合。
- SpringCloud 利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
- SpringCloud 通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
- SpringCloud让微服务架构的落地变得更简单。
2、SpringCloud常用组件
- 采用微服务后就会引发一些列的难题需要去解决,如
- 众多微服务的通信地址应该如何管理
- 微服务之间应该使用何种方式发起调用
- 微服务故障该如何处理
- 众多微服务的配置文件如何集中管理等
- SpringCloud为这一系列的难题提供了相应的组件来解决,如下图SpringCloud最核心
-
Netfli Eureka(注册中心)
- Eureka 是用来管理微服务的通信地址清单,有了Eureka后,就可通过服务名字 实现 服务的调用。
-
Netflix Ribbon/Feign (远程调用,客户端负载均衡)
- Ribbon 与 Feign 都是客服端负载均衡器。
- 作用:服务发生调用时,将请求按某种规则分发到多个目标服务器上。
- 简单理解激素是解决微服务之间的通信问题。
-
Netflix Hystrix(断路器/熔断器)
- 有时一个请求需要多个微服务共同完成,那么一旦某个服务发生故障导致整个调用链上的微服务全都出现异常,甚至导致整个微服务架构瘫痪。
- 作用:Hystrix就是解决微服务故障,保护服务安全的组件。
-
Netflix Zuul(服务网关)
- zuul作为服务网关,可将它看作是微服务的大门,所有的请求都需要经过zuul之后才能到达目标服务。
- 据zuul特性,可将微服务公共的事情交个zuul统一处理。如:用户鉴权、请求监控。
-
Spring Cloud Config (分布式配置/配置中心)
- 微服务架构中的服务实例非常的多,而服务的配置文件分散在每个服务中,每次修改服务的配置文件和重新服务实例都是一个很麻烦的工作。
- Spring Cloud Config作为分布式配置管理中心就是用来统一的管理服务的配置文件。
-
Spring Cloud Bus(消息总线)
- 消息总线是在微服务中给各个微服务广播消息的一个组件,我们使用消息总线构建一个消息中心,其他微服务来接入到消息中心,当消息总线发起消息,接入的微服务都可以收到消息从而进行消费。
-
Spring Cloud sleuth(微服务链路追踪)
- 应用采用微服务架构之后,后台可能有几十个甚至几百个服务在支撑,一个请求请求可能需要多次的服务调用最后才能完成。
- 作用:就是来监控维护之间的调用关系,让程序员方便直观的感受到一个请求经历了哪些微服务,以及服务的请求时间,是否有异常等。
3、SpringCloud的版本
- SpringCloud是基于SpringBoot的,所以两者的jar包都需要导入。
- 注意:SprinbCloud的版本需要和SpringBoot的版本兼容。
- 我的项目
- SpringBoot版本是2.0.5.RELEASE
- SpringCloud的版本是Finchley.SR1
Release Train | SpringBoot Version |
---|---|
Hoxton | 2.2.x |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
4、服务通信协议
(1)RPC
- RPC(Remote Produce Call)远程过程调用,类似的还有RMI。
- 自定义数据格式,基于原生TCP通信,速度快,效率高。
- 早期的webservice、现在dubbo,都是RPC的典型
(2)Http
- Http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。
- 现在客户端浏览器与服务端通信基本都是采用Http协议。
- 也可用来进行远程服务调用。
- 缺点是消息封装臃肿。
- Rest风格,就可以通过http协议来实现
三、 SpringCloud与Dubbo
1、Dubbo简介
- Dubbo最早是阿里巴巴提供的一个服务治理和服务调用框架,现在已经成为Apache的顶级项目。
四、服务注册与发现
1、什么是Eureka
- 帮助管理服务的通信地址。
2、Eureka的工作原理
(1)服务注册
- Eureka 是一个 服务 注册与发现的组件,就是用来统一管理微服务的通信地址的组件,它包含 EurekaServer服务器(注册中心)和 EurekaClient客户端组成。
- EurekaServer是独立的服务,而 EurekaClient需要集成到每个微服务中。
- 微服务(EurekaClient)在启动时,会向EurekaServer提交自己的服务信息(如:服务名,ip,端口等),在在 EurekaServer会形成一个微服务的通信地址列表存储起来。----这个过程服务注册
(2)服务发现
- 微服务(EurekaClient)会定期(RegistryFetchIntervalSeconds:默认30s)的从EurekaServer拉取一份微服务通信地址列表缓存到本地。
- 当一个微服务在向另一个微服务发起调用的时候会根据目标服务的服务名找到其通信地址,然后基于HTTP协议向目标服务发起请求。—这叫服务发现
(3)服务续约
- 微服务(EurekaClient)采用定时(LeaseRenewalIntervalInSeconds:默认30s)发送“心跳”请求向EurekaServer发请求进行服务续约,其实就是定时向 EurekaServer发请求报告自己的健康状况,告诉EurekaServer自己还活着,不要把自己从服务地址清单中剔除掉,那么当微服务(EurekaClient)宕机未向EurekaServer续约,或者续约请求超时,注册中心机会从服务地址清单中剔除该续约失败的服务。
(4)服务下线
- 微服务(EurekaClient)关闭服务前向注册中心发送下线请求,注册中心(EurekaServer)接受到下线请求负责将该服务实例从注册列表剔除。
(5)Eureka工作流程
3、EurekaServer实例
(1)多模块项目结构
- 所有的jar包交给父工程管理
- 现在主要用到
spring-cloud-parent //父项目--Project
pom.xml //父项目的pom
service-eureka-server-11000 //注册中心EurekaServer
service-user-server-31000 //用户服务EurekaClient ,提供者--model
service-order-server-21000 //订单服务EurekaClient ,消费者--model
(2)Spring-cloud-parent(父项目)管理依赖,在pom.xml文件中
- 参考文档:https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi__quick_start.html#_client_side_usage
- springcloud-parent父工程:负责管理SpringBoot和SpringCloud的jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lfg</groupId>
<artifactId>spring-cloud-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>service-eureka-11000</module>
<module>service-order-21000</module>
<module>service-user-31000</module>
</modules>
<!--公共的一些配置-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<!--1.管理 SpringBoot的jar包-->
<!--SpringBoot-->
<parent>
<groupId>org.springframework.boot</groupId>
<version>2.2.5.RELEASE</version>
<artifactId>spring-boot-starter-parent</artifactId>
</parent>
<!--2.管理 SpringCloud的jar包
-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
(3)搭建EurasiaServer----service-eureka-server-11000
- 参考文档:https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi_spring-cloud-eureka-server.html#netflix-eureka-server-starter
- 在springcloud-parent父工程下面搭建springcloud-eureka-server-11000,然后集成EurekaServer
a.service-eureka-server-11000项目结构
b.在pom.xml文件,引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!--集成自己的父工程-->
<parent>
<artifactId>spring-cloud-parent</artifactId>
<groupId>com.lfg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-eureka-11000</artifactId>
<dependencies>
<!--导入springboot 基础包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入EurekaServer包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
-
提示
-
spring-cloud-starter-netflix-eureka-server
- EurekaServer端的基础依赖,同时也把EurekaClient端也导入进来了
-
spring-boot-starter-web
- web服务的基础依赖是不可缺少的
-
c.配置启动类
package cn.lfg2000;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* 注册中心启动类
* @EnableEurekaServer : 开启EurekaServer服务端
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApp {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApp.class);
}
}
d.配置application.yml配置文件
server:
port: 11000 #端口号
eureka:
instance:
hostname: localhost #主机名
client: #客户端配置
register-with-eureka: false #false:EurekaServer自己不要注册到EurekaServer自己 ,只有EurekaClient才注册
fetch-registry: false #EurekaServer不要拉取服务的通信地址列表 ,只有EurekaClient才拉取地址列表
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server: #注册中心的注册地址
enable-self-preservation: false #关闭自我保护警告
e.启动测试
- 启动springcloud-eureka-server-11000,浏览器访问http://localhost:11000,若出现下面界面Eureka注册成功
4、Eureka自我保护
- 默认情况下,当EurekaServer接收到服务续约的心跳失败比例在15分钟之内低于85%,EurekaServer会把这些服务保护起来,即不会把该服务从服务注册地址清单中剔除掉,但是在此种情况下有可能会出现服务下线,那么消费者就会拿到一个无效的服务,请求会失败,那我们需要对消费者服务做一些重试,或在熔断策略。
- 当EurekaServer开启自我保护时,监控主界面会出现红色警告信息,我们可以使用
eureka.server.enable-self-preservation=false
来关闭EurekaServer的保护机制,这样可以确保注册中心中不可用的实例被及时的剔除,但是不推荐
5、EurekaClient实例
- 参考文档: https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi__service_discovery_eureka_clients.html#netflix-eureka-client-starter
- service-user-server-31000 与 service-order-server-21000 两个都是EurekaClient客户端,都需要去集成EurekaClient,。
(1)service-user-server-31000–用户服务
a.service-user-server-31000 项目结构
b.在pom.xml文件,引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-parent</artifactId>
<groupId>com.lfg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-user-31000</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
- 同步:顺序执行
- 异步:不用等待上一步的执行完,才执行下一步;可以先执行下一步,再执行上一步。
c.配置启动类
package cn.lfg2000;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* 用户的启动类
* @EnableEurekaClient: 标记该应用是 EurekaClient(客户端)
*/
@SpringBootApplication
@EnableEurekaClient //或者@EnableDiscoveryClient
public class UserApp {
public static void main(String[] args) {
SpringApplication.run(UserApp.class);
}
}
d.配置 application.yml配置文件
#端口号
server:
port: 31001
#指定服务的名字
spring:
application:
name: service-user
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:11000/eureka/
instance: #使用ip进行注册
instance-id: service-user:31001 #实例ID
prefer-ip-address: true #开启,使用指定ip进行注册
e.启动测试
(2)service-order-server-21000–订单服务
- 操作流程与用户服务一致。
- 启动类 和 配置文件需要修改
a.启动类
package cn.lfg2000;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class);
}
}
b.application.xml
server:
port: 21000
spring:
application:
name: service-order
eureka:
client:
service-url:
defaultZone: http://localhost:11000/eureka
instance:
prefer-ip-address: true #启动ip注册 默认使用计算机的名字注册
instance-id: service-order:21000
五、RestTemplate 服务通信
1、服务通信流程,以订单服务通信用户服务为例
- 调用关系:浏览器—>订单服务—>用户服务
- 订单服务通过RestTemplate向用户服务发起调用,目的是要获取到用户服务返回的User对象,最终是需要浏览器获取到User。
2、RestTemplate介绍
- 微服务的通信协议主流的有RPC,Http,SpringCloud是基于Http Restful 风格 ,在Java中发起一个Http请求的方式很多,比如 Apache的HttpClient , OKHttp等等 。
- Spring为我们封装了一个基于Restful的使用非常简单的Http客户端工具 RestTemplate ,我们就用它来实订单服务和用户服务的通信。
- 需要注意的是,RestTmplate本身不具备服务发现和负载均衡器的功能,案例只是演示在订单服务中使用RestTemplate基于ip和端口的方式向用户服务发起调用,即:不走注册中心,不使用服务发现方式。
3、编码实现RestTemplate
(1)搭建公共模块–service-entity
- 该模块用户存放实体类
a.service-entity 项目结构
b.在pom.xml文件中引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-parent</artifactId>
<groupId>com.lfg</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-entity</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
c.创建实体类
- User
package cn.lfg2000.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
private Long id;
private String username;
}
- Order
package cn.lfg2000.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Order {
private Long id;
private String desc;
private Double price;
private User user;
}
d.在用户模块、订单模块引入server-entity模块
<dependency>
<groupId>com.lfg</groupId>
<artifactId>service-entity</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
e.在用户模块的controller层编写执行方法
package cn.lfg2000.controller;
import cn.lfg2000.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
//用户服务:暴露接口给订单访问
@RestController
public class UserController {
// 将用户服务的端口号注入port中
@Value("${server.port}")
private Integer port;
//订单服务来调用这个方法 http://localhost:31001/user/1
@GetMapping("/user/{id}")
public User getUserById(@PathVariable("id") long id){
// 模拟一个user对象
return new User(id,"一个人哭"+port);
}
}
f.在订单模块的controller层中编写执行方法并获取User对象
- 编写配置类RestTemplate
package cn.lfg2000.config;
import com.netflix.loadbalancer.RandomRule;
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 RestTemplateConfig {
//配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
@Bean
/*@LoadBalanced*/
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
/*@Bean
public RandomRule getRandomRule(){
return new RandomRule();
}*/
}
- 订单模块controller层执行方法
package cn.lfg2000.controller;
import cn.lfg2000.pojo.Order;
import cn.lfg2000.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/{id}")
public Order getOrder(@PathVariable("id") long id){
String url = "http://localhost:31001/user/"+id;
User user = restTemplate.getForObject(url, User.class);
return new Order(id,"一刀999",99.99,user);
}
}
g.测试
六、Ribbon客户端负载均衡
1、为什么要Ribbon
- 为了防止应用出现单节点故障问题,同时为了提高应用的作业能力,我们需要对应用做集群 ,如果我们对user-server(用户服务)做了集群 ,那么这个时候回衍生出一些问题:现在有两个user-server(用户服务)就意味着有两个user-server(用户服务)的通信地址,我的order-server(订单服务)在向user-server(用户服务)发起调用的时候该访问哪个?如何访问?这个时候就需要有一个组件帮我们做请求的分发,即:负载均衡器,而Ribbon就是一个 - 客户端负载均衡器。
2、什么是Ribbon
- Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供
客户端负载均衡算法
。 - Ribbon客户端组件提供一系列完善的配置项,如,连接
超时,重试
等。 - 简单来说,Ribbon是一个客户端负载均衡器,Ribbon可以
按照负载均衡算法(如简单轮询,随机连接等)向多个服务发起调用
(正好可以解决上面的问题),我们也很容易使用Ribbon实现自定义的负载均衡算法
。
3、Ribbon的工作机制
- 如下图,我们将用户服务)做集群处理,增加到2个节点(注意:两个用户服务的服务名要一样,ip和端口不一样),在注册中心的服务通信地址清单中user-server(用户服务)这个服务下面会挂载两个通信地址 。
- 真实操作就是,修改application.yml配置文件中的端口号,再运行启动类就好了
- 订单服务 会定时拉取通信地址清单到本地进行缓存,当订单服务想用户服务发起调用时,需要指定服务名为 service-user,这时Ribbon根据service-user这个服务名找到两个service-user通信地址,然后根据Ribbon会按照负载均衡算法(morning轮询)选择其中的某个通信地址,发起和http请求实现服务的调用。
4、提供者service-user(用户服务)集群
(1)服务集群方案
- 使用SpringBoot多环境配置方式集群,一个配置文件配置多个service-userr环境
- 需要注意的是集群中的多个服务名(spring.application.name)应该一样,我们把相同的东西提取到最上面,不同的东西配置在各自的环境中
(2)用户服务集群配置
- 真实操作就是,修改application.yml配置文件中的端口号,再运行启动类就好了
- 第一个service-user
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:11000/eureka/
instance: #使用ip进行注册
instance-id: service-user:31001
prefer-ip-address: true #开启,使用指定ip进行注册
#端口号
server:
port: 31001
#指定服务的名字
spring:
application:
name: service-user
- 第二个service-user,修改application.yml ,并启动
#注册到EurekaServer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:11000/eureka/
instance: #使用ip进行注册
instance-id: service-user:31000 #实例ID
prefer-ip-address: true #开启,使用指定ip进行注册
#端口号
server:
port: 31000
#指定服务的名字
spring:
application:
name: service-user
5、消费者service-order集成Ribbon
- Ribbon 集成官方文档:https://cloud.spring.io/spring-cloud-static/Greenwich.SR5/multi/multi_spring-cloud-ribbon.html#netflix-ribbon-starter
(1)在service-order服务的pom.xml中引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
(2)开启负载均衡
- @LoadBalanced:开启负载均衡的能力
- 修改RestTemplate的Bean的定义方法
package cn.lfg2000.config;
import com.netflix.loadbalancer.RandomRule;
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 RestTemplateConfig {
//配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
/*@Bean
public RandomRule getRandomRule(){
return new RandomRule();
}*/
}
(3)修改service-order服务controller层的执行方法
package cn.lfg2000.controller;
import cn.lfg2000.pojo.Order;
import cn.lfg2000.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/{id}")
public Order getOrder(@PathVariable("id") long id){
String url = "http://service-user/user/"+id;
User user = restTemplate.getForObject(url, User.class);
return new Order(id,"一刀999",99.99,user);
}
}
6、测试
七、Ribbon负载均衡算法
1、Ribbon内置算法
2、配置负载均衡算法
(1)注解全局配置
/**
* 订单的启动类
*/
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApplication1030
{
//配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具
//@LoadBalanced :让RestTemplate有负载均衡的功能
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
//负载均衡算法
@Bean
public RandomRule randomRule(){
return new RandomRule();
}
//省略...
(3)yml方式配置负载均衡算法
a.配置全局Ribbon算法
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
b.配置某个服务的Ribbon算法
service-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
八、Ribbon调优配置
1、超时配置
-
使用Ribbon进行服务通信时为了防止网络波动造成服务调用超时,我们可以针对Ribbon配置超时时间以及重试机制
-
也可以针对具体的服务进行超时配置:如"<服务名>.ribbon…"
ribbon:
ReadTimeout: 3000 #读取超时时间
ConnectTimeout: 3000 #链接超时时间
MaxAutoRetries: 1 #重试机制:同一台实例最大重试次数
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数
OkToRetryOnAllOperations: false #是否所有操作都重试,因为针对post请求如果没做幂等处理可能会造成数据多次添加/修改
2、饥饿加载
- 我们在启动服务使用Ribbon发起服务调用的时候往往会出现找不到目标服务的情况,这是因为Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以往往我们在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况,即在服务启动时就把Ribbon相关内容创建好。
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: user-server #针对于哪些服务需要饥饿加载
``
(3)yml方式配置负载均衡算法
a.配置全局Ribbon算法
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
b.配置某个服务的Ribbon算法
service-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
八、Ribbon调优配置
1、超时配置
-
使用Ribbon进行服务通信时为了防止网络波动造成服务调用超时,我们可以针对Ribbon配置超时时间以及重试机制
-
也可以针对具体的服务进行超时配置:如"<服务名>.ribbon…"
ribbon:
ReadTimeout: 3000 #读取超时时间
ConnectTimeout: 3000 #链接超时时间
MaxAutoRetries: 1 #重试机制:同一台实例最大重试次数
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数
OkToRetryOnAllOperations: false #是否所有操作都重试,因为针对post请求如果没做幂等处理可能会造成数据多次添加/修改
2、饥饿加载
- 我们在启动服务使用Ribbon发起服务调用的时候往往会出现找不到目标服务的情况,这是因为Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以往往我们在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况,即在服务启动时就把Ribbon相关内容创建好。
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: user-server #针对于哪些服务需要饥饿加载