1. 服务注册和发现Eureka
包含3种角色:
-
Register Service:服务注册中心,它是一个 Eureka Server,提供服务注册和发现的功能。
-
Provider Service:服务提供者,它是一个 Eureka Client,提供服务。
-
Consumer Service:服务消费者,它是 一个 Eureka Cient,消费服务。
1.1 服务消费的基本过程
首先需要一个服务注册中心 Eureka Server,服务提供者 Eureka Client 向服务注册中心 Eureka Server 注册,将自己的信息(比如服务名和服务的 IP地址等) 通过REST API的形式提交给服务注册中心 Eureka Server。同样,服务消费者 Eureka Client也向服务注册中心Eureka Server 注册,同时服务消费者获取一份服务注册列表的信息 ,该列表包含了所有向服务注册中心Eureka Server注册的服务信息 获取服务注册列表信息之后 ,服务消费者就知道服务提供者的 IP地址,可以通过http远程调用来消费服务提供者的服务。
1.2 主要概念
服务注册(register)
eureka client向server注册时,提供自身的元数据,比如ip地址、端口等
服务续约(renew)
eureka client在默认情况下会每隔30秒发送一次心跳来进行服务续约。通过服务续约来告知server该client仍然可用,没有出现故障。正常时间如果server在90秒内没有收到client的心跳,server会将client实例从注册列表中删除。
获取服务注册列表信息(fetch registries)
client从server获取服务注册表信息,将其缓存在本地,并根据服务注册列表信息查找其他服务的信息,从而进行远程调用。
服务下线(cancel)
client在程序关闭时可以向server发送下线请求。
服务剔除(eviction)
默认情况下,client连续90秒没有向server发送服务续约(即心跳)时,server会将服务从服务注册列表删除,即服务剔除。
1.3 高可用架构
eureka高可用架构如下图所示:
1.4 Eureka的高性能设计
掘金-【双11狂欢的背后】微服务注册中心如何承载大型系统的千万级访问?
2. 负载均衡Ribbon
负载均衡是指将负载分摊到多个执行单元上,常见的负载均衡有两种方式。一种是独立进程单元,通过负载均衡策略,将请求转发到不同的执行单元上,例如 Ngnix。另一种是将负载均衡逻辑以代码的形式封装到服务消费者的客户端上服务消费者客户端维护了一份服务提供者的信息列表 ,有了信息列表,通过负载均衡策略将请求分摊给多个服务提供者,从而达到负载均衡的目的,如Ribbon。
在Spring Cloud构建的微服务系统中, Ribbon作为服务消费者的负载均衡器,有两种使用方式,一种是和RestTemplate相结合,另一种是和Feign相结合。
负载均衡器的核心类为 LoadBalancerClient, LoadBalancerClient可以获取负载均衡的服务提供者的实例信息。LoadBalancerClient的choose(”eureka-client'’)方法可以轮流得到 eureka-client 的多个服务实例的信息。那么负载均衡器是怎么获取到这些客户端的信息的呢?查看官方文档可以知道 ,负载均衡器 LoadBalancerClient是从 Eureka Client获取服务注册列表信息的,并将服务注册列表信息缓存了一份。 在 LoadBalancerClient 调用 choose()方法时,根据负载均衡策略选择一个服务实例的信息,从而进行了负载均衡。 LoadBalancerClient也可以不从 Eureka Client获取注册列表信息, 这时需要自己维护一份服务注册列表信息。如果禁止Ribbon从Eureka获取注册列表信息(配置项ribbon.eureka.enabled=false),则需要自己去维护一份服务注册列表信息, 根据自己维护服务注册列表的信息, Ribbon也可以实现负载均衡。
Ribbon的负载均衡主要是通过LoadBalancerClient来实现的,而 LoadBalancerClient具体交给了ILoadBalancer来处理, ILoadBalancer通过配置IRule、IPing等,向EurekaClient获取注册列表的信息,默认每10秒向EurekaClient发送一次“ping”, 进而检查是否需要更新服务的注册列表信息。最后, 在得到服务注册列表信息后,ILoadBalancer根据IRule 的策略进行负载均衡。
3. 声明式调用Feign
Feign是一个伪Java Http客户端,Feign不做任何的请求处理。Feign通过处理注解生成Request模板,从而简化了Http API的开发。开发人员可以使用注解的方式定制Request API模板。在发送Http Request请求之前 ,Feign通过处理注解的方式替换掉Request模板中的参数, 生成真正的Request,并交给 Java Http客户端去处理 。 利用这种方式,开发者只需要关注 Feign注解模板的开发 ,而不用关注Http请求本身,简化了Http请求的过程 ,使得Http请求变得简单和容易理解。
在Feign中,Client是一个非常重要的组件,Feign最终发送Request请求以及接收Response响应都是由Client组件完成的。Client在Feign源码中是一个接口,在默认的情况下,Client的实现类是Client.Default, Client.Default是由HttpURLConnnection来实现网络请求的。另外,Client还支持 HttpClient和OkHttp来进行网络请求。
Feign如何实现负载均衡?最终负载均衡交给loadBalancerContext来处理,即Ribbon。
Feign的源码实现过程如下
(1)首先通过@EnableFeignClients注解开启FeignClient的功能。只有这个注解存在,才会在程序启动时开启对@FeignClient注解的包扫描。
(2)根据Feign的规则实现接口,并在接口上面加上@FeignClient注解。
(3)程序启动后,会进行包扫描,扫描所有的@FeignClient的注解的类,并将这些信息注入IoC容器中。
(4)当接口的方法被调用时,通过JDK的代理来生成具体的RequestTemplate模板对象。
(5)根据RequestTemplate再生成Http请求的Request对象。
(6)Request对象交给Client去处理,其中Client的网络请求框架可以是HttpURLConnection、HttpClient和OkHttp。
(7)最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。
4. 路由网关SpringCloud Zuul
为什么需要Zuul
(1)Zuul、Ribbon以及Eureka相结合,可以实现智能路由和负载均衡的功能,Zuul能够将请求流量按某种策略分发到集群状态的多个服务实例。
(2)网关将所有服务的API接口统一聚合,并统一对外暴露。外界系统调用API接口时,都是由网关对外暴露的API接口,外界系统不需要知道微服务系统中各服务相互调用的复杂性。微服务系统也保护了其内部微服务单元的API接口,防止其被外界直接调用,导致服务的敏感信息对外暴露。
(3)网关服务可以做用户身份认证和权限认证,防止非法请求操作API接口,对服务器起到保护作用。
(4)网关可以实现监控功能,实时日志输出,对请求进行记录。网关可以用来实现流量监控,在高流量的情况下,对服务进行降级。
(5)API接口从内部服务分离出来,方便做测试。
Zuul的工作原理
Zuul是通过Servlet 来实现的,Zuul通过自定义的ZuulServlet (类似于 Spring MVC的DispatcServlet〕来对请求进行控制。Zuul的核心是一系列过滤器,可以在Http请求的发起和响应返回期间执行一系列的过滤器。Zuul包括以下4种过滤器。
-
PRE过滤器:它是在请求路由到具体的服务之前执行的,这种类型的过滤器可以做安全验证 ,例如身份验证、参数验证等。
-
ROUTING过滤器:它用于将请求路由到具体的微服务实例。在默认情况下, 它使用Http Client进行网络请求。
-
POST 过滤器:它是在请求己被路由到微服务后执行的。一般情况下,用作收集统计信息、指标,以及将响应传输到客户端 。
-
ERROR 过滤器:它是在其他过滤器发生错误时执行的。
Zuul请求的生命周期如下图所示:
如果服务存在多个实例,Zuul结合Ribbon会做负载均衡,将请求分发路由到不同的服务实例。如果不需要用Ribbon做负载均衡,可以指定服务实例的url,直接访问指定的url(在实际开发中不可取),如果指定url,并且想做负载均衡,那么就需要自己维护负载均衡的服务注册列表,首先将ribbon.eureka.enabled设置为false,即Ribbon负载均衡客户端不向Eureka Client获取服务注册列表信息,然后需要自己维护一份注册列表。
在Zuul上配置熔断器,可以与Ribbon、Eureka和Hystirx等组件相结合,实现负载均衡、熔断器的功能。
Zuul的常见使用方式,Zuul是采用了类似于Spring MVC 的 DispatchServlet来实现的,采用的是异步阻塞模型,所以性能比Ngnix差。由于Zuul和其他 Netflix组件可以相互配合、无缝集成 , Zuul很容易就能实现负载均衡、智能路由和熔断器等功能。在大多数情况下,Zuul都是以集群的形式存在的。由于 Zuul的横向扩展能力非常好,所以当负载过高时,可以通过添加实例来解决性能瓶颈。
一种常见的使用方式是对不同的渠道使用不同的Zuul来进行路由,例如移动端共用一个Zuul网关实例 ,Web端用另一个Zuul网关实例,其他的客户端用另外一个Zuul实例进行路由。这种不同的渠边用不同 Zuul实例的架构如下图所示。
另外一种常见的集群是通过Ngnix和Zuul相互结合来做负载均衡。暴露在最外面的是Ngnix主从双热备进行Keepalive, Ngnix经过某种路由策略,将请求路由转发到Zuul集群上, Zuul最终将请求分发到具体的服务上。架构图如下图所示。
5. 整体分析
掘金-拜托!面试请不要再问我Spring Cloud底层原理
掘金-【性能优化之道】每秒上万并发下的Spring Cloud参数优化实战
参考资料
掘金-大白话聊聊Java并发面试问题之微服务注册中心的读写锁优化【石杉的架构笔记】
芋道源码-注册中心 Eureka源码解析——应用实例注册发现(四)之自我保护机制
程序猿DD-微服务架构的基础框架选择:Spring Cloud还是Dubbo?
cnblogs-两大微服务框架Dubbo和SpringCloud的对比
开源中国-服务注册中心,Eureka与Zookeeper比较
芋道源码-对于注册中心,ZooKeeper、Eureka 哪个更合适?
Eureka确保AP,当网络分割故障发生时,只要有一台Eureka还在就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。内置心跳服务、自我保护模式,支持客户端缓存,设计哲学是同时保留”好数据“与”坏数据“总比丢掉任何”好数据“要更好,高可用与可伸缩的服务发现服务。
Zookeeper保证CP,因网络问题使得ZK集群失去master节点是较大概率会发生的事,选举耗时较长且期间整个ZK集群都是不可用的,任何时刻对ZK的访问请求能得到一致的数据结果,作为分布式协调服务其职责就是保证数据(注:配置数据,状态数据)在其管辖下的所有服务之间保持同步、一致。作为一个分布式协同服务,ZooKeeper非常好,但是对于Service发现服务来说就不合适。
由于Eureka本身存在较多缓存,服务状态更新滞后,最常见的状况是:服务下线后状态没有及时更新,服务消费者调用到已下线的服务导致请求失败。
从CAP理论看,Eureka是一个AP系统,优先保证可用性(A)和分区容错性(P),不保证强一致性(C),只保证最终一致性,因此在架构中设计了较多缓存
来源:oschina
链接:https://my.oschina.net/u/2939155/blog/3001394