上文我们讲解了(RestTemplate、Eureka、Eureka集群、Ribbon负载均衡、Eureka节点服务剔除)
上文链接
接下来我们继续讲讲其他的微服务的知识示例
疑问:Eureka那么好用为什么要换掉它呢?
在Euraka的GitHub上,宣布Eureka 2.x闭源。近这意味着如果开发者继续使用作为 2.x 分支上现有工作 repo 一部分发布的代码库和工件,则将自负风险。
那么我们可以替换哪些注册中心呢?(如下)
组件名 | 语言 | CAP | 一致性算法 | 服务健康检查 | 对外暴露接口 |
---|---|---|---|---|---|
Eureka | JAVA | AP(可用性、分区容忍性) | 无 | 可配支持 | HTTP |
Consul | Go | CP(一致性、分区容忍性) | Raft | 支持 | HTTP/DNS |
Zookeeper | JAVA | CP(一致性、分区容忍性) | Paxos | 支持 | 客户端 |
Nacos | JAVA | AP(可用性、分区容忍性) | Raft | 支持 | HTTP |
本文我们讲解下consul替代上文的eureka
consul是近几年比较流行的服务发现工具,工作中用到,简单了解一下。consul的三个主要应用场景: 服务发现、服务隔离、服务配置。
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实 现、健康检查、Key/Value
存储、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等)。 使用起来也较 为简单。
Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和 Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker等轻量级容器可无缝配合。
Consul 的优势:
综合比较, Consul 作为服务注册和配置管理的新星, 比较值得关注和研究。
特性:
(1)一致性
Consul强一致性(CP)
Eureka保证高可用和最终一致性(AP)
(2)开发语言和使用
Consul 不同于 Eureka 需要单独安装,访问Consul 官网下载 Consul 的最新版本,我这里是consul1.5.3x。根据不同的系统类型选择不同的安装包。
(注意如果你是阿里云,请开放防火墙端口、安全组,如果有宝塔的话,记得也要开放宝塔的防火墙端口)
## 从官网下载最新版本的Consul服务
wget https://releases.hashicorp.com/consul/1.5.3/consul_1.5.3_linux_amd64.zip
##使用unzip命令解压
unzip consul_1.5.3_linux_amd64.zip
##将解压好的consul可执行命令拷贝到/usr/local/bin目录下
cp consul /usr/local/bin
##测试一下
consul
启动consul服务
0.0.0.0指允许任意ip访问
##已开发者模式快速启动,-client指定客户端可以访问的ip地址
[root@node01 ~]# consul agent -dev -client=0.0.0.0
==> Starting Consul agent...
Version: 'v1.5.3'
Node ID: '49ed9aa0-380b-3772-a0b6-b0c6ad561dc5'
Node name: 'node01'
Datacenter: 'dc1' (Segment: '')
Server: true (Bootstrap: false)
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false,
Auto-Encrypt-TLS: false
启动成功之后访问: http://IP:8500 ,可以看到 Consul 的管理界面
consul agent -dev -client=0.0.0.0
Consul 支持健康检查,并提供了 HTTP 和 DNS 调用的API接口完成服务注册,服务发现,以及K/V存储这些功能。接下来通过发送HTTP请求的形式来了解一下Consul
{
"Datacenter": "dc1",
"Node": "node01",
"Address": "192.168.74.102",
"Service": {
"ID":"mysql-01",
"Service": "mysql",
"tags": ["master","v1"],
"Address": "192.168.74.102",
"Port": 3306
}
}
{
"Datacenter": "dc1",
"Node": "node01",
"ServiceID": "mysql-01"
}
可以参照Consul提供的KV存储的API完成基于Consul的数据存储(以下会举例说明)
含义 | 请求路径 | 请求方式 |
---|---|---|
查看key | v1/kv/:key | GET |
保存或更新 | v1/kv/:key | put |
删除key | v1/kv/:key | delete |
key值中可以带/, 可以看做是不同的目录结构。
value的值经过了base64_encode,获取到数据后base64_decode才能获取到原始值。数据不能大
于512Kb
不同数据中心的kv存储系统是独立的,使用dc=?参数指定
注意:product项目、以及order项目都要引入(需要注册和发现的微服务都要引入)
引入jar包的同时,注释掉上述的eureka的jar包
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.boot
spring-boot-starter-actuator
其中 spring-cloud-starter-consul-discovery
是SpringCloud提供的对consul支持的相关依赖。
spring-boot-starter-actuator 适用于完成心跳检测响应的相关依赖。(上文的eureka的安全检测也依赖于这个启动器)
product微服务的yaml文件(注意不需要数据库的,不配置数据库相关的配置)
server:
# 9011 9015
port: 9011 #端口
spring:
application:
name: ebuy-product #服务名称
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/ebuy?useUnicode=true&characterEncoding=utf8
username: root
password: 123
cloud:
consul:
host: localhost
port: 8500
discovery:
register: true
instance-id: ${spring.application.name}-1
service-name: ${spring.application.name}
port: ${server.port}
health-check-path: /actuator/health
health-check-interval: 15s
prefer-ip-address: true
ip-address: ${spring.cloud.client.ip-address}
mybatis:
type-aliases-package: cn.ebuy.product.pojo
mapper-locations: cn/ebuy/product/pojo/*.xml
logging:
level:
cn.ebuy: DEBUG
${spring.application.name}:${spring.cloud.client.ipAddress}
记得修改server.port,instance-id(前者是端口号,后者是注册的实例名字)
前面我们使用的RestTemplate实现REST API调用,代码大致如下:
@GetMapping("/ribbon/{id}")
public EasybuyProduct getOrderByRibbonId(@PathVariable long id){
return restTemplate.getForObject("http://ebuy-product/product/"+id,EasybuyProduct.class);
}
那么为什么RestTemplate用着好好的,为什么要用Feign呢?
由代码可知,我们是使用拼接字符串的方式构造URL的,该URL只有一个参数。
但是,在现实中,URL中往往含有多个参数。
这时候我们如果还用这种方式构造URL,那么就会非常痛苦。那应该如何解决? 我们带着这样的问题进入到本章的学习
Feign是Netflix开发的声明式,模板化的HTTP客户端,其灵感来自Retrofit,JAXRS-2.0以及WebSocket.
只需要在服务调用方添加即可!
org.springframework.cloud
spring-cloud-starter-openfeign
创建一个Feign接口,此接口是在Feign中调用微服务的核心接口
在服务消费者ebuy_order 添加一个 OrderFeginClient 接口
重启服务,调用
Ribbon是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以 在客户端
配置RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate
模拟http请求,步骤相当繁琐。
Feign 是在 Ribbon的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。采用接口的方式,只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方 法即可,不需要自己构建http请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写客户端变得非常容易。
Feign中本身已经集成了Ribbon依赖和自动配置,因此我们不需要额外引入依赖,也不需要再注册
RestTemplate 对象。另外,我们可以像之前说的那样去配置Ribbon,可以通过 ribbon.xx 来进
行全局配置。也可以通过 服务名.ribbon.xx 来对指定服务配置:
为什么要配置Feign,之前用的不是好好的吗?,要怎么配置?
因为真实的请求环境并不总是一帆风顺,可能会网络阻塞、可能能会线程拥挤,导致feign调用的服务不能及时的完成服务的正常使用,导致报错(接下来来个例子演示下)
报错(读取超时时间 超时)
代码:
ebuy-product:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 随机,默认轮询
ConnectTimeout: 2500 # Ribbon的连接超时时间(创建连接时间:毫秒)
ReadTimeout: 5000 # Ribbon的数据读取超时时间 (得到数据的时间)
OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数 (1表示不重试自己)
从Spring Cloud Edgware开始,Feign支持使用属性自定义Feign。对于一个指定名称的Feign Client(例如该Feign Client的名称为 feignName ),Feign支持如下配置项:
feign:
client:
config:
ebuy-product:
connectTimeout: 5000 # 相当于Request.Options
readTimeout: 5000 # 相当于Request.Options
# 配置Feign的日志级别,相当于代码配置方式中的Logger
loggerLevel: full
connectTimeout : 建立链接的超时时长
readTimeout : 读取超时时长
loggerLevel: Fegin的日志级别
errorDecoder :Feign的错误解码器
retryer : 配置重试
requestInterceptors : 添加请求拦截器
decode404 : 配置熔断不处理404异常
虽然是慢了点,但是数据正常返回,服务正常!!!
Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:
feign:
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩
同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
注:上面的数据类型、压缩大小下限均为默认值。
在开发或者运行阶段往往希望看到Feign请求过程的日志记录,默认情况下Feign的日志是没有开启的。
要想用属性配置方式来达到日志效果,只需在 application.yml 中添加如下内容即可:
feign:
client:
config:
order-product:
loggerLevel: FULL
logging:
level:
cn.warehouse.order.fegin.ProductFeginClient:
debug
Jmetter安装十分简单,下载解压之后,安装目录下
bin/jmeter.bat 已管理员身份启动即可
上图可以看出getMsg这个方法是不调用product模块的服务的!
模拟洪峰环境(线程不足用户使用)
这显然是不好的用户体验
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务累计,导致服务瘫痪。
在SpringBoot程序中,默认使用内置tomcat作为web服务器。单tomcat支持最大的并发请求是有限的,如果某一接口阻塞,待执行的任务积压越来越多,那么势必会影响其他接口的调用。
上述的测试便是order调用product的服务出现问题,导致任务累计,进而使得order的tomcat的线程处于阻塞状态,getMsg的请求被排队。
那么如何解决上述的问题呢?
实现的主要思路是将上述的order调用product的服务的线程进行隔离,与getMsg方法不处在同一线程池中,进而使得getMsg方法正常执行,但是order调用product的服务仍然会很拥塞(但是可以返回fallback,结束线程)!
com.netflix.hystrix
hystrix-metrics-event-stream
1.5.12
com.netflix.hystrix
hystrix-javanica
1.5.12
在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B
服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服
务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕,
导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难 性的严重后果,这就是服务故障的“雪崩”效应。
雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方
法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根
本原因来源于服务之间的强依赖,所以我们可以提前评估,做好熔断,隔离,限流。
顾名思义,它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。
当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体 的系统服务
熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压
力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这 种牺牲局部,保全整体的措施就叫做熔断
所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的
fallback回调,返回一个缺省值。 也可以理解为兜底
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说
系统的吞吐量是可以被测算的,为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流
量并采取少量措施以完成限制流量的目的。比方:推迟解决,拒绝解决,或者者部分拒绝解决等等。
Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失
败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
在启动类 OrderApplication 中添加 @EnableCircuitBreaker 注解开启对熔断器的支持。
解读
由代码可知,为 hystrixFindById 方法编写一个回退方法orderFallBack,该方法与 hystrixFindById 方法具有相同的参数与返回值类型,该方法返回一个默认的错误信息。 在 hystrixFindById 方法上,使用注解
@HystrixCommand
的fallbackMethod属性,指定熔断触发的降级方法 是
orderFallBack。 因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。 在
hystrixFindById 方法上 HystrixCommand(fallbackMethod = “orderFallBack”) 用来 声明一个降级逻辑的方法 当ebuy-product 微服务正常时,浏览器访问
http://localhost:9016/order/hystrix/816753
这是因为之前在product服务提供方那里加了个线程休眠为2秒,而hystrix判断服务超过1秒未返回,就进行熔断降级处理!!
因为我们在product那里设置了休眠2秒,来模拟并发问题。
上述的熔断处理已经实现,但是如果每一个请求都要配置一个对应的熔断的兜底方法,那肯定是不行的!!!所以hystrix提供了一种全局的熔断处理。
package cn.ebuy.order.controller;
import cn.ebuy.order.pojo.EasybuyProduct;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/order2")
@DefaultProperties(defaultFallback = "orderFallBack")
public class Order2Controller {
@Autowired
RestTemplate restTemplate;
@GetMapping("/testString")
public String testString(){
return "测试成功";
}
@RequestMapping(value = "/hystrix/{id}")
@HystrixCommand
public EasybuyProduct hystrixFindById(@PathVariable Long id){
EasybuyProduct easybuyProduct=restTemplate.getForObject("http://ebuy-product/product/"+id,EasybuyProduct.class);
return easybuyProduct;
}
/**
* 降级(兜底)
* @return
*/
/*注意全局的不要加参数*/
public EasybuyProduct orderFallBack(){
EasybuyProduct easybuyProduct=new EasybuyProduct();
easybuyProduct.setEpDescription("全局熔断处理");
return easybuyProduct;
}
}
注意此时兜底的这个orderFallBack方法参数可以为空,但是返回类型必须要和加了@HystrixCommand
的方法保持一致,后期我们将写一个包装类(涵盖实体泛型对象、以及一些基本信息),用来作为熔断降级的返回类型,这里暂不做演示
上述我们实现了restTemplate的熔断降级,接下来实现Feign的熔断降级!!!
SpringCloud Fegin默认已为Feign整合了hystrix,所以添加Feign依赖后就不用在添加hystrix,那么怎么才能让Feign的熔断机制生效呢,只要按以下步骤开发:
如果restTemplate的熔断降级已经配置了的话,那么这一步省略
//开启熔断
@EnableCircuitBreaker
feign:
hystrix: #在feign中开启hystrix熔断
enabled: true
package cn.ebuy.order.feign;
import cn.ebuy.order.pojo.EasybuyProduct;
import org.springframework.stereotype.Component;
//注意要加载该组件
@Component
public class OrderFeignClientCallBack implements OrderFeignClient {
@Override
public EasybuyProduct findById(Long id) {
EasybuyProduct easybuyProduct=new EasybuyProduct();
easybuyProduct.setEpDescription("Feign熔断处理");
return easybuyProduct;
}
}
这样Feign的熔断降级便是完成了。