一. 架构演变:
1. 单体应用架构:
1.1单体架构:就是单体的架构,即所有的模块,组件等都在一个应用中应用最终打成一个(war,jar)包使用一个容器(Tomcat)进行部署,通常一个应用享用一个数据库。
1.2. 单体架构的优缺点
优点:
-
易于开发 :架构简单,技术成本低
-
易于测试 :所有功能在一个项目,方便测试
-
易于部署 :一个Tomcat就可以实现部署,简单方便
缺点:
-
代码臃肿不方便开发维护(代码可读性差)
-
代码编译系统启动变慢
-
系统扩展性能变差(牵一发而动全身)
-
无法针对某一个业务做扩展(集群)
-
对大数据量,高并发量的处理不占优势
-
技术选型单一
-
模块/业务耦合度高
1.3.单体应用与集群
单体架构中,为了提升应用的并发作业能力和防止应用的单节点故障(一个Tomcat挂了,应用就挂了), 我们通常会对应用做集群。
集群:把单体应用进行复制为多个相同的应用一起工作来提高作业能力,让多个应用做的是相同的事情。(就如同我们生活中的场景“收银”,一个收银台不足以支撑超市大量客户的结账流程,所以需要多个收银台同时工作,多个收银台都是做的相同的收银工作 )
集群的作用:处理高并发,高可用。
集群的缺点:
-
项目本身任然是单体,代码依然臃肿
-
面对海量数据数据库会成为性能瓶颈(需要分库分表)
-
持续交互能力差,代码臃肿,业务复杂,开发维护时间长,新人入手成本高。
负载均衡(Nginx ):可以按照某种算法(轮询,ip_hash等等)将请求路由到不同的后端服务器上。
负载均衡算法如:轮询,随机,权重。
2. 分布式与SOA
2.1.分布式架构
分布式就是将应用按照业务进行拆分成多个子应用,多个子应用部署在不同的服务器中,多个子应用组成一个完整的系统,所有的子系统一起工作相互通信相互协调才能完成最终的业务流程,缺一不可,简单理解:“多个人在一起做不同的事情,多人和在一起才是一件完整的事情,有点0.5+0.5=1的感觉”。
2.2.面向服务的架构SOA
SOA是面向服务的架构,它的思想是每个子应用可以通过网络通信协议向其他子应用提供服务或者消费服务,SOA也是分布式架构
SOA架构中有重要的两个角色,服务消费者(Consumer)和服务提供者(Provider)即服务调用者和服务被调用者,这样的架构优点有:
-
模块拆分,使用API通信,降低模块之间的耦合度
-
项目拆分多个子应用,每个子应用业务简单,代码简单,方便维护开发。
-
不同技术人员可以负责不同的子应用
-
提高服务之间的重用性,业务逻辑可组合。
其缺点在于:
-
服务之间的API接口开发增加了工作量,
-
SOA服务之间的网络通信调用对性能有一定的影响(尽管很小)
-
相对于单体应用来说,技术,人力成本较高。
-
部署和运维相对麻烦
3. 微服务架构
3.1. 什么是微服务
微服务:就是把单一应用进行细粒度的拆分成多个小(微)的服务相,每个服务独立运行,每个服务只需要专注一个业务即可,并且每个服务都可以有自己的数据库(分库),服务之间互协调配合完成整个系统的业务
微服务有如下的特点 :
解决高并发,
解决代码臃肿,
单个服务业务简单,
扩展性强,
对敏捷开发支持友好。
微服务的缺点:
运维成本高,
硬件成本高,
技术成本高 ,
服务调用网络开销 ,
事务处理麻烦。
二.SpringCloud介绍
1.1.SpringCloud基本概念
Spring cloud是一个基于Spring Boot实现的服务治理工具包
,用于微服务架构中管理和协调服务的
。
Spring Cloud是一系列框架的有序集合。
基础设施有:服务发现注册()、配置中心、消息总线、负载均衡、断路器、数据监控等。
1.2SpringCloud常用组件
Netflix Eureka
当我们的微服务过多的时候,管理服务的通信地址是一个非常麻烦的事情,Eureka就是用来管理微服务的通信地址清单的,有了Eureka之后我们通过服务的名字就能实现服务的调用。
Netflix Ribbon\Feign : 客户端负载均衡
Ribbon和Feign都是客户端负载均衡器,它的作用是在服务发生调用的时候帮我们将请求按照某种规则分发到多个目标服务器上,简单理解就是用来解决微服务之间的通信问题。
Netflix Hystrix :断路器
微服务的调用是非常复杂的,有的时候一个请求需要很多的微服务共同完成,那么一旦某个服务发生故障,导致整个调用链上的微服务全都出现异常,甚至导致整个微服务架构瘫痪。Hystrix就是用来解决微服务故障,保护微服务安全的组件。
Netflix Zuul : 服务网关
zuul作为服务网关,我们可以把它看作是微服务的大门,所有的请求都需要经过zuul之后才能到达目标服务,根据这一特性,我们可以把微服务公共的是事情交给zuul统一处理,如:用户鉴权,请求监控等。
Spring Cloud Config :分布式配置
微服务架构中的服务实例非常的多,服务的配置文件分散在每个服务中,每次修改服务的配置文件和重新服务实例都是一个很麻烦的工作,Spring Cloud Config作为分布式配置管理中心就是用来统一的管理服务的配置文件。
Spring Cloud Bus : 消息总线
消息总线是在微服务中给各个微服务广播消息的一个组件,我们使用消息总线构建一个消息中心,其他微服务来接入到消息中心,当消息总线发起消息,接入的微服务都可以收到消息从而进行消费。
Spring Cloud sleuth :微服务链路追踪
当我们的应用采用微服务架构之后,后台可能有几十个甚至几百个服务在支撑,一个请求请求可能需要多次的服务调用最后才能完成,链路追踪的作用就是来监控维护之间的调用关系,让程序员方便直观的感受到一个请求经历了哪些微服务,以及服务的请求时间,是否有异常等。
2 服务通信协议介绍
2.1 RPC
RPC(Remote Produce Call)远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型
2.2.Http
Http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。 现在热门的Rest风格,就可以通过http协议来实现
三. 服务注册与发现
1. Eureka介绍
1.1. 什么是Eureka
Eureka 是Netflix公司提供的服务注册与发现
组件。
微服务的其中一个特点是服务之间需要进行网络通信,服务器之间发起调用时调用服务得知道被调用服务的通信地址,试问当微服务数量成百上千之多,程序员该如何管理众多的服务通信地址,对于随时新增加的微服务和下线的微服务,又应该如何去动态添加和删除这些微服务的通信地址呢?所以手工管理服务的通信地址是一件遥不可及的事情,我们需要借助一个强大的工具帮我们实现这一功能 - Eureka,同类型的组件还有 zookeeper,consul等
1.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)接受到下线请求负责将该服务实例从注册列表剔除
2. EurekaServer实战
文章采用“用户”,“订单”,“支付”等业务来演示整个SpringCloud的各个组件
2.1.多模块项目结构
1.搭建项目结构
欲善其事,比利其器,为了方便演示SpringCloud的各个组件,我这里采用多模块的方式搭建项目,所有的jar包交给父工程来管理,搭建好的项目结构如下:
springcloud-parent //父项目 pom.xml //父项目的pom springcloud-eureka-server-1010 //注册中心EurekaServer springcloud-user-server-1020 //用户服务EurekaClient ,提供者 springcloud-order-server-1030 //订单服务EurekaClient ,消费者
啥?不懂SpringBoot?找篇文章学一学!!! , 啥?多模块项目不会搭建?找篇文章学一学!!!
2.父项目管理依赖
SpringCloud的Jar包管理参照文档:4. Quick Start
springcloud-parent父工程负责管理SpringBoot和SpringCloud的jar包 ,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"> <modelVersion>4.0.0</modelVersion> <groupId>cn.itsource.springboot</groupId> <artifactId>springcloud-parent</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>springcloud-eureka-server-1010</module> <module>springcloud-user-server-1020</module> <module>springcloud-order-server-1030</module> </modules> <packaging>pom</packaging> <!--公共的一些配置--> <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> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> </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> <!--3.这里是所有子项目都可以用的jar包--> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project>
2.2. 搭建Eureka Server
参照文档:12. Service Discovery: Eureka Server
在springcloud-parent父工程下面搭建好子工程springcloud-eureka-server-1010,然后我们来集成EurekaServer。
1.导入依赖
<?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>springcloud-parent</artifactId> <groupId>cn.itsource.springboot</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-eureka-server-1010</artifactId> <name>springcloud-eureka-server-1010</name> <dependencies> <!--spring-cloud-starter-netflix-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
提示:spring-cloud-starter-netflix-eureka-server作为EurekaServer端的基础依赖,但同时这个包也把EurekaClient端的以来也导入进来了,spring-boot-starter-web作为web服务的基础依赖是不可缺少的。
2.主配置类
/ * 注册中心启动类 * @EnableEurekaServer : 开启EurekaServer服务端 */ @SpringBootApplication @EnableEurekaServer public class EurekaServerApplication1010 { public static void main( String[] args ) { SpringApplication.run(EurekaServerApplication1010.class); } }
提示:在主配置类上通过 @EnableEurekaServer 注解开启了EurekaServer端的功能。
3.application.yml配置文件
server: port: 1010 #端口 eureka: instance: hostname: localhost #主机 client: #客户端配置 registerWithEureka: false #EurekaServer自己不要注册到EurekaServer自己 ,只有EurekaClient才注册 fetchRegistry: false #EurekaServer不要拉取服务的通信地址列表 ,只有EurekaClient才拉取地址列表 serviceUrl: #注册中心的注册地址 defaultZone: http://localhost:1010/eureka/ #http://${eureka.instance.hostname}:${server.port}/eureka/ server: enable-self-preservation: false #关闭自我保护警告
提示:这里配置了EurekaServer的端口为 1010 ,主机名 localhost ,需要特别说明的是我们再引入EurekaServer的基础依赖spring-cloud-starter-netflix-eureka-server时,这个依赖即引入了EurekaServer所需要的包,也引入了EurekaClient的包,换句话说,现在的springcloud-eureka-server-1010工程既是一个 EurekaServer,也是一个EurekaClient。
我们这里暂时把EurekaClient的功能屏蔽掉 ,即关闭它的服务注册和发现功能,让他做好EurekaServer该做的事情即可。
-
serviceUrl是服务注册地址,EurekaClient需要注册到EurekaServer就得跟上该地址。
-
registerWithEureka=false :禁止自己向自己注册
-
fetchRegistry=false : 禁止拉取服务注册列表
4.启动测试
启动springcloud-eureka-server-1010工程,浏览器访问 http://localhost:1010 ,出现如下界面代码EurekaServer集成成功:
2.3.Eureka自我保护
默认情况下,当EurekaServer接收到服务续约的心跳失败比例在15分钟之内低于85%,EurekaServer会把这些服务保护起来,即不会把该服务从服务注册地址清单中剔除掉,但是在此种情况下有可能会出现服务下线,那么消费者就会拿到一个无效的服务,请求会失败,那我们需要对消费者服务做一些重试,或在熔断策略。
当EurekaServer开启自我保护时,监控主界面会出现红色警告信息,我们可以使用eureka.server.enable-self-preservation=false
来关闭EurekaServer的保护机制,这样可以确保注册中心中不可用的实例被及时的剔除,但是不推荐
关闭Eureka自我保护后的配置如下:
server: port: 1010 #端口 eureka: instance: hostname: localhost #主机 client: #客户端配置 registerWithEureka: false #EurekaServer自己不要注册到EurekaServer自己 fetchRegistry: false #不要拉取服务的通信地址列表 serviceUrl: #注册中心的注册地址 defaultZone: http://localhost:1010/eureka/ #http://${eureka.instance.hostname}:${server.port}/eureka/ server: enable-self-preservation: false #关闭自我保护
3.EurekaClient实战-用户服务
参照官方文档 : 11. Service Discovery: Eureka Clients根据上一章节我们的Eureka的理解,Eureka分为服务端和客户端,服务端已经搭建成功,我们来搭建客户端。再来看一下我们之前的那张图:
其实我们的用户服务 springcloud-user-server-1020 ,订单服务springcloud-order-server-1030 两个工程都是EurekaClient客户端,都需要去集成EurekaClient,我们先从springcloud-user-server-1020 下手 。
3.1.导入依赖
修改springcloud-user-server-1020,导入EurekaClient基础依赖:spring-cloud-starter-netflix-eureka-client,导入web的基础依赖:spring-boot-starter-web,具体的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>springcloud-parent</artifactId> <groupId>cn.itsource.springboot</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-user-server-1020</artifactId> <name>springcloud-user-server-1020</name> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
提示:不要忘记导入web的基础依赖 spring-boot-starter-web
3.2.主配置类
我们可以在主配置通过注解@EnableEurekaClient标记服务作为Eureka客户端
/ * 用户的启动类 * @EnableEurekaClient: 标记该应用是 Eureka客户端 */ @SpringBootApplication @EnableEurekaClient public class UserServerApplication1020 { public static void main( String[] args ) { SpringApplication.run(UserServerApplication1020.class); } }
提示:主配置类通过打@EnableEurekaClient注解开启EurekaClient客户端功能,当然如果不打这个标签也能实现功能,因为导入spring-cloud-starter-netflix-eureka-client 依赖后,默认就开启了EurekaClient
3.3.application.yml配置
在配置文件中,我们需要通过eureka.client.serviceUrl配置EurekaServer的地址,EurekaClient根据该地址把自己注册给服务端。
#注册到EurekaServer eureka: client: serviceUrl: defaultZone: http://localhost:1010/eureka/ spring: application: name: user-server server: port: 1020
提示:serviceUrl是EurekaServer注册中的地址,主机和端口都应该指向springcloud-eureka-server-1010工程,这里额外指定了服务的名字,和端口,这些信息都会被注册到EurekaServer
3.4.测试EurekaClient
启动springcloud-eureka-server-1010 , 启动springcloud-user-server-1020 , 浏览器再次访问http://localhost:1010,那你应该可以看到我们的user-server服务已经被注册到EurekaServer。如下:
3.5.使用IP进行注册
默认情况下EurekaClient使用hostname进行注册到EurekaServer,我们希望使用ip进行注册,可以通过配置eureka.instance.prefer-ip-address=true
来指定,同时为了方便区分和管理服务实例,我们指定服务的实例ID,通过eureka.instance.instance-id为user-serer:1020
来指定,具体配置如下:
#注册到EurekaServer eureka: client: serviceUrl: defaultZone: http://localhost:1010/eureka/ instance: prefer-ip-address: true #使用ip地址进行注册 instance-id: user-server:1020 #实例ID spring: application: name: user-server server: port: 1020
重启springcloud-user-server-1020 工程,访问注册中心,可以看到实例ID已经发生改变,如下:
4.Eureka Client实战-订单服务
订单服务和用户服务的做法一样,只是yml配置文件中有些稍微的不同。
4.1.导入依赖
同用户服务一样,省略...
...省略部分不重要的内容... <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
4.2.主配置类
同用户服务一样,省略...
/ * 订单的启动类 * @EnableEurekaClient: 标记该应用是 Eureka客户端 */ @SpringBootApplication @EnableEurekaClient public class OrderServerApplication1030 { public static void main( String[] args ) { SpringApplication.run(OrderServerApplication1030.class); } }
4.3.application.yml配置
订单服务除了服务名,端口和用户服务不一样,其他的都一样,如下:
eureka: client: serviceUrl: defaultZone: http://localhost:1010/eureka/ instance: prefer-ip-address: true #使用ip地址进行注册 instance-id: order-server:1030 #实例ID spring: application: name: order-server server: port: 1030
4.4.测试
启动订单服务,访问Eureka Server的监控界面:http://localhost:1010,可以看到订单服务也注册进去了。
4.5.做个小结
到这里Eureka服务注册与发现案例已经完成,服务端和客户端的搭建都相对简单,一般都是导个包,打个标签,配置文件都搞定了,作为一个合格的程序员,我们不能光停留在用的层面,它的一些重要工作方式与思想也是需要我们去掌握。
四.RestTemplate服务通信
1.如何实现服务通信
1.1.流程说明
上一章节我们对Eureka Server做了高可用集群搭建,这一章节我们来实现简单版的服务通信。
目前除了Eureka Server以外我们的微服务有订单服务springcloud-order-server-1030,和用户服务springcloud-user-server-1020 , 我们就用这两个服务来演示微服务的通信,他们的调用关系应该是:浏览器 -> 订单服务 -> 用户服务,如下图:
这里订单服务通过RestTemplate向用户服务发起调用,目的是要获取到用户服务返回的User对象,最终是需要浏览器获取到User。
1.2.解决方案
用户服务需要提供User对象,我们需要为其编写Controller接口,编写相关方法返回User,订单服务需要从用户服务获取到User对象,而浏览器需要访问订单服务获取到User对象,所以订单服务也需要编写Controller接口供浏览器来调用。
我们发现不管是用户服务还是订单服务都需要用到User对象,那么是不是在用户服务和订单服务都需要创建User的模型?当然没必要,公共的东西就是要抽取,所以我们会把User对象封装在一个公共的模块 springcloud-user-common中然后让用户服务和订单服务都去依赖这个模块。
1.3.RestTemplate介绍
微服务的通信协议主流的有RPC,Http,SpringCloud是基于Http Restful 风格 ,在Java中发起一个Http请求的方式很多,比如 Apache的HttpClient , OKHttp等等 。Spring为我们封装了一个基于Restful的使用非常简单的Http客户端工具 RestTemplate ,我们就用它来实订单服务和用户服务的通信。需要注意的是,RestTmplate本身不具备服务发现和负载均衡器的功能,我们本章的案例只是演示在订单服务中使用RestTemplate基于ip和端口的方式向用户服务发起调用,即:不走注册中心,不使用服务发现方式。5.4.编码实战
2.编码实战
2.1.搭建公共模块
创建工程模块 springcloud-user-common ,效果如下:
springcloud-parent springcloud-eureka-server-1010 springcloud-order-server-1030 springcloud-user-common //公共User模块 springcloud-user-server-1020
在springcloud-user-common中创建User对象如下
package cn.itsource.springboot.domain; public class User { private Long id; private String username; private String desc; public User() { } public User(Long id, String username, String desc) { this.id = id; this.username = username; this.desc = desc; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } }
2.2.用户和订单依赖User模块
修改工程springcloud-order-server-1030 , springcloud-user-server-1020在他们的pom.xml都导入springcloud-user-common 模块
<dependency> <groupId>cn.itsource.springboot</groupId> <artifactId>springcloud-user-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
2.3.用户服务返回User
修改 springcloud-user-server-1020 工程,编写controller,返回User对象
//用户服务:暴露接口给订单访问 @RestController public class UserController { //订单服务来调用这个方法 http://localhost:1020/user/10 // @GetMapping(value = "/user/{id}" ) @RequestMapping(value = "/user/{id}",method = RequestMethod.GET) public User getById(@PathVariable("id")Long id){ //根据id去数据库查询User return new User(id,"zs:"+id,"我是zs"); } }
2.4.订单服务获取User
在订单服务中需要使用RestTemplate调用用户服务,我们需要把RestTmplate配置成Bean方便使用(当然也可以不创建Bean,用的时候直接new对象也可以) ,修改工程springcloud-order-server-1030在主配置类配置RestTemplate如下:
/** * 订单的启动类 */ @SpringBootApplication @EnableEurekaClient public class OrderServerApplication1030 { //配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main( String[] args ) { SpringApplication.run(OrderServerApplication1030.class); } }
创建controller,通过RestTemplate调用用户服务,代码如下:
//订单服务 @RestController public class OrderController { //需要配置成Bean @Autowired private RestTemplate restTemplate ; //浏览器调用该方法 @RequestMapping(value = "/order/{id}",method = RequestMethod.GET) public User getById(@PathVariable("id")Long id){ //发送http请求调用 user的服务,获取user对象 : RestTemplate //目标资源路径:user的ip,user的端口,user的Controller路径 String url = "http://localhost:1020/user/"+id; //发送http请求 return restTemplate.getForObject(url, User.class); } }
这里的url是用户服务获取user资源的路径,通过RestTmplate发起Http调用,拿到User对象后返回给浏览器。
2.5.测试服务通信
依次启动Eureka Server注册中心(不启动也行) , 用户服务 ,订单服务 , 浏览器访问订单服务:http://localhost:1030/order/1 , 返回结果:
思考,这种方式有什么问题?
五.Ribbon客户端负载均衡
1.基本概念
1.1.为什么要Ribbon
我们知道,为了防止应用出现单节点故障问题,同时为了提高应用的作业能力,我们需要对应用做集群 ,如果我们对user-server(用户服务)做了集群 ,那么这个时候回衍生出一些问题:现在有两个user-server(用户服务)就意味着有两个user-server(用户服务)的通信地址,我的order-server(订单服务)在向user-server(用户服务)发起调用的时候该访问哪个?如何访问?这个时候就需要有一个组件帮我们做请求的分发,即:负载均衡器,而Ribbon就是一个 - 客户端负载均衡器。
1.2.什么是Ribbon
Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法
。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试
等。简单的说,Ribbon是一个客户端负载均衡器,Ribbon可以按照负载均衡算法(如简单轮询,随机连接等)向多个服务发起调用
(正好可以解决上面的问题),我们也很容易使用Ribbon实现自定义的负载均衡算法
。
1.3.Ribbon的工作机制
如下图,我们将user-server(用户服务)做集群处理,增加到2个节点(注意:两个user-server(用户服务)的服务名要一样,ip和端口不一样),在注册中心的服务通信地址清单中user-server(用户服务)这个服务下面会挂载两个通信地址 。 order-server(订单服务)会定时把服务通信地址清单拉取到本地进行缓存
, 那么当order-server(订单服务)在向user-server(用户服务)发起调用时,需要指定服务名为 user-server(用户服务)
;那么这个时候,ribbon会根据user-server(用户服务)这个服务名找到两个order-server的通信地址
, 然后ribbon会按照负载均衡算法(默认轮询)选择其中的某一个通信地址,发起http请求实现服务的调用
,如下图:
在理解了Ribbon工作机制之后,我们就来编码实战上图描述的场景。
2.提供者user-server(用户服务)集群
2.1.服务集群方案
使用SpringBoot多环境配置方式集群,一个配置文件配置多个order-server环境 ,需要注意的是集群中的多个服务名(spring.application.name)应该一样,我们把相同的东西提取到最上面,不同的东西配置在各自的环境中
2.2.用户服务集群配置
修改 application.yml 如下:
#注册到EurekaServer eureka: client: serviceUrl: defaultZone: http://peer1:1010/eureka/,http://peer2:1011/eureka/,http://peer3:1012/eureka/ #使用ip地址进行注册 instance: prefer-ip-address: true spring: application: name: user-server #服务名都叫user-server profiles: active: user-server1 --- server: port: 1020 eureka: instance: instance-id: user-server:1020 spring: profiles: user-server1 --- server: port: 1021 eureka: instance: instance-id: user-server:1021 spring: profiles: user-server2
用户服务集群配置成功,启动方式同注册中心集群一样。需要配置服务运行多实例启动,启动时需要注意修改spring.profiles.active的值来切换不同的实例。
2.3.修改Controller
为了后续测试的时候方便区分不同的用户服务实例,这里我们在Controller中读取应用的端口随User返回,在启动不同的用户服务实例的时候端口是不同的,修改后的代码如下:
//用户服务:暴露接口给订单访问 @RestController public class UserController { //加载端口 @Value("${server.port}") private int port; //订单服务来调用这个方法 http://localhost:1020/user/10 // @GetMapping(value = "/user/{id}" ) @RequestMapping(value = "/user/{id}",method = RequestMethod.GET) public User getById(@PathVariable("id")Long id){ //根据id去数据库查询User System.out.println(port); return new User(id,"zs:"+id,"我是zs,port:"+port); //端口随User返回 } }
3.消费者Order-server集成Ribbon
Ribbon集成官方文档:16. Client Side Load Balancer: Ribbon
修改springcloud-order-server-1030工程,集成Ribbon
3.1.导入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
3.2.开启负载均衡
修改RestTemplate的Bean的定义方法,加上Ribbon的负载均衡注解@LoadBalanced赋予RestTemplate有负债均衡的能力。
/** * 订单的启动类 */ @SpringBootApplication @EnableEurekaClient public class OrderServerApplication1030 { //配置一个RestTemplate ,Spring封装的一个机遇Restful风格的http客户端 工具 //@LoadBalanced :让RestTemplate有负载均衡的功能 @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } //省略...
3.3.修改Controller调用方式
在之前的案例中,我们调用用户服务是通过用户服务的主机加上端口“localhost:1020”的调用方式,现在我们把 “localhost:1020” 修改为 用户服务的服务名 。底层会通过服务发现的方式使用Ribbin进行负载均衡调用。
@RestController public class OrderController { //需要配置成Bean @Autowired private RestTemplate restTemplate ; //浏览器调用该方法 @RequestMapping(value = "/order/{id}",method = RequestMethod.GET) public User getById(@PathVariable("id")Long id){ //发送http请求调用 user的服务,获取user对象 : RestTemplate //user的ip,user的端口,user的Controller路径 //String url = "http://localhost:1020/user/"+id; String url = "http://user-server/user/"+id; //发送http请求 return restTemplate.getForObject(url, User.class); } }
3.4.测试Ribbon
分别启动EurekaServer注册中心 ,启动两个UserServer用户服务,启动OrderServer订单消费者服务,浏览器访问订单服务:http://localhost:1030/order/1 ,发送多次请求。
观察响应的结果中的端口变化 - 端口会交替出现1020,,1021我们可以推断出Ribbon默认使用的是轮询策略。
4.负载均衡算法
4.1.Ribbon内置算法
Ribbon内置7种负载均衡算法,每种算法对应了一个算法类如下:
4.2.配置负载均衡算法
Ribbon可以进行全局负载均衡算法配置,也可以针对于具体的服务做不同的算法配置。同时可以使用注解方式和yml配置方式来实现上面两种情况。
1.注解全局配置
随机算法的效果最好演示,我们把负载均衡算法修改成随机算法,只需要RandomRule配置成Bean即可,修改主配置类如下:
/** * 订单的启动类 */ @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(); } //省略...
测试重启订单服务,访问http://localhost:1030/order/1 ,发送多次请求应该可以看到结果中的端口随机变动。
2.yml方式配置负载均衡算法
配置全局Ribbon算法
ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
配置某个服务的Ribbon算法
user-server: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
4.做个总结
上面我们介绍了通过注解方式进行负载均衡策略全局配置和针对某个服务的负载均衡配置,也介绍了通过yml方式配置,我们可以根据项目情况选择其中一种配置方式即可。
5.Ribbon调优配置
5.1.超时配置
使用Ribbon进行服务通信时为了防止网络波动造成服务调用超时,我们可以针对Ribbon配置超时时间以及重试机制
ribbon: ReadTimeout: 3000 #读取超时时间 ConnectTimeout: 3000 #链接超时时间 MaxAutoRetries: 1 #重试机制:同一台实例最大重试次数 MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数 OkToRetryOnAllOperations: false #是否所有操作都重试,因为针对post请求如果没做幂等处理可能会造成数据多次添加/修改
当然也可以针对具体的服务进行超时配置:如"<服务名>.ribbon..."
5.2.饥饿加载
我们在启动服务使用Ribbon发起服务调用的时候往往会出现找不到目标服务的情况,这是因为Ribbon在进行客户端负载均衡的时候并不是启动时就创建好的,而是在实际请求的时候才会去创建,所以往往我们在发起第一次调用的时候会出现超时导致服务调用失败,我们可以通过设置Ribbon的饥饿加载来改善此情况,即在服务启动时就把Ribbon相关内容创建好。
ribbon: eager-load: enabled: true #开启饥饿加载 clients: user-server #针对于哪些服务需要饥饿加载