笔记是看了江南一点雨的课,然后做的,基本上是上课笔记的东西,实践多一点,然后结合《深入理解 Spring Cloud 与微服务构建》一书,自己做了一些记载。有看不懂的大家可以留言一起討論。还在更新中...
理论方面我做了读书笔记:《Spring Cloud 微服务架构进阶》读书笔记
微服务介绍:
微服务架构越来越流行,这个没有异议。
2009 年,Netflix 重新定义了它的应用程序员的开发模型,这个算是微服务的首次探索。 20014 年,《Microservices》,这篇文章以一个更加通俗易懂的方式,为大家定义了微服务。
为什么要用微服务?
互联网应用产品的两大特点:
1. 需求变化快 2. 用户群体庞大
在这样的情况下,我们需要构建一个能够灵活扩展,同时能够快速应对外部环境变化的一个应用,使用 传统的开发方式,显然无法满足需求。这个时候,微服务就登场了。
2.1 什么是微服务
简单来说,微服务就是一种将一个单一应用程序拆分为一组小型服务的方法,拆分完成后,每一个服务 都运行在独立的进程中,服务于服务之间采用轻量级的通信机制来进行沟通(Spring Cloud 中采用基于 HTTP 的 RESTful API)。
每一个服务,都是围绕具体的业务进行构建,例如一个电商系统,订单服务、支付服务、物流服务、会 员服务等等,这些拆分后的应用都是独立的应用,都可以独立的部署到生产环境中。就是在采用微服务 之后,我们的项目不再拘泥于一种语言,可以 Java、Go、Python、PHP 等等,混合使用,这在传统的 应用开发中,是无法想象的。而使用了微服务之后,我们可以根据业务上下文来选择合适的语言和构建 工具进行构建。
微服务可以理解为是 SOA 的一个传承,一个本质的区别是微服务是一个真正分布式、去中心化的,微服务的拆分比 SOA 更加彻底。
面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构件在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。
2.2 微服务优势
- 1. 复杂度可控 ,
- 2. 独立部署,微服务独立发布
- 3. 技术选型灵活 ,多语言使用
- 4. 较好的容错性 ,
- 5. 较强的可扩展性 。
2.3 使用 Spring Cloud 的优势
- Spring Cloud 可以理解为微服务这种思想在 Java 领域的一个具体落地。Spring Cloud 在发展之初,就借鉴了微服务的思想,同时结合 Spring Boot,Spring Cloud 提供了组件的一键式启动和部署的能力, 极大的简化了微服务架构的落地。
- Spring Cloud 这种框架,从设计之初,就充分考虑了分布式架构演化所需要的功能,例如服务注册、配 置中心、消息总线以及负载均衡等。这些功能都是以可插拔的形式提供出来的,这样,在分布式系统不 断演化的过程中,我们的 Spring Cloud 也可以非常方便的进化。
3. Spring Cloud 介绍
3.1 什么是 Spring Cloud
Spring Cloud 是一系列框架的集合,Spring Cloud 内部包含了许多框架,这些框架互相协作,共同构建分布式系统。利用这些组件,可以非常方便的构建一个分布式系统。
3.2 核心特性
- 1. 服务注册与发现
- 2. 负载均衡
- 3. 服务之间调用
- 4. 容错、服务降级、断路器
- 5. 消息总线
- 6. 分布式配置中心
- 7. 链路器
3.3 版本名称
不同于其他的框架,Spring Cloud 版本名称是通过 A(Angel)、B(Brixton)、C(Camden)、 D(Dalston),E(Edgware)、F(Finchley)这些名字使用了伦敦地铁站的名 字,目前最新版是 H (Hoxton)版。
Spring Cloud 中,除了大的版本之外,还有一些小版本,小版本命名方式如下:
- M ,M 版是 milestone (里程碑)的缩写,所以我们会看到一些版本叫 M1、M2
- RC,RC 是 Release Candidate,表示该项目处于候选状态,这是正式发版之前的一个状态,所以 我们会看到 RC1、RC2
- SR,SR 是 Service Release ,表示项目正式发布的稳定版,其实相当于 GA(Generally Available) 版。所以,我们会看到 SR1、SR2
- SNAPSHOT,这个表示快照版
4. Spring Cloud 体系
4.1 Spring Cloud 包含的组件
- Spring Cloud Netflix,这个组件,在 Spring Cloud 成立之初,立下了汗马功劳。但是, 2018 年 的断更,也是 Netflix 掉链子了。
- Spring Cloud Config,分布式配置中心,利用 Git/Svn 来集中管理项目的配置文件
- Spring Cloud Bus,消息总线,可以构建消息驱动的微服务,也可以用来做一些状态管理等
- Spring Cloud Consul,服务注册发现
- Spring Cloud Stream,基于 Redis、RabbitMQ、Kafka 实现的消息微服务
- Spring Cloud OpenFeign,提供 OpenFeign 集成到 Spring Boot 应用中的方式,主要解决微服务 之间的调用问题
- Spring Cloud Gateway,Spring Cloud 官方推出的网关服务 Spring Cloud Cloudfoundry,利用 Cloudfoundry 集成我们的应用程序
- Spring Cloud Security,在 Zuul 代理中,为 OAuth2 客户端认证提供支持
- Spring Cloud AWS ,快速集成亚马逊云服务
- Spring Cloud Contract,一个消费者驱动的、面向 Java 的契约框架
- Spring Cloud Zookeeper,基于 Apache Zookeeper 的服务注册和发现
- Spring Cloud Data Flow,在一个结构化的平台上,组成数据微服务
- Spring Cloud Kubernetes,Spring Cloud 提供的针对 Kubernetes 的支持
- Spring Cloud Function
- Spring Cloud Task,短生命周期的微服务
5、注册中心
Eureka是Spring Cloud中的注册中心,类似于Dubbo中的Zookeeper.
那么到底什么是注册中心,我们为什么需要注册中心?
传统的单体应用:
- 所有的业务都集中在一个项目中,当用户从浏览器发起请求时,直接由前端发起请求给后端,后端调用业务逻辑,给前端请求做出响应,完成一次调用。整个调用过程是一条直线,不需要服务之间的中转,所以没有必要引入注册中心
- 随着公司项目越来越大,我们会将系统进行拆分,例如一个电商项目,可以拆分为订单模块、物流模块、支付模块、CMS模块等等。这样,当用户发起请求时,就需要各个模块之间进行协作,这样不可避免的要进行模块之间的调用。此时,我们的系统架构就会发生变化:
- 这样,模块之间的调用,变得越来越复杂,而且模块之间还存在强耦合。例如A调用B.那么就要在A中写上B的地址,也意味着B的部署位置要固定,同时,如果以后B要进行集群化部署, A也需要修改。
为了解决服务之间的耦合,注册中心闪亮登场。
Eureka
Eureka基于REST来实现服务的注册与发现。
Spring Cloud Eureka并且服务端与客户端均采用Java编写,所以Eureka主要适用于通过Java实现的分布式系统,或是与JVM兼容语言构建的系统。但是,由于Eureka服务端的服务治理机制提供了完备的RESTful API,所以它也支持将非Java语言构建的微服务应用纳入Eureka的服务治理体系中来。只是在使用其他语言平台的时候,需要自己来实现Eureka的客户端程序。
Spring Cloud中封装了Eureka,在Eureka的基础上,优化了一些配置,然后提供了可视化的页面,可以方便的查看服务的注册情况以及服务注册中心集群的运行情况。
Eureka由两部分:服务端和客户端,
- 服务端就是注册中心,用来接收其他服务的注册,Eureka服务端,我们也称为服务注册中心。它同其他服务注册中心一样,支持高可用配置,它依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。如果Eureka以集群模式部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其他分片会把它们的状态再次同步回来。以在AWS上的实践为例, Netlix推荐每个可用的区域运行一个Eureka服务端,通过它来形成集群。
- 客户端则是一个Java客户端,用来注册,并可以实现负载均衡等功能。Eureka客户端,主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时, Eureka客户端向注册中心注册自身提供的服务并周期性地发送心跳来更新它的服务租约。同时,它也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期性地刷新服务状态。
从图中,我们可以看出, Eureka中,有三个角色:
- Eureka Server:注册中心,Eureka提供的服务端,提供服务注册与发现的功能,也就是在上一节中我们实现的eureka-server..
- Eureka Provider:服务提供者,提供服务的应用,可以是Spring Boot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其他应用发现,
- Eureka Consumer:服务消费者,消费者应用从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务,
5.1 Eureka搭建
Eureka本身是使用Java来开发的,
Spring Cloud使用Spring Boot技术对Eureka进行了封装,
所以,在Spring Cloud中使用Eureka非常方便,只需要引入spring-cloud-starter-netlix-eurekaserver这个依赖即可。
然后就像启动一个普通的Spring Boot项目一样启动Eureka即可创建一个普通的Spring Boot项目, 在程序的启动类 EurekaClientApplication 加上注解@EnableEurekaServer 开启@EnableEurekaServer功能
package com.liruilong.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
// 开启eureka功能
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
#给当前服务起一个名字
spring.application.name=eureka
#设置端口号
server.port=1111
#默认情况下, Eureka Server也是一个普通的微服务,所以当它还是一个注册中心的时候,
# 他会有两层身份: 1.注册中心: 2,普通服务,
# 即当前服务会自己把自己注册到自己上面来#register-with-eureka设置为false,
# 表示当前项目不要注册到注册中心上
eureka.client.register-with-eureka=false
#表示是否从eureka Server 上获取注册信息
eureka.client.fetch-registry=false
创建时,添加Eureka依赖:
项目启动后标记这是一个Eureka Server:
项目启动之后,浏览器输入http://localhost:1111/就可以查看Eureka后台管理页面
5.2 Eureka集群
使用了注册中心之后,所有的服务都要通过服务注册中心来进行信息交换。
服务注册中心的稳定性就非常重要了,一旦服务注册中心掉线,会影响到整个系统的稳定性。所以,在实际开发中, Eureka一般都是以集群的形式出现的。
Eureka集群,实际上就是启动多个Eureka实例,多个Eureka实例之间,互相注册,互相同步数据,共同组成一个Eureka集群。
搭建Eureka集群
首先我们需要一点准备工作,修改电脑的hosts文件(c:\windows\system32\drivers\etc\hosts ) 127.0.0.1 eurekaA eurekaB
我们在resources目录下,再添加两个配置文件,分别为application-a.properties以及application-b.properties:然后通过命令行的方式配置不同的参数启动项目。--spring.profiles.active=a/b
用一个服务,改配置启动两次,模拟Eureka集群。相互注册
服务打包之后java -jar 服务jar 。
application-a.properties
#给当前服务起一个名字
spring.application.name=eureka
#设置端口号
server.port=1111
eureka.instance.hostname=eurekaA
#默认情况下, Eureka Server也是一个普通的微服务,所以当它还是一个注册中心的时候,
# 他会有两层身份: 1.注册中心: 2,普通服务,
# 即当前服务会自己把自己注册到自己上面来#register-with-eureka设置为false,
# 表示当前项目不要注册到注册中心上
eureka.client.register-with-eureka=true
#表示是否从eureka Server 上获取注册信息
eureka.client.fetch-registry=true
#A服务要注册到B服务上
eureka.client.service-url.defaultZone=http://eurekaB:1112/eureka
application-b.properties
#给当前服务起一个名字
spring.application.name=eureka
#设置端口号
server.port=1112
eureka.instance.hostname=eurekaB
#默认情况下, Eureka Server也是一个普通的微服务,所以当它还是一个注册中心的时候,
# 他会有两层身份: 1.注册中心: 2,普通服务,
# 即当前服务会自己把自己注册到自己上面来#register-with-eureka设置为false,
# 表示当前项目不要注册到注册中心上
eureka.client.register-with-eureka=true
#表示是否从eureka Server 上获取注册信息
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://eurkaA:1111/eurka
上述配置代码中, defaultZone 为默认的 Zone,来源于 AWS (业务流程管理开发平台是一个易于部署和使用的业务流程管理基础平台软件)的概念。区域(Region)和可 用区(Availability Zone, AZ)是 AWS 的另外两个概念。区域是指服务器所在的区域,比如北 美洲、南美洲、欧洲和亚洲等,每个区域一般由多个可用区组成。 在本案例中 defaultZone 是 指 Eureka Server 的注册地址。
项目中的报错可以不用管;
5.3 Eureka工作细节
Eureka本身可以分为两部分,Eureka Server和Eureka Client
5.3.1 Eureka Server
Eureka Server 主要对外提供了三个功能:
- 服务注册,所有的服务都注册到Eureka Server上面来。
- 提供注册表,注册表就是所有注册上来的一个列表,Eureka Client在调用服务时,需要获取这个注册表,一般来说,这个注册表会缓存下来,如果缓存失效,则直接获取最新的注册表。
- 同步状态,Eureka Clint通过注册,心跳机制,和Eureka Server同步当前客户端的状态。
5.3.1Eureka Client
Eureka Client主要用来简化每一个服务和Eureka Server之间的交互,Eureka Client会自动拉取,更新以及缓存Eureka Serve中的信息,这样,即使Eureka Server所有节点都宕机,Eureka Client依然能够获取到想要调用的服务器的地址(但是地址可能不准确)。
Eureka客户端的配置主要分为以下两个方面。
- 服务注册相关的配置信息,包括服务注册中心的地址、服务获取的间隔时间、可用区域等.
- 服务实例相关的配置信息,包括服务实例的名称、IP地址、端口号、健康检查路径等。
服务注册(Register):服务提供者将自己注册到服务注册中心(Eureka Serve),需要注意,所谓的服务注册,只是一个业务上的划分,本质上他就是一个Eureka Client,当Eureka Client向Eureka Server注册时,他需要提供自身的一些元数据信息,例如IP地址,端口,名称,运行状态。
服务续约(Renew):Eureka Client 注册到 Eureka Server 上之后, 事情没有结束,刚刚开始而已。注册成功之后,默认情况下,Eureka Client每隔30秒就向Eureka Server发送一条心跳消息,来告诉Eureka Server我还在运行,如果Eureka Server连续 90 秒都收到Eureka Client的续约消息(连续三次),会认为Eureka Client 已经掉线了,会将掉线的Eureka Client 从当前的服务注册列表中删除。
服务下线(Cancel):当Eureka Client 下线时,他会主动发送一条消息,
DiscoveryManager. getinstance() .shutdownComponent();
告诉Eureka Server ,我下线啦。
获取注册表信息(Fetch Registries),Eureka Client从Eureka Server上获取服务的注册信息,并将其缓存到本地,本地客户端上。在需要调用远程服务时,会从该信息中查询远程服务所对应的IP地址,端口等信息,Eureka Client上缓存的服务注册信息会定期更新30秒,如果Eureka Server 返回的注册表信息与本地缓存的注册表信息不同的话,Eureka Client会自动处理。
服务踢除(Eviction):在默认情况下,当 Eureka Client 连续 90 秒没有向 Eureka Server 发送服务续约(即心跳〉 时, Eureka Server 会将该服务实例从服务注册列表删除,即服务剔除。
属性信息:
- 一个是是否允许获取注册表信息:eureka.client.fetch-registy=true
- 缓存的服务注册信息,定期更新的时间间隔:Eureka Client.registry-fetch-interval-seconds =30
Eureka集群原理:
在集群架构中,Eureka Server之间通过Replicata进行数据同步,不同的Eureka Server之间不区分主从节点,所有的节点之间都是平衡的,节点之间,通过置顶的ServiceURl来相互注册,形成一个集群,进而提高节点的可用性。
在Eureka Server集群中,如果有一个节点宕机,Eureka client 会自动切花到新的Eureka Server上,每一个Eureka Server节点,都会相互同步数据,Eurack的链接方式,可以是单线的,在配置ServierURl时,可以指定多个注册地址,
Eureka的区分:region地理的不同区域,zone具体的机房。
6、服务注册于服务消费
6.1、服务注册
服务注册就是把一个微服务注册到Euraka Server 上,当其他服务需要调用该服务时,只需要从Eureka Server 上查询该服务的信息即可。
创建一个provider服务,作为我们的服务提供者,创建项目时,选择Eureka Client依赖,这样,当服务创建成功后,简单配置一下,就可以被注册到Eureka Servel。
spring.application.name=provider
server.port=1113
eureka.client.service-url.defaultZone = http://localhost:1111/eureka
分别表示当前服务的名称,端口号,以及服务的地址
启动Eureka服务,等到服务注册中心启动成功之后,在启动provider。都启动成功之后,浏览器输入:http://localhost:1111就可以查看注册信息。
6.2服务消费
6.2.1基本用法
首先在provider中提供一个接口,用于生产,然后创建一个新的consumer项目,消费这个接口。在provider中,提供一个hello接口,如下:
方式一:在普通服务中写死服务调用的接口,耦合度太高。
方法二:在Eureka Client中通过DiscoveryClient来获取服务列表,然后选择指定的生产者,返回服务列表。
方法三:开启多个不同端口的provider生产服务实现Eureka Client集群及,做简单的线性负载均衡。
生产者:provider服务:
package com.liruilong.provider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/12 23:02
*/
@RestController
public class HelloController {
@Value("${server.port}")
Integer port;
@GetMapping("/hello")
public String hello(@RequestParam(defaultValue = "小明") String name){
return "hello Spring Cloud: I am " + name+ "I am from post:" +port ;
}
}
spring.application.name=provider
server.port=1113
eureka.client.service-url.defaultZone = http://localhost:1111/eureka
消费者:consumer服务
package com.liruilong.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/12 23:18
*/
@RestController
public class UserHelloController {
@Autowired
DiscoveryClient discoveryClient;
/**
* @Author Liruilong
* @Description http服务请求写死的,
* @Date 10:32 2020/3/13
* @Param []
* @return java.lang.String
**/
@GetMapping("/hello1")
public String hello1(){
String sendurl = "http://localhost:1113/hello";
return sendURl(sendurl);
}
/**
* @Author Liruilong
* @Description :借助Eureka Client提供的DiscoveryClient工具,根据服务名重Eureka上查询一个服务的详细信息。
* @Date 10:18 2020/3/13
* @Param []
* @return java.lang.String
**/
@GetMapping("/hello2")
public String hello2(){
//DiscoveryClient查询到的服务列表是一个集合,因为服务中部署的过程中,可能是集群形式。每一项都是一个实例
List<ServiceInstance> list = discoveryClient.getInstances("provider");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
StringBuffer sendurl = new StringBuffer();
sendurl.append("http://").append(host).append(":").append(port).append("/hello");
return sendURl(sendurl.toString());
}
/**
* @Author Liruilong
* @Description 实现简单的线性负载均衡+集群
* @Date 11:26 2020/3/13
* @Param []
* @return java.lang.String
**/
int count = 0;
@GetMapping("/hello3")
public String hello3(){
//DiscoveryClient查询到的服务列表是一个集合,因为服务中部署的过程中,可能是集群形式。每一项都是一个实例
List<ServiceInstance> list = discoveryClient.getInstances("provider");
ServiceInstance instance = list.get((count++) % list.size());
String host = instance.getHost();
int port = instance.getPort();
StringBuffer sendurl = new StringBuffer().append("http://").append(host).append(":").append(port).append("/hello");
return sendURl(sendurl.toString());
}
/**
* @Author Liruilong
* @Description: 利用HttpUrConnection来发起请求,
* @Date 10:32 2020/3/13
* @Param [sendurl]
* @return java.lang.String
**/
public String sendURl( String sendurl){
HttpURLConnection connection = null;
try {
URL url = new URL(sendurl);
connection = (HttpURLConnection) url.openConnection();
if(connection.getResponseCode() == 200){
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String s = br.readLine();
br.close();
return s;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return "error";
}
}
spring.application.name=consumer
server.port=1115
eureka.client.service-url.defaultZone = http://localhost:1111/eureka/
测试:
改造;
Http调用:使用Spring提供的RestTemplate来实现,提供一个RestTemplate。http调用时不在使用HttpUrlConnection,直接使用RestTemplate。
package com.liruilong.fsconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class FsconsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FsconsumerApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
/**
* @Author Liruilong
* @Description :借助Eureka Client提供的DiscoveryClient工具,根据服务名重Eureka上查询一个服务的详细信息。
* @Date 10:18 2020/3/13
* @Param []
* @return java.lang.String
**/
@GetMapping("/hello2")
public String hello2(){
//DiscoveryClient查询到的服务列表是一个集合,因为服务中部署的过程中,可能是集群形式。每一项都是一个实例
List<ServiceInstance> list = discoveryClient.getInstances("provider");
ServiceInstance instance = list.get(0);
String host = instance.getHost();
int port = instance.getPort();
StringBuffer sendurl = new StringBuffer();
sendurl.append("http://").append(host).append(":").append(port).append("/hello");
return restTemplate.getForObject(sendurl.toString(),String.class);
}
负载均衡,这里使用过Ribbon实现, Ribbon是一个为客户端提供负载均衡功能的服务,它内部提供了一个叫做ILoadBalance的接口代表负载均衡器的操作,比如有添加服务器操作、选择服务器操作、获取所有的服务器列表、获取可用的服务器列表等等。
使用RestTemplate进行Eureka Client(包括服务提供者以及服务消费者,在这里其实是服务消费者使用RestTemplate)之间的通信,为RestTemplate配置类添加@LoadBalanced注解即可
@Autowired
@Qualifier("restTemplate")
RestTemplate restTemplate;
/**
* @Author Liruilong
* @Description :利用discoveryClient查provider地址,然后根据本地的负载均衡处理。选一个服务,然后拼接url
* restTemplate不能复用。
* @Date 11:26 2020/3/13
* @Param []
* @return java.lang.String
**/
int count = 0;
@GetMapping("/hello3")
public String hello3() {
return restTemplate.getForObject("http://provider/hello",String.class);
}
6.3 RestTemplate
RestTemplate是从Spring3.0开始支持的一个Http请求工具,这个请求工具和Spring Boot无关,更和Spring Cloud无关。RestTemplate提供了常见的REST请求方法模板,例如GET, POST,PUTDELETE请求以及一些通用的请求执行方法exchange和execute方法。
RestTemplate本身实现了RestOperations接口,而在RestOperations接口中,定义了常见的RESTful操作,这些操作在RestTemplate中都得到了很好的实现
GET
首先我们在provider中定义一个hello2接口:
这两大类方法实际上是重载的,唯一不同的,就是返回值类型。getForobject返回的是一个对象,这个对象就是服务端返回的具体值. getForEntity返回的是一个ResponseEntity,这个ResponseEntity中除了服务端返回的具体数据外,还保留了Http 响应头的数据
看清楚两者的区别之后,接下来看下两个各自的重载方法, getForObject和getForEntity分别有三个重载方法,两者的三个重载方法基本都是一致的。所以,这里,我们主要看其中一种。三个重载方法,其实代表了三种不同的传参方式。
@GetMapping("/hello3")
public String hello3() {
return restTemplate.getForObject("http://provider/hello?name={1}", String.class,"liruilong ");
}
@GetMapping("/hello4")
public String hello4() throws UnsupportedEncodingException {
String s1 ;
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://provider/hello?name={1}", String.class, "liruilomng");
Map<String,Object> map = new HashMap<>();
map.put("name","liruilong");
ResponseEntity<String> responseEntitys = restTemplate.getForEntity("http://provider/hello?name={name}",String.class,map);
System.out.println(responseEntitys.getBody());
System.out.println(restTemplate.getForEntity(URI.create("http://provider/hello?name"+ URLEncoder.encode("liruilong","utf-8")),String.class));
s1 = responseEntity.getBody() + "\n";
s1 += responseEntity.getStatusCode() + "\n";
s1 += responseEntity.getStatusCodeValue() + "\n";
s1 += responseEntity.getHeaders().toString();
return s1;
}
POST
首先在provider中提供两个POST接口,同时,因为POST请求可能需要传递JSON,所以,这里我们创建一个普通的Maven项目作为commons模块,然后这个commons模块被provider和consumer共同引用,这样我们就可以方便的传递JSON了
.commons模块创建成功后,首先在commons模块中添加User对象,然后该模块分别被provider和consumer引用。
然后,我们在provider中,提供和两个POST接口:
/**
* @Author Liruilong
* @Description K-v形式
* @Date 18:07 2020/3/18
* @Param [user]
* @return com.liruilong.commons.User
**/
@PostMapping("/user1")
public User addUser1(User user){
return user;
}
/**
* @Author Liruilong
* @Description JSON 形式
* @Date 2020/3/18
* @Param [user]
* @return com.liruilong.commons.User
**/
@PostMapping("/user2")
public User addUser2(@RequestBody User user){
return user;
}
post参数到底是key/value形式还是json形式,主要看第二个参数,
如果第二个参数是MultivalueMap ,则参数是以key/value形式来传递的,如果是一个普通对象,则参数是以json形式来传递的。
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@GetMapping("/hello6")
public User hello6(){
MultiValueMap<String,Object> map = new LinkedMultiValueMap<>();
map.add("username","LiRuilong");
map.add("password","123456");
User user = restTemplate.postForObject("http://provider/user1",map,User.class);
System.out.println(restTemplate.postForObject("http://provider/user2",new User("123","123"), User.class).toString());
return user;
}
最后再看看一下postForLocation.有的时候,当我执行完一个post请求之后,立马要进行重定向.一个非常常见的场景就是注册,注册是一个post请求,注册完成之后,立马重定向到登录页面去登录。对于这种场景,我们就可以使用postForLocation.首先我们在provider上提供一个用户注册接口
package com.liruilong.provider;
import com.liruilong.commons.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/18 19:19
*/
@Controller
public class RegisterController {
@GetMapping("/loginPage")
@ResponseBody
public String loginPage(String username) {
return "loginPage" + username;
}
@PostMapping("regiser")
public String regiser(User user) {
return "redirect:http://provider/loginPage?username=" + user.getUsername();
}
}
@GetMapping("/hello7")
public String hello7(){
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("username","liruilong");
map.add("password","123");
URI uri = restTemplate.postForLocation("http://provider/regiser",map);
System.out.println(uri);
String s = restTemplate.getForObject(uri,String.class);
System.out.println(s);
return s;
}
put方式:
/**
* @Author Liruilong
* @Description K-v形式
* @Date 18:07 2020/3/18
* @Param [user]
* @return com.liruilong.commons.User
**/
@PutMapping("/user1")
public void updateUser1(User user){
System.out.println(user.toString());
}
/**
* @Author Liruilong
* @Description JSON 形式
* @Date 2020/3/18
* @Param [user]
* @return com.liruilong.commons.User
**/
@PutMapping("/user2")
public void updateUser2(@RequestBody User user){
System.out.println(user.toString());
}
@GetMapping("/hello8")
public void hello8(){
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("username","liruilong");
map.add("password","123");
restTemplate.put("http://provider/user1",map);
restTemplate.put("http://provider/user2",new User("123","123"));
}
Delete方式
@DeleteMapping("/user1")
public void DeleteUser1(Integer id){
System.out.println(id);
}
@DeleteMapping("/user2/{id}")
public void DeleteUser2(@PathVariable Integer id){
System.out.println(id);
}
@GetMapping("/hello9")
public void hello9(){
restTemplate.delete("http://provider/user1?id={1}","99");
restTemplate.delete("http://provider/user2/{1}","99");
}
客户端负载均衡就是相对服务端负载均衡而言的。
服务端负载均衡,就是传统的Nginx的方式,用Nginx做负载均衡,我们称之为服务端负载均衡:
这种负载均衡,我们称之为服务端负载均衡,它的一个特点是,就是调用的客户端并不知道具体是哪个Server提供的服务,它也不关心,反正请求发送给Nginx, Nginx再将请求转发给Tomcat,客户端只需要记着Nginx的地址即可。客户端负载均衡则是另外一种情形:
客户端负载均衡,就是调用的客户端本身是知道所有Server的详细信息的,当需要调用Server上的接口的时候,客户端从自身所维护的Server列表中,根据提前配置好的负载均衡策略,自己挑选一个Server来调用,此时,客户端知道它所调用的是哪一个Server.
在RestTemplate中,要想使用负载均衡功能,只需要给RestTemplate实例上添加一个@LoadBalanced注解即可,此时, RestTemplate就会自动具备负载均衡功能,这个负载均衡就是客户端负载均衡。
客户端负载均衡: Spring Cloud Ribbon
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Nettlix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。
负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常所说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。
- 硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如FS等;
- 软件负载均衡则是通过在服务器上安装一些具有均衡负载功能或模块的软件来完成请求分发工作,比如Nginx等。
不论采用硬件负载均衡还是软件负载均衡,只要是服务端负载均衡都能以类似下图的架构方式构建起来:
硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,通过心跳检测来剔除故障的服务端节点以保证清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(比如线性轮询、按权重负战、按流量负载等)从维护的可用服务端清单中取出一台服务端的地址,然后进行转发。
客户端负载均衡原理
在Spring Cloud中,实现负载均衡非常容易,只需要添加@LoadBalanced注解即可。
只要添加了该注解,一个原本普普通通做Rest请求的工具RestTemplate就会自动具备负载均衡功能,这个是怎么实现的呢?
整体上来说,这个功能的实现就是三个核心点:
- 1,从Eureka Client本地缓存的服务注册信息中,选择一个可以调用的服务
- 2,根据1中所选择的服务,重构请求URL地址
- 3将1、2步的功能嵌入到RestTemplate中
Consul 的简单应用
除了 Eureka, Spring Cloud 还提供了 Consul 服务注册与发现的支持实现。 Consul 是由 HashiCorp 基于 Go 语言开发的服务软件,支持多数据中心、 分布式和高可 用的服务注册和发现。 它采用 Raft 算法保证服务的一致性,支持健康检查。
consul安装好之后访问:8500
新建一個項目:
spring.application.name=consul-pro
server.port=2000
#Consul的配置
spring.cloud.consul.host=39.97.241.18
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.service-name=consul-pro
package com.liruilong.consulpro;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulProApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulProApplication.class, args);
}
}
构建一个生产服务
package com.liruilong.consulpro;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/20 17:00
*/
@RestController
public class HelloController {
@Value("${server.port}")
Integer prot;
@GetMapping("/hello")
public String hello(){
return "生活加油:"+prot;
}
}
构建一个消费服务:
spring.application.name=consul-con
server.port=2002
spring.cloud.consul.port=8500
spring.cloud.consul.host=39.97.241.18
spring.cloud.consul.discovery.service-name=consul-con
package com.liruilong.consulcon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class ConsulConApplication {
public static void main(String[] args) {
SpringApplication.run(ConsulConApplication.class, args);
}
@Bean
RestTemplate restTemplate(){
return new RestTemplate();
}
}
消费接口服务:这个报错我也没办法解决。
package com.liruilong.consulcon;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/20 19:52
*/
@RestController
public class Hellocontroller {
@Autowired
RestTemplate template;
@Autowired
LoadBalancerClient loadBalancerClient;
@GetMapping("/hello")
public String hello(){
ServiceInstance chooes = loadBalancerClient.choose("consul-provider");
System.out.println(chooes.getHost());
System.out.println(chooes.getInstanceId());
return template.getForObject(chooes.getUri()+"/hello",String.class);
}
}
8. Hystrix
8.1基本介绍
Hystrix叫做断路器熔断器。微服务系统中,整个系统出错的概率非常高,因为在微服务系统中,涉及到的模块太多了,每一个模块出错,都有可能导致整个服务出,当所有模块都稳定运行时,整个服务才算是稳定运行。
我们希望当整个系统中,某一个模块无法正常工作时,能够通过我们提前配置的一些东西,来使得整个系统正常运行,即单个模块出问题,不影响整个系统
8.2基本用法
首先创建一个新的SpringBoot模块,然后添加依赖:
spring.application.name=hystrix
server.port=3000
eureka.client.service-url.defaultZone = http://localhost:1111/eureka
package com.liruilong.hystrix;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/*@SpringBootApplication
@EnableCircuitBreaker */
//开启断路器
@SpringCloudApplication
public class HystrixApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
提供接口:
package com.liruilong.hystrix;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/21 11:55
*/
@RestController
public class Hellocontroller {
@Autowired
HelloService helloService;
@GetMapping("/hello")
private String hello(){
return helloService.hello();
}
}
调用方式可以链式调用,
package com.liruilong.hystrix;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/21 11:56
*/
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
/**
* @Author Liruilong
* @Description 在这个方法中,发起一个远程调用,去调用provider 中的。/hello 接口
* 在这个方法上添加@HystrixCommand 注解,配置fallbackMethon
* @Date 16:40 2020/3/21
* @Param []
* @return java.lang.String
**/
@HystrixCommand(fallbackMethod = "error")
public String hello(){
return restTemplate.getForObject("http://provider/hello",String.class);
}
/**
* @Author Liruilong
* @Description 可以依次降级
* @Date 17:02 2020/3/21
* @Param []
* @return java.lang.String
**/
@HystrixCommand(fallbackMethod = "erroe1")
public String error(){
System.out.println("服务降级啦1");
return restTemplate.getForObject("http://provider/hello",String.class);
}
public String erroe1(){
return "服务降级2";
}
}
请求命令:
请求命令就是以继承类的方式来替代前面的注解方式。需要继承Hystrixcommand来实现,run方法里写逻辑,接口中通过构造函数调用,对于异常的处理getFallback方法。
package com.liruilong.hystrix;
import com.netflix.hystrix.HystrixCommand;
import org.springframework.web.client.RestTemplate;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/21 17:28
*/
public class HelloCommand extends HystrixCommand<String> {
RestTemplate restTemplate;
//setter 请求的详细配置
public HelloCommand(Setter setter, RestTemplate restTemplate) {
super(setter);
this.restTemplate = restTemplate;
}
@Override
protected String run() throws Exception {
return restTemplate.getForObject("http://provider/hello",String.class);
}
@Override
protected String getFallback() {
return "继承类的方式-服务降级啦";
}
}
调用方式分为两种 ,
- 直接使用execute执行
- 先入队然后在执行。
package com.liruilong.hystrix;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
/**
* @Description :
* @Author: Liruilong
* @Date: 2020/3/21 11:55
*/
@RestController
public class Hellocontroller {
@Autowired
HelloService helloService;
@Autowired
RestTemplate restTemplate;
@GetMapping("/hello")
private String hello(){
return helloService.hello();
}
@GetMapping("/hello1")
private void hello1(){
HelloCommand helloCommand1 = new HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("helloCommand")), restTemplate);
System.out.println(helloCommand1.execute());
// 方法二:入队
//方法一:直接执行
HelloCommand helloCommand = new HelloCommand(HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("helloCommand1")), restTemplate);
Future<String> queue = helloCommand.queue();
try{
String s = queue.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
异步请求调用:
@HystrixCommand(fallbackMethod = "erroe1")
public Future<String> hello2(){
return new AsyncResult<String>() {
@Override
public String invoke() {
return restTemplate.getForObject("http://provider/hello",String.class);
}
};
}
@GetMapping("/hello2")
public void hello2() throws ExecutionException, InterruptedException {
Future<String> hello2 = helloService.hello2();
System.out.println(hello2.get());
}
异常处理
当服务器调用的时,如果不是provider原因导致的调用失败,而是consumer中本本身代码出了问题,即consumer中抛出了代码异常。这个时候,也会自动进行服务降级,但是这个时候我们需要知道异常出自哪里。
我们可以在error方法中获取异常。
@HystrixCommand(fallbackMethod = "erroe1")
public String error(Throwable t){
System.out.println("服务降级啦1");
System.out.println(t.getMessage());
return restTemplate.getForObject("http://provider/hello",String.class);
}
public String erroe1(){
return "服务降级2";
}
继承的方式:
@Override
protected String getFallback() {
System.out.println(getExecutionException().getMessage());
return "继承类的方式-服务降级啦";
}
设定不要服务降级
@HystrixCommand(fallbackMethod = "erroe1",ignoreExceptions = ArithmeticException.class)
public String error(Throwable t){
System.out.println("服务降级啦1");
System.out.println(t.getMessage());
return restTemplate.getForObject("http://provider/hello",String.class);
}
请求缓存:
在consumer中调用同一个接口,如果参数形同,则可以使用之前的缓存,需要加@CacheResult,缓存有一个生命周期的概念,这里也一样,需要初始化HystrixRequestContext.初始化完成之后,缓存开始生效。调用close方法之后,缓存开始失效。
@HystrixCommand(fallbackMethod = "erroe3")
@CacheResult//表示该方法的请求结果会被缓存i起来,默认情况下,缓存的key即使方法的参数,
public String hello3(String name) {
return restTemplate.getForObject("http://provider/hello?name={1}",String.class,name);
}
public String erroe3(String name){
return "服务降级+缓存";
}
默认情况下,缓存的key就是所调用的方法的参数,如果参数有多个,就是多个参数组合起来作为缓存key。
如果多个参数,但是又想使用其中一个作为缓存的key,那么可以通过@Cachekey注解来解决。同时当多个参数时,键相同,即使,其他参数不相同,多次请求也可以使用第一次缓存的结果。
@HystrixCommand(fallbackMethod = "erroe3")
@CacheResult//表示该方法的请求结果会被缓存i起来,默认情况下,缓存的key即使方法的参数,
public String hello3(@CacheKey String name, Integer age) {
return restTemplate.getForObject("http://provider/hello?name={1}",String.class,name);
}
public String erroe3(String name){
return "服务降级+缓存";
}
@CacheRemove:在做数据缓存时,如果我们删除数据同时希望把缓存的数据也删除,即可以使用这个。
@HystrixCommand
@CacheRemove(commandKey = "hello3")
public String deleteByName(String name){
return name;
}
继承的话需要调用:
@Override
protected String getCacheKey() {
return null;
}
请求合并:
如果consumer中,频繁的调用provider中的同一个接口,在调用时,只是参数不一样,那个这样的情况下,就可以将多个请求合并为一个,提高请求发送的效率。
标签:服务,String,Spring,笔记,Eureka,springframework,org,import,Cloud From: https://blog.51cto.com/u_13474506/5929835