我有一些同学,主要是做传统行业,外包项目,技术一直都搞的比较简单。共同的一个问题,就是都没怎么搞过分布式系统,现在互联网公司,一般都是做分布式的系统。当然了,大家都不是做底层的分布式系统,比如分布式存储系统,hadoop hdfs,分布式计算系统,hadoop mapreduce,spark,分布式流式计算系统,storm。
分布式业务系统,把原来用java开发的一个大块系统,给拆分成多个子系统,多个子系统之间互相调用,形成一个大系统的整体。假设原来你做了一个OA系统,里面包含了权限模块、员工模块、请假模块、财务模块,一个工程,里面包含了一堆模块,模块与模块之间会互相去调用,1台机器部署。
现在如果你把他这个系统给拆开,权限系统,员工系统,请假系统,财务系统,4个系统,4个工程,分别在4台机器上部署。
一个请求过来,完成这个请求,这个员工系统,调用权限系统,调用请假系统,调用财务系统,4个系统分别完成了一部分的事情,最后4个系统都干完了以后,才认为是这个请求已经完成了。
事情就是这么个事,那么面试中常见的问题有哪些呢?
(1)为什么要进行系统拆分? (2)说一下rpc框架的工作原理?注册中心挂了可以继续通信吗? (3)dubbo支持哪些序列化协议?说一下hessian的数据结构?PB知道吗?为什么PB的效率是最高的? (4)负载均衡策略和高可用策略都有哪些?动态代理策略呢? (5)dubbo的spi思想是什么? (6)如何进行服务治理、服务降级、失败重试以及超时重试? (7)分布式服务接口的幂等性如何设计(比如不能重复扣款)? (8)分布式服务接口请求的顺序性如何保证? (9)如何自己设计一个类似dubbo的rpc框架? (9)dubbo和spring cloud的区别是什么? |
下面主要以dubbo为主讲一下分布式系统的相关内容,为什么选择dubbo而不是spring cloud,原因很简单,因为dubbo相对来说要麻烦的多(dubbo更具有技术含量)。当然了,要是工作使用肯定选择spring cloud要好的多,但是这是面试。不过也无所谓,dubbo面试的问题会了,spring cloud的也不成问题了,也就那回事。
为什么要进行系统拆分?如何进行系统拆分?拆分后不用rpc可以吗?
从这个问题开始就进行分布式系统环节了,现在出去分布式成标配了,没有哪个公司不问问你分布式的事儿。你要是不会分布式的东西,简直这简历没法看,你的面试机会会很少。
网上查查,答案极度零散和复杂,很琐碎,原因一大坨。一句话总结,如果是那种代码量多达几十万行的中大型项目,团队里有几十个人,那么如果不拆分系统,开发效率极其低下,问题很多。但是拆分系统之后,每个人就负责自己的一小部分就好了,可以随便玩儿随便弄。分布式系统拆分之后,可以大幅度提升复杂系统大型团队的开发效率。
但是同时,也要提醒的一点是,系统拆分成分布式系统之后,大量的分布式系统面临的问题也是接踵而来,所以后面的问题都是在围绕分布式系统带来的复杂技术挑战在说。
这个问题说大可以很大,可以扯到领域驱动模型设计上去,说小了也很小,大部分的系统,是要进行多轮拆分的,第一次拆分,可能就是将以前的多个模块该拆分开来了,比如说将电商系统拆分成订单系统、商品系统、采购系统、仓储系统、用户系统,等等吧。
但是后面可能每个系统又变得越来越复杂了,比如说采购系统里面又分成了供应商管理系统、采购单管理系统,订单系统又拆分成了购物车系统、价格系统、订单管理系统。
扯深了实在很深,所以这里给大家举个例子,你自己感受一下,核心意思就是根据情况,先拆分一轮,后面如果系统更复杂了,可以继续分拆。你根据自己负责系统的例子,来考虑一下就好了。一个服务的代码不要太多,1万行左右,两三万撑死了吧。
当然可以了,大不了最次,就是各个系统之间,直接基于spring mvc,就纯http接口互相通信呗,还能咋样。但是这个肯定是有问题的,因为http接口通信维护起来成本很高,你要考虑超时重试、负载均衡等等各种乱七八糟的问题,比如说你的订单系统调用商品系统,商品系统部署了5台机器,你怎么把请求均匀地甩给那5台机器?这不就是负载均衡?你要是都自己搞那是可以的,但是确实很痛苦。
我们通常用的rpc框架,就是本地进行接口调用,现有的注册组件会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡了、服务实例上下线自动感知了、超时重试了,等等乱七八糟的问题,那你就不用自己做了。
说一下的rpc的工作原理?注册中心挂了可以继续通信吗?说说一次rpc请求的流程?
那既然开始聊分布式系统了,那肯定是先从RPC的原理开始聊了,我们知道,现在比较常见RPC框架就是dubbo和spring cloud。
前几年大部分公司的分布式系统的rpc框架标准是dubbo,基于dubbo也可以构建一整套的微服务架构,但是需要自己大量开发。现在大量的公司开始转向spring cloud了,spring cloud人家毕竟是微服务架构的全家桶式的这么一个东西。
因为很多公司还在用dubbo,所以dubbo肯定还是会问的,何况人家dubbo现在重启开源社区维护了,未来应该也还是有一定市场和地位的。要搞清楚dubbo的工作原理,我们先来了解一下dubbo组件的几个层级:
层级 | 说明 | 备注 |
service层 | 接口层 | 给服务提供者和消费者来实现的 |
config层 | 配置层 | 主要是对dubbo进行各种配置的 |
proxy层 | 服务代理层 | 透明生成客户端的stub和服务单的skeleton |
registry层 | 服务注册层 | 负责服务的注册与发现 |
cluster层 | 集群层 | 封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务 |
monitor层 | 监控层 | 对rpc接口的调用次数和调用时间进行监控 |
protocol层 | 远程调用层 | 封装rpc调用 |
exchange层 | 信息交换层 | 封装请求响应模式,同步转异步 |
transport层 | 网络传输层 | 抽象mina和netty为统一接口 |
serialize层 | 数据序列化层 | 数据序列化处理 |
知道上面的概念后,dubbo的工作原理就好理解了,首先就是provider向注册中心去注册,然后consumer从注册中心订阅服务,注册中心会通知consumer注册好的服务,consumer调用provider。此外,consumer和provider都异步的通知监控中心。
那么,注册中心挂了可以继续通信吗?
答案是可以的,因为刚开始初始化的时候,消费者会将提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信。
spring cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。其中最核心的组件就是注册中心——Eureka组件了。
Eureka 是 Netflix 出品的用于实现服务注册和发现的工具,Spring Cloud 封装了 Netflix 公司开发的 Eureka 模块来实现服务注册和发现。Eureka采用C-S的设计架构,包含Eureka Server(服务提供者) 和Eureka Client(服务消费者)两个组件。
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
在默认配置中EurekaServer服务在一定时间(默认为90秒)没接受到某个服务的心跳连接后,EurekaServer会注销该服务。但是会存在当网络分区发生故障,导致该时间内没有心跳连接,但该服务本身还是健康运行的情况。Eureka通过“自我保护模式”来解决这个问题。在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性P在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡。
Zookeeper保证CP
Zookeeper(dubbo注册中心底层) 为主从结构,有leader节点和follow节点。当leader节点down掉之后,剩余节点会重新进行选举。选举过程中会导致服务不可用,丢掉了可用行。
Eureka保证AP
Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。
dubbo支持哪些通信协议?支持哪些序列化协议?
上一个问题,说说dubbo的基本工作原理,那是你必须知道的,至少知道dubbo分成哪些层,然后平时怎么发起rpc请求的,注册、发现、调用,这些是基本的。
接着就可以针对底层进行深入的问问了,比如第一步就可以先问问序列化协议这块,就是平时rpc的时候怎么走的?
dubbo协议
默认就是走dubbo协议(dubbo://192.168.0.1:20188)的,单一长连接,NIO异步通信,基于hessian作为序列化协议。
适用的场景就是,传输数据量很小(每次请求在100kb以内),但是并发量很高。
为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就100个连接。然后后面直接基于长连接NIO异步通信,可以支撑高并发请求。否则如果上亿次请求每次都是短连接的话,服务提供者会扛不住。
而且因为走的是单一长连接,所以传输数据量太大的话,会导致并发能力降低。所以一般建议是传输数据量很小,支撑高并发访问。
rmi协议
走java二进制序列化,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输,一般较少用。
hessian协议
走hessian序列化协议,多个短连接,适用于提供者数量比消费者数量还多,适用于文件的传输,一般较少用。
http协议
走json序列化。
webservice
走SOAP文本序列化。
dubbo实际基于不同的通信协议,支持hessian、java二进制序列化、json、SOAP文本序列化多种序列化协议。但是hessian是其默认的序列化协议。
服务的负载均衡策略和集群容错策略都有哪些?动态代理策略呢?
继续深问吧,这些都是用微服务必须知道的一些东西,你得知道基本原理,知道序列化是什么协议,还得知道具体用某个rpc框架的时候,如何负载均衡,如何高可用,如何动态代理。
random loadbalance
默认情况下,dubbo是random load balance随机调用实现负载均衡,可以对provider不同实例设置不同的权重,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。
roundrobin loadbalance
还有roundrobin loadbalance,这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。
leastactive loadbalance
这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求。
consistanthash loadbalance
一致性Hash算法,相同参数的请求一定分发到一个provider上去,provider挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性hash策略。
RoundRobinRule
轮询策略,轮询是最简单的一种负载均衡算法,它的原理是将用户的请求轮流分配给内部的服务器,并且轮询算法并不需要记录当前所有连接的状态,所以它是一种无状态的调度。
RandomRule
随机,也没什么好解释的。
AvailabilityFilteringRule
会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数超过阈值的服务,然后对剩余的服务列表进行轮询。
WeightedResponseTimeRule
权重,根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。刚启动时,如果统计信息不足,则使用轮询策略,等信息足够,切换到 WeightedResponseTimeRule。
RetryRule
重试,先按照轮询策略获取服务,如果获取失败则在指定时间内重试,获取可用服务
BestAvailableRule 选过滤掉多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
ZoneAvoidanceRule
符合判断server所在区域的性能和server的可用性选择服务。
failover cluster模式
失败自动切换,自动重试其他机器,默认就是这个,常见于读操作。
failfast cluster模式
一次调用失败就立即失败,常见于写操作。
failsafe cluster模式
出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。
failbackc cluster模式
失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种。
forking cluster
并行调用多个provider,只要一个成功就立即返回。
broadcacst cluster
逐个调用所有的provider。
spring cloud关于集群容错的话,一般就使用Hystrix组件就好了。
默认使用javassist动态字节码生成,创建代理类。但是可以通过spi扩展机制配置自己的动态代理策略。
dubbo的spi思想是什么?
继续深入问呗,前面一些基础性的东西问完了,确定你应该都ok了解dubbo的一些基本东西,那么问个稍微难一点点的问题,就是spi,先问问你spi是啥?然后问问你dubbo的spi是怎么实现的?
spi,简单来说,就是service provider interface,说白了是什么意思呢,比如你有个接口,现在这个接口有3个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要spi了,需要根据指定的配置或者是默认的配置,去找到对应的实现类加载进来,然后用这个实现类的实例对象。
比如说你要通过jar包的方式给某个接口提供实现,然后你就在自己jar包的META-INF/services/目录下放一个跟接口同名的文件,里面指定接口的实现里是自己这个jar包里的某个类。ok了,别人用了一个接口,然后用了你的jar包,就会在运行的时候通过你的jar包的那个文件找到这个接口该用哪个实现类。
比如说你有个工程A,有个接口A,接口A在工程A里是没有实现类的 -> 系统在运行的时候,怎么给接口A选择一个实现类呢?
你就可以自己搞一个jar包,META-INF/services/,放上一个文件,文件名就是接口名,接口A,接口A的实现类=com.zhss.service.实现类A2。让工程A来依赖你的这个jar包,然后呢在系统运行的时候,工程A跑起来,对接口A,就会扫描自己依赖的所有的jar包,在每个jar里找找,有没有META-INF/services文件夹,如果有,在里面找找,有没有接口A这个名字的文件,如果有在里面找一下你指定的接口A的实现是你的jar包里的哪个类?
SPI机制,一般来说用在哪儿?插件扩展的场景,比如说你开发的是一个给别人使用的开源框架,如果你想让别人自己写个插件,插到你的开源框架里面来,扩展某个功能。
经典的思想体现,大家平时都在用,比如说jdbc。java定义了一套jdbc的接口,但是java是没有提供jdbc的实现类。但是实际上项目跑的时候,要使用jdbc接口的哪些实现类呢?一般来说,我们要根据自己使用的数据库,比如msyql,你就将mysql-jdbc-connector.jar,引入进来;oracle,你就将oracle-jdbc-connector.jar,引入进来。
在系统跑的时候,碰到你使用jdbc的接口,他会在底层使用你引入的那个jar中提供的实现类。但是dubbo也用了spi思想,不过没有用jdk的spi机制,是自己实现的一套spi机制。
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Protocol接口,dubbo要判断一下,在系统运行的时候,应该选用这个Protocol接口的哪个实现类来实例化对象来使用呢?
他会去找一个你配置的Protocol,他就会将你配置的Protocol实现类,加载到jvm中来,然后实例化对象,就用你的那个Protocol实现类就可以了。Protocol负责rpc调用的东西,你可以实现自己的rpc调用组件,实现Protocol接口,给自己的一个实现类即可。
这行代码就是dubbo里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现好了,没问题。
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
@Adaptive
Exporter export(Invoker invoker) throws RpcException;
@Adaptive
Invoker refer(Class type, URL url) throws RpcException;
void destroy();
}
在dubbo自己的jar里,在/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol文件中:
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
所以说,这就看到了dubbo的spi机制默认是怎么玩儿的了,其实就是Protocol接口,@SPI(“dubbo”)说的是,通过SPI机制来提供实现类,实现类是通过dubbo作为默认key去配置文件里找到的,配置文件名称与接口全限定名一样的,通过dubbo作为key可以找到默认的实现了就是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol。
dubbo的默认网络通信协议,就是dubbo协议,用的DubboProtocol。如果想要动态替换掉默认的实现类,需要使用@Adaptive接口,Protocol接口中,有两个方法加了@Adaptive注解,就是说那俩接口会被代理实现。
啥意思呢?
比如这个Protocol接口搞了俩@Adaptive注解标注了方法,在运行的时候会针对Protocol生成代理类,这个代理类的那俩方法里面会有代理代码,代理代码会在运行的时候动态根据url中的protocol来获取那个key,默认是dubbo,你也可以自己指定,你如果指定了别的key,那么就会获取别的实现类的实例了。
通过这个url中的参数不通,就可以控制动态使用不同的组件实现类。好吧,那下面来说说怎么来自己扩展dubbo中的组件。
自己写个工程,要是那种可以打成jar包的,里面的src/main/resources目录下,搞一个META-INF/services,里面放个文件叫:com.alibaba.dubbo.rpc.Protocol,文件里搞一个my=com.zhss.MyProtocol。自己把jar弄到nexus私服里去。
然后自己搞一个dubbo provider工程,在这个工程里面依赖你自己搞的那个jar,然后在spring配置文件里给个配置:
这个时候provider启动的时候,就会加载到我们jar包里的my=com.zhss.MyProtocol这行配置里,接着会根据你的配置使用你定义好的MyProtocol了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的dubbo内部的组件,就是扔个你自己的jar包,然后配置一下即可。
dubbo里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个jar,让你的consumer或者是provider工程,依赖你的那个jar,在你的jar里指定目录下配置好接口名称对应的文件,里面通过key=实现类。
然后对对应的组件,用类似
如何基于dubbo进行服务治理、服务降级、失败重试以及超时重试?
服务治理,这个问题如果问你,其实就是看看你有没有服务治理的思想,因为这个是做过复杂微服务的人肯定会遇到的一个问题。
服务降级,这个是涉及到复杂分布式系统中必备的一个话题,因为分布式系统互相来回调用,任何一个系统故障了,你不降级,直接就全盘崩溃?那就太坑爹了吧。
失败重试,分布式系统中网络请求如此频繁,要是因为网络问题不小心失败了一次,是不是要重试?
超时重试,同上,如果不小心网络慢一点,超时了,如何重试?
调用链路自动生成
一个大型的分布式系统,或者说是用现在流行的微服务架构来说吧,分布式系统由大量的服务组成。那么这些服务之间互相是如何调用的?调用链路是啥?说实话,几乎到后面没人搞的清楚了,因为服务实在太多了,可能几百个甚至几千个服务。
那就需要基于dubbo做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将各个服务之间的依赖关系和调用链路生成出来,做成一张图,显示出来,大家才可以看到对吧。
服务访问压力以及时长统计
需要自动统计各个接口和服务之间的调用次数以及访问延时,而且要分成两个级别。一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50,TP90,TP99,三个档次的请求延时分别是多少;第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的TP50,TP90,TP99,分别是多少。
这些东西都搞定了之后,后面才可以来看当前系统的压力主要在哪里,如何来扩容和优化啊。此外,还有其他的内容,调用链路失败监控和报警,服务鉴权,每个服务的可用性的监控(接口调用成功率?几个9?,99.99%,99.9%,99%)
比如说服务A调用服务B,结果服务B挂掉了,服务A重试几次调用服务B,还是不行,直接降级,走一个备用的逻辑,给用户返回响应
public interface HelloService {
void sayHello();
}
public class HelloServiceImpl implements HelloService {
public void sayHello() {
System.out.println("hello world......");
}
}
然后配置中,
现在就是mock,如果调用失败统一返回null。但是可以将mock修改为true,然后在跟接口同一个路径下实现一个Mock类,命名规则是接口名称加Mock后缀。然后在Mock类里实现自己的降级逻辑。
public class HelloServiceMock implements HelloService {
public void sayHello() {
// 降级逻辑
}
}
所谓失败重试,就是consumer调用provider要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。
某个服务的接口,要耗费5s,你这边不能干等着,你这边配置了timeout之后,我等待2s,还没返回,我直接就撤了,不能干等你。
如果是超时了,timeout就会设置超时时间;如果是调用失败了自动就会重试指定的次数。你就结合你们公司的具体的场景来说说你是怎么设置这些参数的,timeout,一般设置为200ms,我们认为不能超过200ms还没返回。
retries,3次,设置retries,还一般是在读请求的时候,比如你要查询个数据,你可以设置个retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取2次。
分布式服务接口的幂等性如何设计(比如不能重复扣款)?
从这个问题开始,面试官就已经进入了实际的生产问题的面试了。
一个分布式系统中的某个接口,要保证幂等性,该如何保证?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢?
你看,假如你有个服务提供一个接口,结果这服务部署在了5台机器上,接着有个接口就是付款接口。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单不小心发起了两次支付请求,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次?尴尬了。
或者是订单系统调用支付系统进行支付,结果不小心因为网络超时了,然后订单系统走了前面我们看到的那个重试机制,咔嚓给你重试了一把,好,支付系统收到一个支付请求两次,而且因为负载均衡算法落在了不同的机器上,尴尬了。
所以你肯定得知道这事儿,否则你做出来的分布式系统恐怕容易埋坑。
网络问题很常见,100次请求,都ok;1万次,可能1次是超时会重试;10万,10次;100万,100次;如果有100个请求重复了,你没处理,导致订单扣款2次,100个订单都扣错了;每天被100个用户投诉;一个月被3000个用户投诉。。。
这个不是技术问题,这个没有通用的一个方法,这个是结合业务来看应该如何保证幂等性的,你的经验。
所谓幂等性,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款,不能多插入一条数据,不能将统计值多加了1,这就是幂等性。
不给大家来学术性词语了。其实保证幂等性主要是三点:
要求是支付一个订单,必须插入一条支付流水,order_id建一个唯一键,unique key。所以你在支付一个订单之前,先插入一条支付流水,order_id就已经进去了。你就可以写一个标识到redis里面去,set order_id payed,下一次重复请求过来了,先查redis的order_id对应的value,如果是payed就说明已经支付过了,你就别重复支付了。
然后呢,你再重复支付这个订单的时候,你写尝试插入一条支付流水,数据库给你报错了,说unique key冲突了,整个事务回滚就可以了。来保存一个是否处理过的标识也可以,服务的不同实例可以一起操作redis。
分布式服务接口请求的顺序性如何保证?
其实分布式系统接口的调用顺序,也是个问题,一般来说是不用保证顺序的。但是有的时候可能确实是需要严格的顺序保证。给大家举个例子,你服务A调用服务B,先插入再删除。好,结果俩请求过去了,落在不同机器上,可能插入请求因为某些原因执行慢了一些,导致删除请求先执行了,此时因为没数据所以啥效果也没有;结果这个时候插入请求过来了,好,数据插入进去了,那就尴尬了。
本来应该是先插入,再删除,这条数据应该没了,结果现在先删除,再插入,数据还存在,最后你死都想不明白是怎么回事。所以这都是分布式系统一些很常见的问题。
首先,一般来说,建议是,你们从业务逻辑上最好设计的这个系统不需要这种顺序性的保证,因为一旦引入顺序性保障,会导致系统复杂度上升,而且会带来效率低下,热点数据压力过大,等问题。
下面我给个我们用过的方案吧,简单来说,首先你得用dubbo的一致性hash负载均衡策略,将比如某一个订单id对应的请求都给分发到某个机器上去,接着就是在那个机器上因为可能还是多线程并发执行的,你可能得立即将某个订单id对应的请求扔一个内存队列里去,强制排队,这样来确保他们的顺序性。
但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成热点怎么办?解决这些问题又要开启后续一连串的复杂技术方案。
最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是什么,避免这种问题的产生。
如何自己设计一个类似dubbo的rpc框架?
说实话,就这问题,其实就跟问你,如何自己设计一个MQ,一样的道理,就考两个:
其实一般问到这问题,起码不能认怂,自己说说参照dubbo的原理,举个例子,dubbo不是有那么多分层么?而且每个分层是干啥的,你大概是不是知道?那就按照这个思路大致说一下吧。举个例子:
这就是一个最最基本的rpc框架的思路,先不说你有多牛逼的技术功底,哪怕这个最简单的思路你先给出来行不行?
特性 | spring cloud | dubbo |
传输协议 | http协议传输,带宽会比较多,同时使用http协议一般会使用JSON报文,消耗会更大 | 二进制的传输,占用带宽会更少 |
开发难度 | springcloud的接口协议约定比较自由且松散,需要有强有力的行政措施来限制接口无序升级 | dubbo的开发难度较大,原因是dubbo的jar包依赖问题很多大型工程无法解决 |
注册中心 | springcloud的注册中心只能用eureka或者自研 | dubbo的注册中心可以选择zk,redis等多种 |
系统结构简易度 | springcloud的系统结构更简单,“注册+springmvc=springcloud” | dubbo各种复杂的Url,protocol,register,invocation,dubbofilter,dubboSPI,dubbo序列化...炫技的成分更多一些 |
网络性能消耗 | 大 | 小 |
后续改进 | springcloud自己带了很多监控、限流措施,但是功能可能和欧美习惯相同,国内需要进行适当改造,但更简单,就是ServletFilter而已,但是总归比dubbo多一些东西是好的 | dubbo的改进是通过dubbofilter,很多东西没有,需要自己继承,如监控,如日志,如限流,如追踪 |
功能完备性 | Spring Cloud由众多子项目组成,如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分布式系统及微服务常用的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等,满足了构建微服务所需的所有解决方案。比如使用Spring Cloud Config 可以实现统一配置中心,对配置进行统一管理;使用Spring Cloud Netflix 可以实现Netflix 组件的功能 - 服务发现(Eureka)、智能路由(Zuul)、客户端负载均衡(Ribbon)。 | Dubbo具有调度、发现、监控、治理等功能,支持相当丰富的服务治理能力。Dubbo架构下,注册中心对等集群,并会缓存服务列表已被数据库失效时继续提供发现功能,本身的服务发现结构有很强的可用性与健壮性,足够支持高访问量的网站。 |
简而言之,Dubbo确实类似于Spring Cloud的一个子集,Dubbo功能和文档完善,在国内有很多的成熟用户,然而鉴于Dubbo的社区现状(曾经长期停止维护),使用起来还是有一定的门槛。dubbo曾经确实很牛逼,但是Spring Cloud是站在近些年技术发展之上进行开发,因此更具技术代表性。
虽然Dubbo 支持短连接大数据量的服务提供模式,但绝大多数情况下都是使用长连接小数据量的模式提供服务使用的。所以,对于类似于电商等同步调用场景多并且能支撑搭建Dubbo 这套比较复杂环境的成本的产品而言,Dubbo 确实是一个可以考虑的选择。但如果产品业务中由于后台业务逻辑复杂、时间长而导致异步逻辑比较多的话,可能Dubbo 并不合适。同时,对于人手不足的初创产品而言,这么重的架构维护起来也不是很方便。