前言
spring cloud是一个全家桶式的技术栈,包含了很多组件,本文从Eureka Ribbon Feign Hystrix Zuul这几个组件说起
一、业务场景
比较我们常见的电商网站,现在实现用户购买商品-支付订单的功能,流程如下:
首先:用户购买商品 会调用订单系统 创建一个订单,用户支付后,需要将订单状态更新为已支付
接着:调用商口系统扣减商品库存
接着:通知仓库中心,进行发货
二、Spring Cloud Eureka服务注册与发现
订单服务在更新完订单状态想调用 库存服务----》返回成功后—》调用通知发货服务
1、订单服务怎么知道库存服务、发货服务
2、这时Spring Cloud Eureka就出场了,Eureka是服务架构中的注册中心,用于负责服务的注册与发现。
1、微服务调用流程
如上图,库存服务、发货服务都有一个Eureka Client组件,用于将服务注册到Eureka Server是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口,
订单服务 民有一个Eureka client组件,这个组件会从Eureka server获取注册表并缓存到自己本地。
当订单服务要调用库存服务时,就从本地注册表获取库存服务的Ip 、端口就可以发送请求
2、小结
eureka分为eureka server 和eureka client
eureka server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号
eureka client:负责将这个服务的信息注册到Eureka server中
3、为何Eureka注册中心能承载大型系统的千万级访问?
初用Eureka可能会产生以下几个问题?
- Eureka注册中心使用什么样的方式来储存各个服务注册时发送过来的机器地址和端口号?
- 各个服务找Eureka Server拉取注册表的时候,是什么样的频率?
- 各个服务是如何拉取注册表的?
- 一个几百服务,部署上千台机器的大型分布式系统,会对Eureka Server造成多大的访问压力?
- Eureka Server从技术层面是如何抗住日千万级访问量的?
各个服务内的Eureka Client组件,默认情况下,每隔30秒会发送一个请求到Eureka Server,来拉取最近有变化的服务信息
Eureka还有一个心跳机制,各个Eureka Client每隔30秒会发送一次心跳到Eureka Server,通知人家表明这个服务实例还活着!如果某个Eureka Client很长时间没有发送心跳给Eureka Server,那么就说明这个服务实例已经挂了。
4、Eureka server注册表存储结构
假设:
现在有100台微服务,每个服务部署20台机器,也就是部署了10020=2000个微服务。
每个服务内部有一个Eureka client,每隔30s请求Eureka server,拉取注册表变化。且Eureka client还会每隔30s发送一次心跳。
得到:
Eureka server 每分钟发2次拉取注册表请求,2次心跳请求 = 4次
2000个微服务 = 20004 = 8000次
每秒= 8000/60 = 133次
一天 = 80006024 = 1152万次
5、Eureka Server是如何保证轻松抗住这每秒数百次请求,每天千万级请求的呢?
Eureka server注册表存储结构: CocurrentHashMap ,基于内存的注册表。
一句话概括: 维护注册表、拉取注册表、更新心跳时间,全部发生在内存里!这是Eureka Server非常核心的一个点。
6、Eureka Server端优秀的多级缓存机制
Eureka Server为了避免同时读写内存数据结构造成的并发冲突问题,还采用了多级缓存机制来进一步提升服务请求的响应速度。
在拉取注册表的时候:
- 首先从ReadOnlyCacheMap里查缓存的注册表。
- 若没有,就找ReadWriteCacheMap里缓存的注册表。
- 如果还没有,就从内存中获取实际的注册表数据。
在注册表发生变更的时候:
- 会在内存中更新变更的注册表数据,同时过期掉ReadWriteCacheMap。
- 此过程不会影响ReadOnlyCacheMap提供人家查询注册表。
- 一段时间内(默认30秒),各服务拉取注册表会直接读ReadOnlyCacheMap
- 30秒过后,Eureka Server的后台线程发现ReadWriteCacheMap已经清空了,也会清空ReadOnlyCacheMap中的缓存
- 下次有服务拉取注册表,又会从内存中获取最新的数据了,同时填充各个缓存
7、多级缓存机制的优点是什么?
- 尽可能保证了内存注册表数据不会出现频繁的读写冲突问题。
- 并且进一步保证对Eureka Server的大量请求,都是快速从纯内存走,性能极高。
7.1多级缓存小结
- 通过上面的分析可以看到,Eureka通过设置适当的请求频率(拉取注册表30秒间隔,发送心跳30秒间隔),可以保证一个大规模的系统每秒请求EurekaServer的次数在几百次。
- 同时通过纯内存的注册表,保证了所有的请求都可以在内存处理,确保了极高的性能
- 另外,多级缓存机制,确保了不会针对内存数据结构发生频繁的读写并发冲突操作,进一步提升性能。
8、Eureka官宣2.x版本不再开源
Eureka 2.0的开源工作已经停止了,如果你要用Eureka 2.x版本的代码来部署到生产环境的话,一切后果请自负。
Eureka1.x存在的问题:
- 虽然他可以部署集群架构,但是集群中每个Eureka实例都是对等的。每个Eureka实例都包含了全部的服务注册表,每个Eureka实例接收到了服务注册/下线等请求的时候,会同步转发给集群中其他的Eureka实例,实现集群数据同步。
那么这里就有一个问题了:如果是支持超大规模的服务集群,这样的模式能行么?
8.1、自研服务注册中心
这个时候如果自己研发服务注册中心,就可以参考大数据领域的Hadoop的架构思想。
Hadoop的设计思想是把注册表分片存储,分布式存储在多台机器上,每台机器存储部分注册表数据。
然后每个Server可以加上一个从节点做热备份,避免单机挂掉导致注册 表数据丢失。
我们来看看架构图,如下所示:
实际在生产环境使用Eureka的时候,你还会碰到很多现实的问题。
比如说上面讲了,Eureka本身是基于简单的同步机制实现集群架构的,但是这里在集群之间进行同步的时候,其实是异步进行的,采用的是最终一致性的协议。
这就可能会导致说,你某个服务注册到了一个Eureka Server实例上去,但是他需要异步复制到其他的Eureka Server,这中间是需要时间的。
所以可能导致其他的Eureka Server是看不到那个刚新注册的服务实例的。
如下图:
但是如果是采取了类似Hadoop的那种数据分片思想的话,一个注册表数据分片就在一台机器上,由这台机器负责提供服务的注册和发现,那么此时就可以实现强一致的效果
也就是说,只要你注册了,立马就会被别人发现,如下图:
8.2、用Consul替代Eureka
Consul功能:
- 服务注册与发现
- Consul的服务注册机制选择的是基于Daft协议的强一致,没有像Eureka那样使用最终一致的效果。
- 健康检查
- Consul可以支持非常强大的健康检查的功能
- kv存储
- Consul不光支持服务注册和发现,居然还可以支持简单的kv存储。支持用key-value对的形式存放一些信息以及提取查询
- 安全的服务通信
- Consul支持让服务之间进行授权来限制哪些服务可以通信和连接
- 多数据中心支持
consul作为服务注册中心架构原理,想看另一篇文章
三、spring cloud 核心组件:Feign
通过Eureka 组件,现在订单服务可以知道其它微服务的地址和端口,传统的需要我们自己拼接参数、服务地址、端口,调用Http请求,再处理返回结果,这些繁琐的流程以及可读性差的代码,怎么办?这时Feign出现了,如果用Feign组件来调用其它服务怎么写?如下:
//仓库微服务feign定义
@FeignClient(value = "cangku-service")
public interface CangkuService{
@PostMapping("/reduceStock/goodsId")
ResponseEntity<String> reduceStock(@PathVariable Long goodsId);
}
//调用
public class OrderService{
@AutoWired
private CangkuService cangkuService;
public Resutl payOrder(){
cangkuService.reduceStock(goodsId);
}
}
只需要定义的接口上加上@FeignClient注解,然后自动这个service就可以调用里面的请求了,FeignClient是如何办到的
1、Feign关键机制
Feign的关键机制就是用了动态代理。
首先,定义我们需要请求的微服务接口,并添加@FeignClient注解,在IOC容器启动时,就会针对有@FeignClient注解的接口创建一个动态代理对象
其次,@AutoWired注入这个对象,实际上注入的是一个动态代理对象,当调用这个对象的接口,本质就是调用这个代理对象的方法
Feign的动态代理对象会根据你在接口上定义的@RequestMapping等注解,动态构造出微服务的方法地址
最后对这个地址发起请求、解析响应
四、Spring Cloud 核心组件:Ribbon
此时搞活动,仓库微服务部署了5台,那么Feign如何选择要调用哪一台?这时就有了Ribbon,它的作用是负载均衡,会在每次请求时选择一台机器,均匀的把请求分发到各个机器上。
1、Ribbon默认的负载均衡算法
Ribbon默认的负载均衡算法是轮询算法
2、Ribbon和Feign以及Eureka协作,共同完成一次http请求
- 首先Ribbon会从Eureka Client里面获取对应的服务注册表,也就知道了所有服务都部署在哪些机器上,在监听哪些端口
- 接着Ribbon就可以使用默认的轮询算法,从中选择一台机器
- Feign就会对这台机器,构造并发起请求
五、Spring Cloud核心组件:Hystrix
1、什么是服务雪崩
目前订单服务最多接受200个线程,需要调用2个微服务:仓库服务、发货服务。此时发货服务不幸挂了,每次订单服务调用发货服务的时候,会卡住几秒,那么会导致什么问题?
1、大量请求订单服务,订单服务请求发货服务 ,因为发货服务挂了,所以有请求都会卡在请求发货服务这块,很快100个线程都卡在这里,没有一个线程可以处理请求
2、然后导致用户在请求订单的时候,发现订单服务也挂了,不响应任何请求
上面就是微服务架构中的雪崩
问题。
现在微服务间的服务相互调用越来越多,如果不做任何保护,某一个服务挂了,就会引起连锁反应,导致别的服务也挂了。比如发货服务挂了,导致订单服务的线程全部卡在请求发货服务地里,没有一个线程可以去接收新的用户请求,导致订单服务也挂了,请求订单服务也会卡住,无法响应。
2、微服务调用导致的雪崩如何解决?
现在只是发货服务挂了,那么如何让订单服务不受影响? 直接对发货服务熔断不就得了,比如5分钟内请求发货服务就直接返回,不要去走网络请求,这个过程就是熔断
,对自己的一个保护机制。
但仅仅这样不去请求发货服务还不够,用户响应怎么办,我们可以先把需要发货的记录记下来,等发货服务恢复了,再去调用发货服务,同时响应客户:稍后发货。这个过程,就是服务降级
六、Spring Cloud 核心组件:Zuul
Zuul微服务网关。这个组件负责网络路由。
现在后台部署了几百个服务,现在有个前端VUE服务,现在要请求订单服务、用户个人信息服务等,调不同的服务都要构造不同的ip?
上面调用情况太乱,所以一般微服务架构中都必然会设计一个网关在里面,像h5,pc等等,不用关心后台有多微服务,就知道一个网关,所有请求都走网关,网关会根据请求中的一些特征,将请求转发给后端的各个服务。
网关的作用不止转发,还有很多好外,比如可以做统一降级、限流、、认证授权、安全,等等。
七、总结
- Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka
Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里 - Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
- Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求,解析响应
- Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
- Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务,Zuul还有作统一认证授权、安全等
参考原文:
https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247483759&idx=1&sn=7e6575861a779711a5ef5f27b8e088e4&chksm=fba6e96cccd1607a709c1437eb0b92df703b8d8eac466205798618480abf600b67f032782e88&scene=21#wechat_redirect