序言
RPC: 在分布式计算,远程过程调用(Remote Procedure Call,缩写为RPC),是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程。RPC是一种服务器-客户端模式,经典实现是通过“发送请求-接受回应”进行信息交互的系统。
就java而言,RPC就是远程方法调用和本地方法调用。
本地方法调用:就是进程内部方法之间的调用,这是我们经常写的,A类的方法调用了A类的另一个方法,或者A类的方法调用了B类的一个方法等等。这些在一个进程中实现的方法之间的调用,都是本地方法调用。具体的方法实现细节也可以清楚的看到。
远程方法调用:相对于本地方法的进程内部调用,远程方法调用是进程之间的方法调用。a服务A类调用了b服务的B类,可以理解为服务于服务之间的调用。(服务是拥有独立的端口号的进程)。
那么如何实现远程方法调用呢?
一般是通过网络之间的调用,进行数据的交互。网络之间数据的交互协议有两种:HTTP,TCP/IP 协议。
框架有了,那么具体的实现就多了,目前市面上大公司都根据自己的业务有具体的实现。例如:Dubbo,DubboX,SpringCloud等。
数据传输就框架层而言,不关注具体协议,那么核心的部分应该有这几部分:
1,被调用者是那个类。
2,被调用者是哪个方法。
3,被调用者参数。
4,被调用者返回值。
本文就来说说Dubbo,这是一款阿里开源出来的一套RPC框架。
Apache Dubbo 是一款高性能、轻量级的开源 Java RPC框架。这是官网的一段描述。
以前是一款RPC调用框架。现在朝着服务框架发展。那么就来说说这两个之间的区别。
为什么会将RPC改为服务?
Dubbo一开始的定位就是RPC,专注于两个服务之间的调用。但随着微服务的盛行,除开服务调用之外,Dubbo也在逐步的涉猎服务治理、服务监控、服务网关等等,所以现在的Dubbo目标已经不止是RPC框架了,而是和Spring Cloud类似想成为了一个服务框架。
基本原理
节点说明:
Provider: 暴漏服务的服务提供方。
Consumer:调用远程服务的服务消费方。
Registry:服务注册于发现注册中心
Monitor:统计服务的调用次数和调用时间的监控中心。
Container:服务运行容器。
调用关系说明:
1,服务容器负责启动,加载,运行服务提供者。
2,服务提供者在启动时,想注册中心注册自己提供的服务。
3,服务消费者在启动时,想注册中心订阅自己所需的服务。
4,注册中心返回服务提供者地址列表给消费者,如果有变动,注册中心将基于长连接更新数据给消费者。
5,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调佣,如果调用失败,在选择另一台调用。
6,服务消费者和提供者,在内容中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
Dubbo详解
接着上面的调用说Dubbo
通过对dubbo架构图了解到,核心的组件就这么几个,dubbo的整体功能就是围绕这几个组件展开的。下面将详细的讲解各个组件之间的关系以及在实际开发中的使用。
服务提供者,服务消费者
说到服务提供者,首先说下方法函数,所谓方法函数就是定义在类中的一段代码块,用来实现某个功能。
在实际的开发中,我们往往是多人开发一个项目,这就需要协作。比如A同学开发的是基础服务,B同学开发的功能需要用到基础服务:例如B同学需要根据身份证号获取一下用户名字,头像,性别等信息。那么B同学就对A同学说需要什么信息,A同学就提供出来一个接口方法让B同学调用。B同学不需要关注具体的实现,因为那不是B同学的工作,只需要把精力放在自己功能的开发上。
像相面这种调用关系,A同学扮演的就是服务的提供者角色,不管是一个服务中的服务提供者还是不同服务间的服务提供者,只需要关注所提供的服务即可,不需要关注调用服务方的功能。
通过上述可以看出来,这样做的好处就是解耦合,分工明确。当然有利就有弊,服务提供者对服务消费者是隔离的,当某天B同学的需求变了,需要增加或者减少一些字段,逻辑可能也做了调整,那么这就需要再次让A同学去进行修改。简单来说就是细化导致的结果就是繁琐。
繁琐体现在,本来一个服务中的调用,A服务修改完,B服务直接就可以用了,但是不同服务之间调用就变得复杂了,这也是细化导致的必然结果,为了实现服务的健壮性,服务一般部署至少两台服务器,这样在一台服务挂掉的时候,还能继续提供服务,那么就会出现多个服务的提供者。
那么作为服务消费者该如何选择呢?
没错就是负载均衡。
负载均衡
提到负载均衡最先想到的就是随机,轮询,这在很多的负载均衡中都有。例如Nginx中就有设置权重随机。轮询,简单理解就是有两台服务器,第一次访问1服务器,第二次访问2服务器,第三次又是访问1服务器,以此类推。
这里重点讲一下后面两种,最少活跃调用数,一致性Hash,这也是在很多中间件中用到的。
最少活跃调用数
最少活跃数一般是在服务端进行统计的,服务提供者统计有多少个请求正在执行中,但是Dubbo却是在消费端进行统计的,统计的流程是这样的:
1,消费者会缓存所调用服务的所有提供者,A1,A2两个服务提供者,服务提供者有个属性几位active,默认值为0.
2,消费者在调用此服务时,如果负载均衡策略是leastactive,消费者端会判断缓存的所有服务提供者的active,选择最小的,如果都相同,则随机。
3,选出服务提供者后,比如选择的是A1,Dubbo就会将active+1,然后真正发出请求调用该服务。
4,消费端收到响应结果后,对A1的active-1,这样就完成了对服务提供者当前活跃调用数进行了统计,并且不影响服务调用的性能。
一致性Hash
就是对参数做hash,相同参数的请求总是发到同一个提供者。比如请求某个产品的详情,id为1的总是路由到A服务器上。当某一台提供者挂掉,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
上面是官网的描述,但是对于热点数据处理还是有风险,比如针对某个热点数据的请求不能都打到某一台服务器上,这样会直接把数据库打死。当然这里可以使用缓存等等,这个问题在后面缓存在做讨论。后面再讨论Redis的hash一致性算法在对这个做详细的说明。
重点:如果消费端和服务端都配置了负载均衡策略,以消费端为准。
服务之间的调用也是有限制的,为什么要有限制。试想一下,现在有三个服务A,B,C。先说服务提供者,A服务作为提供者,提供出来多个服务,比如查询交易记录的服务,由于某些原因执行卡死了,B在调用该服务的时候,并没有做处理,在并发量打的时候,会把A服务请求的线程池打满了,这样就会A提供的其他服务无法正常运行,直至把整个系统拖死。 这是非常严重的事情,那么自然也会有对应的策略来处理这个问题。
没错,首先想到的就是设置个超时时间,超时了抛异常返回,不影响别的请求执行。
服务超时
服务提供者和服务消费者都可以设置超时时长,这两者是不一样的。
消费者调用一个服务,分为三步:
- 消费者发送请求(网络传输)
- 服务端执行服务
- 服务端返回响应(网络传输)
如果在服务端和消费端只在其中一方配置了timeout,那么没有歧义,表示消费端调用服务的超时时间,消费端如果超过时间还没有收到响应结果,则消费端会抛超时异常,但,服务端不会抛异常,服务端在执行服务后,会检查执行该服务的时间,如果超过timeout,则会打印一个超时日志。服务会正常的执行完。
如果在服务端和消费端各配了一个timeout,那就比较复杂了,假设
- 服务执行为5s
- 消费端timeout=3s
- 服务端timeout=6s
那么消费端调用服务时,消费端会收到超时异常(因为消费端超时了),服务端一切正常(服务端没有超时)。
服务超时在一定程度上保护了服务的正常运行,但是还不能解决问题,在并发非常大的情况下还是会出现系统被拖垮的问题。那么接下来看看服务容错。
集群容错
集群容错表示:服务消费者在调用某个服务时,这个服务有多个服务提供者,在经过负载均衡后选出其中一个服务提供者之后进行调用,但调用报错后,Dubbo所采取的后续处理策略。
集群容错的机制非常多:
1,失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2"
来设置重试次数(不含第一次)。
2,快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
3,失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作
4,失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
5,并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2"
来设置最大并行数。
6,广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
集群容错的模式可以保证一个服务挂掉了,不至于拖垮整个服务。那么如何才能最大限度的保证系统的可用性能,那么就需要用到服务降级策略了。
服务降级
服务降级表示:服务消费者在调用某个服务提供者时,如果该服务提供者报错了,所采取的措施。
集群容错和服务降级的区别在于:
- 集群容错是整个集群范围内的容错
- 服务降级是单个服务提供者的自身容错
服务降级是某个服务出现了问题,可以访问一个降级的策略。比如在消费者端,调用A服务的一个接口,该结果挂掉了,那么这时候可以回调事先准备好的一个接口,提供降级处理策略。比如访问的是国家城市信息,可以事先将数据在本地缓存一分,当调用接口挂掉了,可以访问本地缓存中的数据,可能不是最新的数据,当时可以保证服务的正常运行。最大限度的保证了服务的可用性。