精华内容
下载资源
问答
  • 服务发现作为一种基础能力被抽象提供,它试图让请求网络服务像调用本地函数一样简单透明。

    问题缘由

    单机时代,传统软件大多是单体/巨石架构(Monolithic)。大家往一个代码仓库提交CODE,这会导致应用膨胀,难以理解和修改,以及扩展受限,无法按需伸缩等诸多问题。单体架构怎么解决多人合作的问题?模块化,对,按功能拆分,模块之间定义编程接口(API),彼此关心功能而不关心实现。

    https://s5.51cto.com/oss/202004/15/bad70899d7fd1af8086045711e60b446.jpg-wh_600x-s_2903070032.jpg

     

    随着时代发展,单机程序遇到了计算力和存储的双重瓶颈,分布式架构应运而生。单体应用通过函数名(标识)便可轻松完成本地函数调用,在分布式系统中,服务(RPC/RESTful API)承担了类似的角色,但请求服务单靠服务名还不够,服务名只是服务能力(服务类型)的标识,还需要指示服务位于网络何处,而部署在云中的服务实例IP是动态分配的,扩缩容、失败和更新则让问题变得更加复杂,静态配置服务实例适应不了新变化,需要更精细化的服务治理能力,为了解决或者说简化这个问题,服务发现作为一种基础能力被抽象和提供,它试图让请求网络服务像调用本地函数一样简单透明。

    https://s3.51cto.com/oss/202004/15/791b87553615f38c29f0cf423b28657a.jpg-wh_600x-s_4109048289.jpg

     

    服务即功能(函数)。只是服务跟网络紧密联系在一起,所有才会出现网络服务这个名词,服务提供者通过网络发布服务,服务使用者通过网络请求服务,分布式系统突破了单机算力和存储的限制,提升了系统稳定性,使得高并发高可用的海量服务成为可能,但这也增加了软件复杂度,引入软件分层、负载均衡、微服务、服务发现/治理、分布式一致性等新的问题和挑战。

    服务发现

    服务分服务提供者(Service Provider)和服务消费者(Service Consumer),如果要提供海量服务能力,单一的服务实例显然是不够的,如果要提供成千上万种服务,则需要有一个地方记录服务名到服务实例列表的映射,所以,有必要引入一个新的角色:服务中介,服务中介维护一个服务注册表(Service Registry),可以把注册表理解为服务字典,key是服务名,value是服务提供实例列表;服务注册表是联系服务提供者和服务消费者的桥梁,它维护服务提供者的最新网络位置等信息,也是服务发现最核心的部分。

    服务启动的时候,把服务信息注册(put)到服务注册表;服务终止的时候,从服务注册表删除(remove)自身的服务信息。

    服务消费者在请求服务的时候,先去服务注册表按名查询(get)服务提供者列表,然后从列表里挑选一个服务实例,向该实例请求服务。

    大道至简,这便是最简单的服务发现模型,也是服务发现的基本原理,至此,似乎一切都OK,但其实尚有几个问题没有说清楚。

    问题和解法

    • 第一个问题,服务如果不是正常停止,而是被系统kill掉,它便没有机会通知服务注册表把自身服务信息删除,这样注册表便多了一条指向无效服务实例的信息,而服务消费者却并不知情,怎么办?解决的办法很简单:保活(keepalive),服务提供者定期(比如每隔10)给服务中介发送keepalive消息,服务中介收到keepalive消息后更新该服务实例的keepalive timestamp,服务中介定期检查该timestamp,如果超期便把该服务实例从注册表剔除。
    • 第二个问题,服务实例列表变化如何通知服务消费者?不外乎两种方法,轮询和pub-sub。轮询是消费者主动询问服务中介服务列表是否变化,如果有变化,则把新的服务列表发送给消费者。如果消费者过多,则服务中介处理轮询的消息会有压力,在服务类别很多,服务列表很大的时候,它甚至会成为瓶颈。pub-sub是服务中介主动通知服务消费者,时效性相比轮询更好,缺点是会占用单独的线程或者连接资源。

     

    https://s4.51cto.com/oss/202004/15/ee0d6bdd6e53313a2dbc07ec2a436b20.jpg

     

    • 第三个问题,服务中介如果挂了怎么办?所以我们要解决单点的问题,通常会用集群来对抗这种脆弱性,有很多用于做服务注册表的开源解决方案,比如etcd/zookeeper/consul,本质上使用分布式一致性数据库来保存注册表信息,它既解决读写性能问题又提高了系统稳定性可用性。
    • 第四个问题,如果服务消费者每次使用远程服务都需要先查询服务中介获取实例列表,再请求服务,这样效率太低效?对服务中介的压力也不小?通常,客户端会缓存服务实例列表,这样对同名服务的多次请求,便不用重复查询,既减少了延迟又减轻了对服务中介的访问压力。
    • 第五个问题,前述的keepalive有间隔,如果在这个间隔内服务实例不可用,那么服务消费者还是不能感知的,所以还是有可能把请求发送到一个无法提供服务的网络远端机器上去,这样自然是没法work。我们无法从根本上杜绝这种情况,系统需要容忍这种错误,但也可以做一些改进,比如向某实例请求服务失败后便拉黑,避免向同一无效服务实例多次派发请求。
    • 第六个问题,服务消费者怎么从多个服务实例里选择一个?如何确保同一服务消费者的多次服务请求被分配到固定的服务实例(有时候需要这样)?这其实就是负载均衡的问题,有多种策略,比如rr、优先级、比如加权随机、一致性哈希。

    服务发现模式

    服务发现主要有两种模式:客户端发现模式(client-side discovery)和服务端发现模式(server-side discovery)

    客户端发现模式

    https://s3.51cto.com/oss/202004/15/114db98baef5fd82aaa4946cbc5e01da.png

     

    客户端负责查询服务实例列表并决定向哪个实例请求服务,也就是负载均衡策略在客户端实现。该模式包括注册和发现两个部分。

    服务实例调用服务中介的注册接口进行实例注册,服务实例通过keepalive做服务续期,服务中介通过健康检查剔除不可用的服务实例。

    服务消费者请求服务的时候,先向服务注册表查询服务实例列表,注册表是一个服务数据库,为了提升性能和可靠性,客户端通常会缓存服务列表(缓存用来确保注册表挂了之后还能继续工作),拿到实例列表后客户端基于负载均衡策略挑选一个实例发送服务请求。

    优点

    • 直接,客户端可以灵活的执行负载均衡策略。
    • 去中心化,非网关式,有效避开单点瓶颈和可靠性下降。
    • 服务发现直接SDK集成进客户端,这种语言整合程度很好,程序执行性能也很好,排错方便。

    缺点

    • 客户端与服务注册表耦合,需要为服务客户端使用的每种语言每种框架开发服务发现逻辑。
    • 这种侵入式的集成会导致任何服务发现的变化都需要客户端应用程序重新编译和部署,强绑定违背了独立性原则。
    • 服务上下线会对调用方有影响,导致服务短暂不可用。

    服务端发现模式

    https://s2.51cto.com/oss/202004/15/3e258e0f09245fc5313322f96bcf7a2e.png

     

    发现:服务消费者通过负载均衡器发送服务请求,负载均衡器会查询服务注册表,挑选一个服务实例,并将请求转发到服务实例。

    注册:服务注册/注销可以跟上述客户端发现模式一致,也可以通过部署平台的内置服务注册和发现机制完成,即容器化部署平台(docker/k8s)能主动发现服务实例并帮助服务实例完成注册注销。

    对比客户端发现模式,使用服务端发现模式的客户端本地不保存服务实例列表,客户端不做负载均衡,这个负载均衡器既承担了服务发现的角色,又承担了网关的角色,所以经常叫API网关服务器。

    因为负载均衡器是中心式的,所以它也必须是一个集群,单个实例不足以支撑高并发访问,针对负载均衡器本身的服务发现和负载均衡通常借助DNS

    Http服务器,NginxNginx Plus就是此类服务端发现模式的负载均衡器。

    优点

    • 服务发现对于服务消费者是透明的,服务消费者与注册表解耦,服务发现功能的更新对客户端无感知。
    • 服务消费者只需要向负载均衡器发送请求,不需要为每种服务消费者的编程语言和框架,开发服务发现逻辑SDK

    缺点

    • 由于所有请求都要经负载均衡器转发,所以负载均衡器有可能成为新的性能瓶颈。
    • 负载均衡器(服务网关)是中心式的,而中心式的架构会有稳定性的隐忧。
    • 因为负载均衡器转发请求,所以RT会比客户端直连模式高。

    微服务和服务发现

    Service Mesh服务网格是服务于微服务应用程序的可配置基础设施层,旨在处理服务之间的大量基于网络的进程间通信。

    https://s2.51cto.com/oss/202004/15/04c9670ec9d101617feb8e301f005152.jpg-wh_600x-s_1915060129.jpg

     

    Service Mesh服务网关解耦调用和通信,在非mesh下,对于协议的感知和服务发现方法的感知需要应用去做,用mesh之后,就只管调用,mesh通过控制面来控制应用的数据流。

    Mesh做服务发现其实是客户端发现模式的升级版,基于sidecarpilot实现,Sidecars,即数据面板(Data Plane),负责发现目标服务实例地址列表并转发请求。Pilots,即控制面板(Control Plane),负责管理服务注册表的所有服务注册信息。

    服务注册模式

    一个选择是服务实例自注册,即self-registration模式。另一种选择是其它的系统组件来管理服务实例的注册,即third-party registration模式。

    自注册模式如前面所述,它足够简单,不需要第三方组件,缺点是必须为服务中用到的每种编程语言与框架实现注册代码。

    第三方注册服务实例不会自己完成注册注销,它由另一个叫做Service Registrar的系统组件负责,该组件会轮询部署环境或者跟踪订阅事件去感知服务实例的变化,帮助服务实例完成自动化注册注销。

    Third-party registration模式主要的优势在于解耦了服务和服务注册表。不需要为每个语言和框架都实现服务注册逻辑。服务实例注册由一个专用的服务集中实现。缺点是除了被内置到部署环境中,它本身也是一个高可用的系统组件,需要被启动和管理。

    其他

    如果某个服务对于的服务实例特别多,比如在一些头部公司,一个服务名可能对应几千几万个服务实例,这样,服务变更的查询和对比会很慢,IO的量会大得超过想象,通常,会用version num去解决这个问题。

    展开全文
  • 1、微服务为什么要用网关?...1、客户端需要知道每个服务的地址(如果有网关,分布式部署的话这样可以统一api的ip地址,再由网关去分发,可以通过注册中心获取你需要的服务节点列表,然后给你分配你调用那个

    1、微服务为什么要用网关?(首先要理解网关并不是必须的组件,只是一种设计模式或者设计理念)

    客户端直接访问各子服务:

    微服务刚刚诞生的时候,人们将服务进行拆分,实现服务之间的松耦合,并且每个服务有专门的团队维护,然后客户端直接和各个子服务进行交互。比如,订单,商品,会员服务。

    这种客户端直接和后端服务交互的方式会有什么问题呢?

    1、客户端需要知道每个服务的地址(如果有网关,分布式部署的话这样可以统一api的ip地址,再由网关去分发,可以通过注册中心获取你需要的服务节点列表,然后给你分配你调用那个节点去调用)

    2、每个后端服务都需要实现认证、限流、日志、监控、缓存等功能,重复造轮子大大降低了开发效率,而这些公共业务逻辑完全可以拆分出来。而且会造成的代码复杂度和维护难度上升。

    3、假如后端某些服务由之前的http/https调用变成rpc调用,或者某些参数发生改变,则客户端需要做很大调整。

    这里我觉得还有必要补一篇rpc远程调用和restful请求的区别,这里简单科普一下:

    rpc一般用于内部微服务之间的调用,restful请求也就是http请求一般是请求外部服务;

    rpc可以像调用本地方法一样去调用其它微服务(在不同的服务器)的方法,原理是一般是cp/ip协议+动态代理。这里就减少了向外暴露的服务。而http请求是基于http协议的,需要暴露给外部,而且需要自己封装请求提和请求参数。rpc只是一种概念,有多种实现方式。

     

    引入网关可以怎么解决这些问题呢?

    1、网关作为边界,分割了内部应用和外部调用。

    不同于外部API一般使用HTTP或REST,内部微服务可以从使用不用通讯协议中收获益处。这些协议可以是ProtoBuf或AMQP,甚至是诸如SOAP,JSON-RPC或者XML-RPC这样的系统集成协议。API网关可以对这些协议提供统一的外部REST接口,这就允许开发团队挑选一款最适合于内部架构的协议。

     

    2、网关实现了安全层,降低了各子微服务的复杂度

    API网关通过提供额外的安全层帮助阻止大规模攻击。这些攻击包括SQL注入,XML解析漏洞和DoS攻击。

    微服务中有一些常见的要点,诸如使用API令牌进行授权,访问控制和调用频次限制。这每个点都需要每个服务区增加额外的时间去实现它们。API网关将这些要点从你的代码中提取出来,允许你的服务只关注于它们需要关注的任务。同时可以作为统一收集微服务日志的地方,方便了问题的定位。

     

    3、微服务方便了服务的管理,提供了外部请求的统一入口,实现了路由转发,同时降低了对外暴露的服务

    如果一个业务功能,调用了n个外部微服务,那边管理和维护起来简直是噩梦,二微服务万贯基于统一的域名和上下文去访问,管理起来更加方便。同时网关还可以实现路由转发和负载均衡的功能,但是并不是最佳的选择,因为有其它开源组件比它更nb,那就是nginx。

    2、 有了网关为什么还需要nginx或者ribbon作负载均衡?

    你可以理解为基于性能问题。

    3、有了网关为什么还要需要有服务注册和发现?

    让我们来看看一个同时有网关和服务发现注册中心的请求路径是怎样的?

    一个请求过来了,根据网关根据路由规则分析出是请求的哪个服务,然后跟注册中心说:这个XX服务有木有?没有?那404!有?服务下有几个实例,都告诉我我自己找一个或者你告诉我一个能用的。然后把请求往某个服务的某个实例上发送,得到返回值后丢给客户端。       这样就不会出现,某个服务一直被调而一直在等待响应最终造成服务器挂掉的风险。网关保障的是微服务之间的安全和解耦,注册中心是保障微服务的高可用和可靠。但是网关的配置最好更高一点,不然也是一种风险。或者在网关前面挂一个nginx代理,多几套网关实例也行。

    展开全文
  • 问题缘由单机时代,传统软件大多是单体/巨石架构(Monolithic)。大家往一个代码仓库提交CODE,这会导致应用膨胀,难以理解修改,以及扩展受限,无法按需伸缩等诸多问题。单体架构怎...

    问题缘由

    单机时代,传统软件大多是单体/巨石架构(Monolithic)。大家往一个代码仓库提交CODE,这会导致应用膨胀,难以理解和修改,以及扩展受限,无法按需伸缩等诸多问题。单体架构怎么解决多人合作的问题?模块化,对,按功能拆分,模块之间定义编程接口(API),彼此关心功能而不关心实现

    随着时代发展,单机程序遇到了计算力和存储的双重瓶颈,分布式架构应运而生。单体应用通过函数名(标识)便可轻松完成本地函数调用,在分布式系统中,服务(RPC/RESTful API)承担了类似的角色,但请求服务单靠服务名还不够,服务名只是服务能力(服务类型)的标识,还需要指示服务位于网络何处,而部署在云中的服务实例IP是动态分配的,扩缩容、失败和更新则让问题变得更加复杂,静态配置服务实例适应不了新变化,需要更精细化的服务治理能力,为了解决或者说简化这个问题,服务发现作为一种基础能力被抽象和提供,它试图让请求网络服务像调用本地函数一样简单透明。

    服务即功能(函数)。只是服务跟网络紧密联系在一起,所有才会出现网络服务这个名词,服务提供者通过网络发布服务,服务使用者通过网络请求服务,分布式系统突破了单机算力存储的限制,提升了系统稳定性,使得高并发高可用的海量服务成为可能,但这也增加了软件复杂度,引入软件分层、负载均衡、微服务、服务发现/治理、分布式一致性等新的问题和挑战。

    服务发现

    服务分服务提供者(Service Provider)和服务消费者(Service Consumer),如果要提供海量服务能力,单一的服务实例显然是不够的,如果要提供成千上万种服务,则需要有一个地方记录服务名到服务实例列表的映射,所以,有必要引入一个新的角色:服务中介,服务中介维护一个服务注册表(Service Registry),可以把注册表理解为服务字典,key是服务名,value是服务提供实例列表;服务注册表是联系服务提供者和服务消费者的桥梁,它维护服务提供者的最新网络位置等信息,也是服务发现最核心的部分。

    服务启动的时候,把服务信息注册(put)到服务注册表;服务终止的时候,从服务注册表删除(remove)自身的服务信息。

    服务消费者在请求服务的时候,先去服务注册表按名查询(get)服务提供者列表,然后从列表里挑选一个服务实例,向该实例请求服务。

    大道至简,这便是最简单的服务发现模型,也是服务发现的基本原理,至此,似乎一切都OK,但其实尚有几个问题没有说清楚。

    问题和解法

    - 第一个问题,服务如果不是正常停止,而是被系统kill掉,它便没有机会通知服务注册表把自身服务信息删除,这样注册表便多了一条指向无效服务实例的信息,而服务消费者却并不知情,怎么办?解决的办法很简单:保活(keepalive),服务提供者定期(比如每隔10秒)给服务中介发送keepalive消息,服务中介收到keepalive消息后更新该服务实例的keepalive timestamp,服务中介定期检查该timestamp,如果超期便把该服务实例从注册表剔除。

    - 第二个问题,服务实例列表变化如何通知服务消费者?不外乎两种方法,轮询和pub-sub。轮询是消费者主动询问服务中介服务列表是否变化,如果有变化,则把新的服务列表发送给消费者。如果消费者过多,则服务中介处理轮询的消息会有压力,在服务类别很多,服务列表很大的时候,它甚至会成为瓶颈。pub-sub是服务中介主动通知服务消费者,时效性相比轮询更好,缺点是会占用单独的线程或者连接资源。

    - 第三个问题,服务中介如果挂了怎么办?所以我们要解决单点的问题,通常会用集群来对抗这种脆弱性,有很多用于做服务注册表的开源解决方案,比如etcd/zookeeper/consul,本质上使用分布式一致性数据库来保存注册表信息,它既解决读写性能问题又提高了系统稳定性可用性。

    - 第四个问题,如果服务消费者每次使用远程服务都需要先查询服务中介获取实例列表,再请求服务,这样效率太低效?对服务中介的压力也不小?通常,客户端会缓存服务实例列表,这样对同名服务的多次请求,便不用重复查询,既减少了延迟又减轻了对服务中介的访问压力。

    - 第五个问题,前述的keepalive有间隔,如果在这个间隔内服务实例不可用,那么服务消费者还是不能感知的,所以还是有可能把请求发送到一个无法提供服务的网络远端机器上去,这样自然是没法work。我们无法从根本上杜绝这种情况,系统需要容忍这种错误,但也可以做一些改进,比如向某实例请求服务失败后便拉黑,避免向同一无效服务实例多次派发请求。

    - 第六个问题,服务消费者怎么从多个服务实例里选择一个?如何确保同一服务消费者的多次服务请求被分配到固定的服务实例(有时候需要这样)?这其实就是负载均衡的问题,有多种策略,比如rr、优先级、比如加权随机、一致性哈希。

    服务发现模式

    服务发现主要有两种模式:客户端发现模式(client-side discovery)和服务端发现模式(server-side discovery)。

    客户端发现模式

    客户端负责查询服务实例列表并决定向哪个实例请求服务,也就是负载均衡策略在客户端实现。该模式包括注册和发现两个部分。

    服务实例调用服务中介的注册接口进行实例注册,服务实例通过keepalive做服务续期,服务中介通过健康检查剔除不可用的服务实例。

    服务消费者请求服务的时候,先向服务注册表查询服务实例列表,注册表是一个服务数据库,为了提升性能和可靠性,客户端通常会缓存服务列表(缓存用来确保注册表挂了之后还能继续工作),拿到实例列表后客户端基于负载均衡策略挑选一个实例发送服务请求。

    优点

    • 直接,客户端可以灵活的执行负载均衡策略。

    • 去中心化,非网关式,有效避开单点瓶颈和可靠性下降。

    • 服务发现直接SDK集成进客户端,这种语言整合程度最佳,程序执行性能最好,排错方便。

    缺点

    • 客户端与服务注册表耦合,需要为服务客户端使用的每种语言每种框架开发服务发现逻辑。

    • 这种侵入式的集成会导致任何服务发现的变化都需要客户端应用程序重新编译和部署,强绑定违背了独立性原则。

    • 服务上下线会对调用方有影响,导致服务短暂不可用。

    服务端发现模式

    发现:服务消费者通过负载均衡器发送服务请求,负载均衡器会查询服务注册表,挑选一个服务实例,并将请求转发到服务实例。

    注册:服务注册/注销可以跟上述客户端发现模式一致,也可以通过部署平台的内置服务注册和发现机制完成,即容器化部署平台(docker/k8s)能主动发现服务实例并帮助服务实例完成注册注销。

    对比客户端发现模式,使用服务端发现模式的客户端本地不保存服务实例列表,客户端不做负载均衡,这个负载均衡器既承担了服务发现的角色,又承担了网关的角色,所以经常叫API网关服务器

    因为负载均衡器是中心式的,所以它也必须是一个集群,单个实例不足以支撑高并发访问,针对负载均衡器本身的服务发现和负载均衡通常借助DNS。

    Http服务器,Nginx、Nginx Plus就是此类服务端发现模式的负载均衡器。

    优点

    • 服务发现对于服务消费者是透明的,服务消费者与注册表解耦,服务发现功能的更新对客户端无感知。

    • 服务消费者只需要向负载均衡器发送请求,不需要为每种服务消费者的编程语言和框架,开发服务发现逻辑SDK。

    缺点

    • 由于所有请求都要经负载均衡器转发,所以负载均衡器有可能成为新的性能瓶颈。

    • 负载均衡器(服务网关)是中心式的,而中心式的架构会有稳定性的隐忧。

    • 因为负载均衡器转发请求,所以RT会比客户端直连模式高。

    微服务和服务发现

    Service Mesh服务网格是服务于微服务应用程序的可配置基础设施层,旨在处理服务之间的大量基于网络的进程间通信。

    Service Mesh服务网关解耦调用和通信,在非mesh下,对于协议的感知和服务发现方法的感知需要应用去做,用mesh之后,就只管调用,mesh通过控制面来控制应用的数据流。

    Mesh做服务发现其实是客户端发现模式的升级版,基于sidecar和pilot实现,Sidecars,即数据面板(Data Plane),负责发现目标服务实例地址列表并转发请求。Pilots,即控制面板(Control Plane),负责管理服务注册表的所有服务注册信息。

    服务注册模式

    一个选择是服务实例自注册,即self-registration模式。另一种选择是其它的系统组件来管理服务实例的注册,即third-party registration模式。

    自注册模式如前面所述,它足够简单,不需要第三方组件,缺点是必须为服务中用到的每种编程语言与框架实现注册代码。

    第三方注册服务实例不会自己完成注册注销,它由另一个叫做Service Registrar的系统组件负责,该组件会轮询部署环境或者跟踪订阅事件去感知服务实例的变化,帮助服务实例完成自动化注册注销。

    Third-party registration模式主要的优势在于解耦了服务和服务注册表。不需要为每个语言和框架都实现服务注册逻辑。服务实例注册由一个专用的服务集中实现。缺点是除了被内置到部署环境中,它本身也是一个高可用的系统组件,需要被启动和管理。

    其他

    如果某个服务对于的服务实例特别多,比如在一些头部公司,一个服务名可能对应几千几万个服务实例,这样,服务变更的查询和对比会很慢,IO的量会大得超过想象,通常,会用version num去解决这个问题。

    展开全文
  • 简介:RSocket 作为通讯协议的后起之秀,核心是二进制异步化消息通讯,是否也能 Spring Cloud 技术栈结合,实现服务注册发现、客户端负载均衡,从而更高效地实现面向服务的架构?这篇文章我们就讨论一下 Spring ...
    简介:RSocket 作为通讯协议的后起之秀,核心是二进制异步化消息通讯,是否也能和 Spring Cloud 技术栈结合,实现服务注册发现、客户端负载均衡,从而更高效地实现面向服务的架构?这篇文章我们就讨论一下 Spring Cloud 和 RSocket 结合实现服务注册发现和负载均衡。

    头图.png

    作者 | 雷卷
    来源|阿里巴巴云原生公众号

    RSocket 分布式通讯协议是 Spring Reactive 的核心内容,从 Spring Framework 5.2 开始,RSocket 已经是 Spring 的内置功能,Spring Boot 2.3 也添加了 spring-boot-starter-rsocket,简化了 RSocket 的服务编写和服务调用。RSocket 通讯的核心架构中包含两种模式,分别是 Broker 代理模式和服务直连通讯模式。

    Broker 的通讯模式更灵活,如 Alibaba RSocket Broker,采用的是事件驱动模型架构。而目前更多的架构则是面向服务化设计,也就是我们常说的服务注册发现和服务直连通讯的模式,其中最知名的就是 Spring Cloud 技术栈,涉及到配置推送、服务注册发现、服务网关、断流保护等等。在面向服务化的分布式网络通讯中,如 REST API、gRPC 和 Alibaba Dubbo 等,都与 Spring Cloud 有很好地集成,用户基本不用关心服务注册发现和客户端负载均衡这些底层细节,就可以完成非常稳定的分布式网络通讯架构。

    RSocket 作为通讯协议的后起之秀,核心是二进制异步化消息通讯,是否也能和 Spring Cloud 技术栈结合,实现服务注册发现、客户端负载均衡,从而更高效地实现面向服务的架构?这篇文章我们就讨论一下 Spring Cloud 和 RSocket 结合实现服务注册发现和负载均衡。

    服务注册发现

    服务注册发现的原理非常简单,主要涉及三种角色:服务提供方、服务消费者和服务注册中心。典型的架构如下:

    1.png

    服务提供方,如 RSocket Server,在应用启动后,会向服务注册中心注册应用相关的信息,如应用名称,ip 地址,Web Server 监听端口号等,当然还会包括一些元信息,如服务的分组(group),服务的版本号(version),RSocket 的监听端口号,如果是 WebSocket 通讯,还需要提供 ws 映射路径等,不少开发者会将服务提供方的服务接口列表作为 tags 提交给服务注册中心,方便后续的服务查询和治理。

    在本文中,我们采用 Consul 作为服务注册中心,主要是 Consul 比较简单,下载后执行 consul agent -dev 就可以启动对应的服务,当然你可以使用 Docker Compose,配置也非常简单,然后 docker-compose up -d 就可以启动 Consul 服务。

    当我们向服务中心注册和查询服务时,都需要有一个应用名称,对应到 Spring Cloud 中,也就是 Spring Boot 对应的 spring.application.name 的值,这里我们称之为应用名称,也就是后续的服务查找都是基于该应用名称进行的。如果你调用 ReactiveDiscoveryClient.getInstances(String serviceId); 查找服务实例列表时,这个 serviceId 参数其实就是 Spring Boot 的应用名称。考虑到服务注册和后续的 RSocket 服务路由的配合以及方便大家理解,这里我们打算设计一个简单的命名规范。

    假设你有一个服务应用,功能名称为 calculator,同时提供两个服务: 数学计算器服务(MathCalculatorService)和汇率计算器服务(ExchangeCalculatorService),  那么我们该如何来命名该应用及其对应的服务接口名?

    这里我们采用类似 Java package 命名规范,采用域名倒排的方式,如 calculator 应用对应的则为 com-example-calculator 样式,为何是中划线,而不是点?. 在 DNS 解析中作为主机名是非法的,只能作为子域名存在,不能作为主机名,而目前的服务注册中心设计都遵循 DNS 规约,所以我们采用中划线的方式来命名应用。这样采用域名倒排和应用名结合的方式,可以确保应用之间不会重名,另外也方便和 Java Package 名称进行转换,也就是 -  和 . 之间的相互转换。

    那么应用包含的服务接口应该如何命名?服务接口全名是由应用名称和 interface 名称组合而成,规则如下:

    String serviceFullName = appName.replace("-", ".") + "." + serviceInterfaceName; 

    例如以下的服务命名都是合乎规范的:

    • com.example.calculator.MathCalculatorService
    • com.example.calculator.ExchangeCalculatorService

    com.example.calculator.math.MathCalculatorService 则是错误的,  因为在应用名称和接口名称之间多了 math。为何要采用这种命名规范?首先让我们看一下服务消费方是如何调用远程服务的。假设服务消费方拿到一个服务接口,如 com.example.calculator.MathCalculatorService,那么他该如何发起服务调用呢?

    • 首先根据 Service 全面提取处对应的应用名称(appName),如 com.example.calculator.MathCalculatorService 服务对应的 appName 则为 com-example-calculator。如果应用和服务接口之间不存在任何关系,那么想要获取服务接口对应的服务提供方信息,你可能还需要应用名称,这会相对来说比较麻烦。如果接口名称中包含对应的应用信息,则会简单很多,你可以理解为应用是服务全面中的一部分。
    • 调用 ReactiveDiscoveryClient.getInstances(appName) 获取应用名对应的服务实例列表(ServiceInstance),ServiceInstance 对象会包含诸如 IP 地址,Web 端口号、RSocket 监听端口号等其他元信息。
    • 根据 RSocketRequester.Builder.transports(servers) 构建具有负载均衡能力的 RSocketRequester 对象。
    • 使用服务全称和具体功能名称作为路由进行 RSocketRequester 的 API 调用,样例代码如下:

    rsocketRequester .route("com.example.calculator.MathCalculatorService.square")  .data(number)  .retrieveMono(Integer.class)

    通过上述的命名规范,我们可以从服务接口全称中提取出应用名,然后和服务注册中心交互查找对应的实例列表,然后建立和服务提供者的连接,最后基于服务名称进行服务调用。该命名规范,基本做到到了最小化的依赖,开发者完全是基于服务接口调用,非常简单。

    RSocket 服务编写

    有了服务的命名规范和服务注册,编写 RSocket 服务,这个还是非常简单,和编写一个 Spring Bean 没有任何区别。引入 spring-boot-starter-rsocket 依赖,创建一个 Controller 类,添加对应的 MessagMapping annotation 作为基础路由,然后实现功能接口添加功能名称,样例代码如下:

    @Controller @MessageMapping("com.example.calculator.MathCalculatorService") public class MathCalculatorController implements MathCalculatorService {     @MessageMapping("square")     public Mono<Integer> square(Integer input) {         System.out.println("received: " + input);         return Mono.just(input * input);     } }

    上述代码看起来好像有点奇怪,既然是服务实现,添加 @Controller 和 @MessageMapping,看起来好像有点不伦不类的。当然这些 annotation 都是一些技术细节体现,你也能看出,RSocket 的服务实现是基于 Spring Message 的,是面向消息化的。这里我们其实只需要添加一个自定义的 @SpringRSocketService annotation 就可以解决这个问题,代码如下:

    @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Controller @MessageMapping() public @interface SpringRSocketService {     @AliasFor(annotation = MessageMapping.class)     String[] value() default {}; }

    回到服务对应的实现代码,我们改为使用 @SpringRSocketService annotation,这样我们的代码就和标准的 RPC 服务接口完全一模一样啦,也便于理解。此外 @SpringRSocketService 和 @RSocketHandler 这两个 Annotation,也方便我们后续做一些 Bean 扫描、IDE 插件辅助等。

    @SpringRSocketService("com.example.calculator.MathCalculatorService") public class MathCalculatorImpl implements MathCalculatorService {     @RSocketHandler("square")     public Mono<Integer> square(Integer input) {         System.out.println("received: " + input);         return Mono.just(input * input);     } }

    最后我们添加一下 spring-cloud-starter-consul-discovery 依赖,设置一下 bootstrap.properties,然后在 application.properties 设置一下 RSocket 监听的端口和元信息,我们还将该应用提供的服务接口列表作为 tags 传给服务注册中心,当然这个也是方便我们后续的服务管理。样例如下:

    spring.application.name=com-example-calculator spring.cloud.consul.discovery.instance-id=com-example-calculator-${random.uuid} spring.cloud.consul.discovery.prefer-ip-address=true server.port=0 spring.rsocket.server.port=6565 spring.cloud.consul.discovery.metadata.rsocketPort=${spring.rsocket.server.port} spring.cloud.consul.discovery.tags=com.example.calculator.ExchangeCalculatorService,com.example.calculator.MathCalculatorService

    RSocket 服务应用启动后,我们在 Consul 控制台就可以看到服务注册上来的信息,截屏如下:

    2.png

    RSocket 客户端接入

    客户端接入稍微有一点复杂,主要是要基于服务接口全面要做一系列相关的操作,但是前面我们已经有了命名规范,所以问题也不大。客户端应用同样会接入服务注册中心,这样我们就可以获得 ReactiveDiscoveryClient bean,接下来就是根据服务接口全名,如 com.example.calculator.ExchangeCalculatorService 构建出具有负载均衡的 RSocketRequester。

    原理也非常简单,前面说过,根据服务接口全称,获得其对应的应用名称,然后调用 ReactiveDiscoveryClient.getInstances(appName) 获得服务应用对应的实例列表,接下来将服务实例(ServiceInstance)列表转换为 RSockt 的 LoadbalanceTarget 列表,其实就是 POJO 转换,最后将转 LoadbalanceTarget 列表进行 Flux 封装(如使用 Sink 接口),传递给 RSocketRequester.Builder 就完成具有负载均衡能力的 RSocketRequester 构建,详细的代码细节大家可以参考项目的代码库。

    这里要注意的是接下来如何感知服务端实例列表的变化,如应用上下线,服务暂停等。这里我采用一个定时任务方案,定时查询服务对应的地址列表。当然还有其他的机制,如果是标准的 Spring Cloud 服务发现接口,目前是需要客户端轮询的,当然也可以结合 Spring Cloud Bus 或者消息中间件,实现服务端列表变化的监听。如果客户端感知到服务列表的变化,只需要调用 Reactor 的 Sink 接口发送新的列表即可,RSocket Load Balance 在感知到变化后,会自动做出响应,如关闭即将失效的连接、创建新的连接等工作。

    在实际的应用之间的相互通讯,会存在一些服务提供方不可用的情况,如服务方突然宕机或者其网络不可用,这就导致了服务应用列表中部分服务不可用,那么 RSocket 这个时候会如何处理?不用担心,RSocket Load Balance 有重试机制,当一个服务调用出现连接等异常,会重新从列表中获取一个连接进行通讯,而那个错误的连接也会标识为可用性为 0,不会再被后续请求所使用。服务列表推送和通讯期间的容错重试机制,这两者保证了分布式通讯的高可用性。

    最后让我们启动 client-app,然后从客户端发起一个远程的 RSocket 调用,截屏如下:

    3.png

    上图中 com-example-calculator 服务应用包括三个实例,服务的调用会在这三个服务实例交替进行(RoundRobin 策略)。

    开发体验的一些考量

    虽然服务注册和发现、客户端的负载均衡这些都完成啦,调用和容错这些都没有问题,但是还有一些使用体验上的问题,这里我们也阐述一下,让开发体验做的更好。

    1. 基于服务接口通讯

    大多数 RPC 通讯都是基于接口的,如 Apache Dubbo、gRPC 等。那么 RSocket 能否做到?答案是其实完全可以。在服务端,我们已经是基于服务接口来实现 RSocket 服务啦,接下来我们只需要在客户端实现基于该接口的调用就可以。对于 Java 开发者来说,这不是大问题,我们只需要基于 Java Proxy 机制构建就可以,而 Proxy 对应的 InvocationHandler 会使用 RSocketRequester 来实现 invoke() 的函数调用。详细的细节请参考应用代码中的的 RSocketRemoteServiceBuilder.java 文件,而且在 client-app module 中也已经包含了解基于接口调用的 bean 实现。

    2. 服务接口函数的单参数问题

    使用 RSocketRequester 调用远程接口时,对应的处理函数只能接受单个参数,这个和 gRPC 的设计是类似的,当然也考虑了不同对象序列化框架的支持问题。但是考虑到实际的使用体验,可能会涉及到多参函数的情况,让调用方开发体验更好,那么这个时候该如何处理?其实从 Java 1.8 后,interface 是允许增加 default 函数的,我们可以添加一些体验更友好的 default 函数,而且还不影响服务通讯接口,样例如下:

    public interface ExchangeCalculatorService {     double exchange(ExchangeRequest request);     default double rmbToDollar(double amount) {         return exchange(new ExchangeRequest(amount, "CNY", "USD"));     } }

    通过 interface 的 default method,我们可以为调用方提供给便捷函数,如在网络传输的是字节数组 (byte[]),但是在 default 函数中,我们可以添加 File 对象支持,方便调用方使用。Interface 中的函数 API 负责服务通讯规约,default 函数来提升使用方的体验,这两者的配合,可以非常容易解决函数多参问题,当然 default 函数在一定程度上还可以作为数据验证的前哨来使用。

    3. RSocket Broker 支持

    前面我们说到,RSocket 还有一种 Broker 架构,也就是服务提供方是隐藏在 Broker 之后的,请求主要是由 Broker 承接,然后再转发给服务提供方处理,架构样例如下:

    4.png

    那么基于服务发现的机制负载均衡,能否和 RSocket Broker 模式混合使用呢?如一些长尾或者复杂网络下的应用,可以注册到 RSocket Broker,然后由 Broker 处理请求调用和转发。这个其实也不不复杂,前面我们说到应用和服务接口命名规范,这里我们只需要添加一个应用名前缀就可以解决。假设我们有一个 RSocker Broker 集群,暂且我们称之为 broker0 集群,当然该 broker 集群的实例也都注册到服务注册中心(如 Consul)啦。那么在调用 RSocket Broker 上的服务时,服务名称就被调整为 broker0:com.example.calculator.MathCalculatorService,也就是服务名前添加了 appName: 这样的前缀,这个其实是 URI 的另一种规范形式,我们就可以提取冒号之前的应用名,然后去服务注册中心查询获得应用对应的实例列表。 

    回到 Broker 互通的场景,我们会向服务注册中心查询 broker0 对应的服务列表,然后和 broker0 集群的实例列表创建连接,这样后续基于该接口的服务调用就会发送给 Broker 进行处理,也就是完成了服务注册发现和 Broker 模式的混合使用的模式。

    借助于这种定向指定服务接口和应用间的关联,也方便我们做一些 beta 测试,如你想将 com.example.calculator.MathCalculatorService 的调用导流到 beta 应用,你就可以使用 com-example-calculator-beta1:com.example.calculator.MathCalculatorService 这种方式调用服务,这样服务调用对应的流量就会转发给 com-example-calculator-beta1 对应的实例,起到 beta 测试的效果。

    回到最前面说到的规范,如果应用名和服务接口的绑定关系你实在做不到,那么你可以使用这种方式实现服务调用,如 calculator-server:com.example.calculator.math.MathCalculatorService,只是你需要更完整的文档说明,当然这种方式也可以解决之前系统接入到目前的架构上,应用的迁移成本也比较小。如果你之前的面向服务化架构设计也是基于 interface 接口通讯的,那么通过该方式迁移到 RSocket 上完全没有问题,对客户端代码调整也最小。

    总结

    通过整合服务注册发现,结合一个实际的命名规范,就完成了服务注册发现和 RSocket 路由之间的优雅配合,当然负载均衡也是包含其中啦。对比其他的 RPC 方案,你不需要引入 RPC 自己的服务注册中心,复用 Spring Cloud 的服务注册中心就可以,如 Alibaba Nacos, Consul, Eureka 和 ZooKeeper 等,没有多余的开销和维护成本。如果你想更多了解 RSocket RPC 相关的细节,可以参考 Spring 官方博客 《Easy RPC with RSocket》


    更多详细的代码细节,可以点击链接查看文章对应的代码库!

    原文链接:https://developer.aliyun.com/article/781944?

    版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
    展开全文
  • 通过上面两个图的对比,可以发现,服务端负载均衡和客户端负载均衡的主要区别就在于负载均衡发生的位置的不同,服务端负载均衡是发生在服务提供方,比如常见的nginx负载均衡。而客户端负载均衡则是发生在发起请求的...
  • 03_Consul服务注册与发现、Ribbon负载均衡服务调用4、Consul服务注册与发现4.1 Consul简介4.1.1 是什么4.1.2 能干嘛4.1.3 去哪下4.1.4 怎么玩4.2 安装并运行Consul4.2.1 官网安装说明4.2.2 下载完成后只有一个consul...
  • 第七部分: Go微服务 - 服务发现和负载均衡 本部分处理一个健全的微服务架构的两个基本部分 - 服务发现和负载均衡 - 以及在2017年, 它们如何促进重要的非功能性需求的水平扩展。 简介 负载均衡是很出名的概念了,但我...
  • 为什么需要服务发现 在 K8s 集群里面会通过 pod 去部署应用,传统的应用部署不同,传统应用部署在给定的机器上面去部署,我们知道怎么去调用别的机器的 IP 地址。但是在 K8s 集群里面应用是通过 pod 去部署的, ...
  • Dubbo 是一个分布式、高性能、透明化的 RPC 服务框架,提供服务自动注册、自动发现等高效服务治理方案, 可以 Spring 框架无缝集成。 RPC 指的是远程调用协议,也就是说两个服务器交互数据。 2、Dubbo的由来? ...
  • Eureka服务注册与发现 创建父工程并导入pom.xml <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven....
  •  负载均衡是我们处理高并发、缓解网络压力进行服务端扩容的重要手段之一,但是一般情况下我们所说的负载均衡通常都是指服务端负载均衡,服务端负载均衡又分为两种,一种是硬件负载均衡,还有一种是软件负载均衡。...
  • 为什么需要服务发现 在 K8s 集群里面会通过 pod 去部署应用,传统的应用部署不同,传统应用部署在给定的机器上面去部署,我们知道怎么去调用别的机器的 IP 地址。但是在 K8s 集群里面应用是通过 pod 去部署的, ...
  • 它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动部署。Spring Cloud并没有...
  • 本文参照: https://zhuanlan.zhihu.com/p/32841479 https://www.jianshu.com/p/9826d866080a http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html ... 负载均衡 wiki解释: 负载均...
  • 最终达到一个结果,在ribbon中维护这一个列表,ancos服务发现的列表,做负载均衡,注意是ribbon中维护这这个列表,并不是复杂均衡的时候采取nacos交互。 如果服务有变化,这个列表也会做相应的调整。 2 . 最顶级的...
  • 第七节: 服务发现和负载均衡 原文地址 转载请注明原文及翻译地址 这篇文章将关注两个微服务架构的重要部分:服务发现和负载均衡.他们是如何帮助我们2017年经常要求的横向扩展容量的 简介 负载均衡和出名.服务发现...
  • F5,LVS,DNS,CDN F5全称: F5-BIG-IP-GTM 全球流量管理器. 是一家叫F5 Networks的公司开发的四~七层交换机,软硬件捆绑. ...这宝贝是用于对流量内容进行管理分配的设备,也就是负载均衡. 从名字就能看出来:BIG-IP....
  • Mesos:服务发现与负载均衡

    千次阅读 2016-01-17 17:27:33
    这一章主要探讨是Mesos关于服务发现与应用的负载均衡的解决方案,主要侧重对服务发现与负载均衡进行讲解,需要明白的一点,Mesos作为 两层架构,Marathon作为Mesos的systemd服务服务发现功能只需要向marathon提供...
  • 客户端负载均衡与服务端负载均衡

    千次阅读 2017-11-30 15:01:22
    通过Nginx负载均衡服务器发送到...它服务端负载均衡有什么区别? 服务端负载均衡 负载均衡是我们处理高并发、缓解网络压力进行服务端扩容的重要手段之一,但是一般情况下我们所说的负载均衡通常都是指服务端
  • 过去所说的负载均衡常指服务端的负载均衡,在客户端服务器端之间增加负载均衡器来实现。那么,客户端负载均衡是什么? 客户端负载均衡 客户端负载均衡是相对服务端而言的,主要出现在微服务中,服务消费之间一种...
  • 等等 服务提供者集群 服务中心集群:10.1.12.22:8500,10.1.12....Ribbon负载均衡器 新建一个Spring Cloud工程。 1、pom文件中引入ribbon依赖 <dependency> <groupId>org.springframewor...
  • 版权声明:本文为博主原创文章,未经博主允许不得转载。 ... 服务端负载均衡 &nbsp; &... 负载均衡是我们处理高并发、缓解网络压力进行服...
  • 1. 负载均衡负载均衡,英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web...通过服务注册中心的服务名/接口实现负载均衡。实际上gateway
  • 使用Spring Cloud服务调用实现微服务的调用和负载均衡 Spring Cloud服务调用与负载均衡组件 搭建demo项目 项目搭建,只提供相关配置代码,不深入介绍 父工程依赖管理 pom.xml <?xml version="1.0" ...
  • Ribbon是什么 Spring cloud Ribbon是基于Netflix Ribbon实现的一套客户端服务调用以及负载均衡的工具。 Ribbon + Eureka + RestTemplate ...根据用户配置的负载均衡策略,从第二步获取到的服务注册
  • 8学习日记SpringCloud Alibaba服务发现NacosDiscoverClient元数据负载均衡使用Ribbon实现负载均衡Ribbon组成配置文件实现Ribbon自定义配置二(实现全局配置Ribbon负载均衡)Ribbon支持的配置项Java代码实现配置文件...
  • Spring Cloud Ribbon是基于NetFlix Ribbon实现的一套客户端 负载均衡工具 ​ 主要功能是提供客户端的...Ribbon本地负载均衡和Nginx服务端负载均衡区别 ​ Nginx:所有请求交给nginx,nginx实现请求转发 ​ Ribbo

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,927
精华内容 10,370
关键字:

服务注册与发现和负载均衡的区别