怎么本地dbug自己的soa末影接口bug

1176人阅读
SOA技术(9)
对于IT人员来说,SOA已经不仅仅是一种技术,它代表了一种新的,更加宏观的设计和实施IT系统的方法论。那么对于整个IT系统的实施过程来说,每一个步骤都按照SOA的角度来考虑,来实施就成为了SOA项目实施过程中的重点和难点。
接口开发的发展促成了SOA的发展,SOA的诸多优势之中接口规范的标准化是比较突出的一个。从EAI发展到SOA 的ESB,从各种不同的接口标准一统的Web Service,我们看到接口开发的演进过程,标准化、规范化的过程。
在SOA这种新的架构和设计思想下,接口合约(Interface contract)成为约束服务提供方和服务消费方的手段。对于开发人员来讲合约会转换为WSDL或者IDL这样的定义文件。有了这样的定义文件,提供方和消费方的开发人员就可以并行的开发。在这种新的开发方式,大量的服务可以有效的复用,开发的效率和开发的服务都可以有效的扩展;在这种方式下,可以把某些任务分配另外的公司和合作伙伴。从这种方式来讲,SOA也方便了软件的外包和离岸开发。
然而,中国有句老话叫&兴一利必生一弊&,这种开发模式下对于测试人员来讲会有新的挑战。
首先,通过接口合约分配给不同开发商的服务,这些开发商内部如何有有效的机制来保证开发接口的质量和对于服务规范(SLA)的满足程度。
第二,在服务组装阶段(一个典型的例子就是BPM的流程的开发就会有这样的阶段),如何有效的保证测试的顺利进行。
第一个阶段,服务开发阶段的功能测试(接口测试)。
有EAI系统或者接口系统开发经验的人都有知道接口的测试是一个比较繁琐和效率低下的过程。SOA规范了接口算是改进了一大步,但是它也没有解决接口测试的根本问题。对于开发和管理者来说,在集成测试之前,保证每个服务的功能测试都是很痛苦的,需要大量的工作。但是对于基于SOA的设计和实现来说,这么做会屏蔽组装(Assembly)阶段更大的,更具破坏性的风险。
我们知道接口合约在现实项目中往往是word文档或者excel格式的文档。对于很多技术实现的细节并没有描述,比较理想的情况下是有比较准确和细化的接口定义文件WSDL。在有了WSDL文件的基础上,我们可以制作一些服务的simulator或者客户端的simulator来模拟服务的调用和执行,但是如何有效的覆盖到所有的业务逻辑,就需要测试人员和业务需求人员科学严谨的工作了。
接口开发有个金科玉律,不要把问题留集成测试。问题越早发现越好。
第二个阶段,服务的集成测试阶段(System Integration Test或Assembling Test)
在 IBM对于SOA的实施过程的方法论描述中,有一个叫做Assemble的阶段。这个阶段就对于Service Testing有着详细的描述。但是在我看来,这些描述反应的问题就是对于真实的商业环境来讲对于SLA的理解是这个阶段测试的重点。
这个阶段还会涉及到各个厂商的协同测试,因为对于跨多个公司开发服务的集成测试来讲,如何协调这些不同的组织,或者说利用什么样的工具和手段提供集成测试的效率,也是现阶段各个厂商和大的集成公司在相关的SOA文档和规范中没有涉及的。因此对于测试人员来说,细节的工作不好做,也缺乏整体的方法论指导的情况下,如何不能说测试人员会进入一个SOA测试的黑洞呢?
&我正在参与一个基于SOA的集成项目,希望能在实践过程中找到这个问题的答案,也希望有机会与大家一起分享我的经验。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:246331次
积分:3005
积分:3005
排名:第11662名
原创:52篇
转载:17篇
评论:80条
(1)(1)(3)(3)(1)(2)(1)(1)(2)(2)(3)(4)(1)(1)(1)(10)(4)(3)(4)(1)(2)(2)(1)(2)(3)(1)(11)(2)dubbo学习过程、使用经验分享及实现原理简单介绍
我的图书馆
dubbo学习过程、使用经验分享及实现原理简单介绍
一、前言部门去年年中开始各种改造,第一步是模块服务化,这边初选dubbo试用在一些非重要模块上,慢慢引入到一些稍微重要的功能上,半年时间,学习过程及线上使用遇到的些问题在此总结下。整理这篇文章差不多花了两天半时间,请尊重劳动成果,如转载请注明出处http://blog.csdn.net/hzzhoushaoyu/article/details/二、什么是dubboDubbo是阿里巴巴提供的开源的SOA服务化治理的技术框架,据说只是剖出来的一部分开源的,但一些基本的需求已经可以满足的,而且扩展性也非常好(至今没领悟到扩展性怎么做到的),通过spring bean的方式管理配置及实例,较容易上手且对应用无侵入。更多介绍可戳。三、如何使用dubbo1.服务化应用基本框架如上图所示,一个抽象出来的基本框架,consumer和provider是框架中必然存在的,Registry做为全局配置信息管理模块,推荐生产环境使用Registry,可实时推送现存活的服务提供者,Monitor一般用于监控和统计RPC调用情况、成功率、失败率等情况,让开发及运维了解线上运行情况。应用执行过程大致如下:服务提供者启动,根据协议信息绑定到配置的IP和端口上,如果已有服务绑定过相同IP和端口的则跳过注册服务信息至注册中心客户端启动,根据接口和协议信息订阅注册中心中注册的服务,注册中心将存活的服务地址通知到客户端,当有服务信息变更时客户端可以通过定时通知得到变更信息在客户端需要调用服务时,从内存中拿到上次通知的所有存活服务地址,根据路由信息和负载均衡机制选择最终调用的服务地址,发起调用通过filter分别在客户端发送请求前和服务端接收请求后,通过异步记录一些需要的信息传递到monitor做监控或者统计2.服务接口定义一般单独有一个jar包,维护服务接口定义、RPC参数类型、RPC返回类型、接口异常、接口用到的常量,该jar包中不处理任何业务逻辑。比如命名api-0.1.jar,在api-0.1.jar中定义接口[java]&并在api-0.1.jar中定义RpcResponseDto,RpcAccountRequestDto,RpcBusinessException,RpcSystemException。服务端通过引用该jar包实现接口并暴露服务,客户端引用该jar包引用接口的代理实例。3.注册中心开源的dubbo已支持4种组件作为注册中心,我们部门使用推荐的zookeeper做为注册中心,由于就瓶颈来说不会出现在注册中心,风险较低,未做特别的研究或比较。zookeeper,推荐集群中部署奇数个节点,由于zookeeper挂掉一半的机器集群就不可用,所以部署4台和3台的集群都是在挂掉2台后集群不可用redismulticast,广播受到网络结构的影响,一般本地不想搭注册中心的话使用这种调用dubbo简易注册中心对于zookeeper客户端,dubbo在2.2.0之后默认使用zkclient,2.3.0之后提供可选配置Curator,提到这个点的原因主要是因为zkclient发现一些问题:①服务器在修改服务器时间后zkClient会抛出日志错误之类的异常然后容器(我们使用resin)挂掉了,也不能确定就是zkClient的问题,接入dubbo之前无该问题②dubbo使用zkclient不传入连接zookeeper等待超时时间,使用默认的Integer.MAX_VALUE,这样在zookeeper连不上的情况下不报错也无法启动;目前我们准备寻找其他解决方案,比如使用curator试下,还没正式投入。4.服务端配置应用名[html]&配置dubbo注解识别处理器,不指定包名的话会在spring bean中查找对应实例的类配置了dubbo注解的[html]&配置注册中心,通过group指定注册中心分组,可通过register配置是否注册到该注册中心以及subscribe配置是否从该注册中心订阅[html]&配置服务协议,多网卡可通过IP指定绑定的IP地址,不指定或者指定非法IP的情况下会绑定在0.0.0.0,使用Dubbo协议的服务会在初始化时建立长连接[html]&通过xml配置文件配置服务暴露,首先要有个spring bean实例(无论是注解配置的还是配置文件配置的),在下面ref中指定bean实例ID,作为服务实现类[html]&通过注解方式配置服务暴露,Component是Spring bean注解,Service是dubbo的注解(不要和spring bean的service注解弄混),如前文所述,dubbo注解只会在spring bean中被识别[java]&5.客户端同服务端配置应用名、注解识别处理器和注册中心。配置客户端reference bean。客户端跟服务端不同的是客户端这边没有实际的实现类的,所以配置的dubbo:reference实际会生成一个spring bean实例,作为代理处理Dubbo请求,然后其他要调用处直接使用spring bean的方式使用这个实例即可。xml配置文件配置方式,id即为spring bean的id,之后无论是在spring配置中使用ref="firstDubboService"还是通过@Autowired注解都OK[html]&另外开发、测试环境可通过指定Url方式绕过注册中心直连指定的服务地址,避免注册中心中服务过多,启动建立连接时间过长,如[html]&注解配置方式引用,[java]&Reference被识别的条件是spring bean实例对应的当前类中的field,如上是直接修饰spring bean当前类中的属性这个地方看了下源码,本应该支持当前类和父类中的public set方法,但是看起来是个BUG,Dubbo处理reference处部分源码如下[java]&6.监控中心如果使用Dubbo自带的监控中心,可通过简单配置即可,先通过github获得dubbo-monitor的源码,部署启动后在应用配置如下[html]&[html]&7.服务路由最重要辅助功能之一,可随时配置路由规则调整客户端调用策略,目前dubbo-admin中已提供基本路由规则的配置UI,到github下载源码部署后很容易找到地方,这里简单介绍下怎么用路由。下面是dubbo-admin的新建路由界面,可配置信息都在图片中有,比如现在我们有10.0.0.1~3三台消费者和10.0.0.4~6三台服务提供者,想让1和2调用4,3调用5和6的话,则可以配置两个规则,1.消费者IP:10.0.0.1,10.0.0.2 ;提供者IP:10.0.0.42.消费者IP:10.0.0.3;提供者IP:10.0.0.5,10.0.0.6另外,IP地址支持结尾为*匹配所有,如10.0.0.*或者10.0.*等。不匹配的配置规则和匹配的配置规则是一致的。配置完成后可在消费者标签页查看路由结果8.负载均衡dubbo提供4种负载均衡方式:Random,随机,按权重配置随机概率,调用量越大分布越均匀,默认是这种方式RoundRobin,轮询,按权重设置轮询比例,如果存在比较慢的机器容易在这台机器的请求阻塞较多LeastActive,最少活跃调用数,不支持权重,只能根据自动识别的活跃数分配,不能灵活调配ConsistentHash,一致性hash,对相同参数的请求路由到一个服务提供者上,如果有类似灰度发布需求可采用dubbo的负载均衡机制是在客户端调用时通过内存中的服务方信息及配置的负责均衡策略选择,如果对自己系统没有一个全面认知,建议先采用random方式。9.dubbo过滤器有需要自己实现dubbo过滤器的,可关注如下步骤:dubbo初始化过程加载META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/三个路径(classloaderresource)下面的com.alibaba.dubbo.rpc.Filter文件文件配置每行Name=FullClassName,必须是实现Filter接口@Activate标注扩展能被自动激活@Activate如果group(provider|consumer)匹配才被加载@Activate的value字段标明过滤条件,不写则所有条件下都会被加载,写了则只有dubbo URL中包含该参数名且参数值不为空才被加载如下是dubbo rpc access log的过滤器,仅对服务提供方有效,且参数中需要带accesslog,也就是配置protocol或者serivce时配置的accesslog="d:/rpc_access.log"[java]&10.其他特性可关注以上链接内容,dubbo提供较多的辅助功能特性,大多目前我们暂时未使用到,后续我们这边关注到的两个特性可能会再引进来使用:结果缓存,省得自己再去写一个缓存,对缓存没有特殊要求的话直接使用dubbo的好了分组合并,对RPC接口不同的实现方式分别调用然后合并结果的一种调用模式,比如我们要查用户是否合法,一种我们要查是否在黑名单,同时我们还要关注登录信息是否异常,然后合并结果四、前车之鉴这个主要是在整个学习及使用过程中记录的,以及一些同事在初识过程问过我的,这边做了整理然后直接列举在下面:1.服务版本号引用只会找相应版本的服务[java]&为了今后更换接口定义发布在线时,可不停机发布,使用版本号2.暴露一个内网一个外网IP问题为了在测试环境提供一个内网访问的地址和一个办公区访问的地址。·增加一个指定IP为内网地址的服务协议·增加一个不指定IP的服务协议,但是在/etc/hosts中hostname对应的IP要为外网IP上面这种方案是一开始使用的方案,后面发现dubbo在启动过程无论是否配路由还是会一个个去连接,虽然不影响启动,但是由于存在超时所以会影响启动时间,而且每台机器还得特别配置指定IP,后面使用另外一套方案:服务不配置ip,绑定到0.0.0.0,自动获取保证获取到是内网IP注册到注册中心即可,如果不是想要的IP,可以在/etc/hosts中通过绑定Hostname指定IP内网访问方式通过注册中心或者直连指定内网IP和端口外网访问方式通过直连指定外网IP和端口使用这种方式需要注意做好防火墙控制等,比如在线默认也是不指定IP,会绑定在0.0.0.0,如果非法人员知道调用的外网IP和端口,而且可以直接访问就麻烦了(如果在应用中做IP拦截也成,需要注意有防范措施)。3.dubbo reference注解问题前文介绍使用时已经提到过,@Reference只能在spring bean实例对应的当前类中使用,暂时无法在父类使用;如果确实要在父类声明一个引用,可通过配置文件配置dubbo:reference,然后在需要引用的地方跟引用spring bean一样就行4.服务超时问题目前如果存在超时,情况基本都在如下几点:客户端耗时大,也就是超时异常时的client elapsed xxx,这个是从创建Future对象开始到使用channel发出请求的这段时间,中间没有复杂操作,只要CPU没问题基本不会出现大耗时,顶多1ms属于正常IOThread繁忙,默认情况下,dubbo协议一个客户端与一个服务提供者会建立一个共享长连接,如果某个客户端处于特别繁忙而且一直往一个服务提供者塞请求,可能造成IOThread阻塞,一般非常特殊的情况才会出现服务端工作线程池中线程全部繁忙,接收消息后塞入队列等待,如果等待时间比预想长会引起超时网络抖动,如果上述情况都排除了,还出现在请求发出后,服务接收请求前超过预想时间,只能归类到网络抖动了,需要SA一起查看问题服务自身耗时大,这个需要应用自身做好耗时统计,当出现这种情况的时候需要用数据来说明问题及规划优化方案,建议采用缓存埋点的方式统计服务中各个执行阶段的耗时情况,最终如果超过预想时间则把缓存统计的耗时情况打日志,减少日志量,且能够得到更明确的信息现在我们应用使用过程中发现两种类型的耗时,一种我们目前只能归类到网络抖动,后续需要找运维一起关注这个问题,另外一种是由于一些历史原因,数据库查询容易发生抖动,总有一个时间点会突然多出很多超时。5.服务保护服务保护的原则上是避免发生类似雪崩效应,尽量将异常控制在服务周围,不要扩散开。说到雪崩效应,还得提下dubbo自身的重试机制,默认3次,当失败时会进行重试,这样在某个时间点出现性能问题,然后调用方再连续重复调用,很容易引起雪崩,建议的话还是很据业务情况规划好如何进行异常处理,何时进行重试。服务保护的话,目前我们主要从以下几个方面来实施,也不成熟,还在摸索:考虑服务的dubbo线程池类型(fix线程池的话考虑线程池大小)、数据库连接池、dubbo连接数限制是否都合适考虑服务超时时间和重试的关系,设置合适的值一定时间内服务异常数较大,则可考虑使用failfast让客户端请求直接返回或者让客户端不再请求经领导推荐,还在学习Release it,后续有其他想法,再回头来编辑。6.zkclient的问题前文已经提到过zkclient有两个问题,修改服务器时间会导致容器挂掉;dubbo使用zkclient没有传超时时间导致zookeeper无法连接的时候,直接阻塞Integer.MAX_VALUE。正在调研curator,目前只能说curator不会在无法连接的时候直接阻塞。另外zkclient和curator的jar包应该都是jdk1.6编译的,所以系统还在jdk1.5以下的话无法使用。7.注册中心的分组group和服务的不同实现group这两个东西完全不同的概念,使用的时候不要弄混了。registry上可以配置group,用于区分不同分组的注册中心,比如在同一个注册中心下,有一部分注册信息是要给开发环境用的,有一部分注册信息时要给测试环境用的,可以分别用不同的group区分开,目前对这个理解还不透彻,大致就是用于区分不同环境。service和reference上也可以配置group,这个用于区分同一个接口的不同实现,只有在reference上指定与service相同的group才会被发现,还有前文提到的分组合并结果也是用的这个。五、dubbo如何工作的其实dubbo整个框架内容并不算大,仔细看的话可能最多两天看完一遍,但是目前还是没领悟到怎么做到的扩展性,学习深度还不够~要学习dubbo源码的话,必须要拿出官方高清大图才行。这张图看起来挺复杂的样子,真正拆分之后对照源码来看会发现非常清晰、简单直观。1.如何跟进源码入口就是各种dubbo配置项的解析,&dubbo:xxx /&都是spring namespace,可以看到dubbo jar包下META-INF里面的spring.handlers,自定义的spring namespace处理器。对于spring不太熟的同学可以先了解下这个功能,入口都在这里,解析成功后每个&dubbo:xxx /&配置项都对应一个spring实例。2.服务提供者首先把这张图拆分成三块,首先是服务端剖去网络传输模块,也就是大图中的右上角。这里主要抽几个主要的类,从服务初始化到接收消息的流程简单说明下,有兴趣的再对照源码看下会比较清晰。ServiceBean继承ServiceConfig,做为服务配置管理和配置信息校验,每一个dubbo:service配置或者注解都会对应生成一个ServiceBean的实例,维护当前服务的配置信息,并把一些全局配置塞入到该服务配置中。另外ServiceBean本身是一个InitializingBean,在afterPropertiesSet时通过配置信息引导服务绑定和注册。可以留意到ServiceBean还实现了ApplicationListener,在全部spring bean加载完成后判断是否延迟加载的逻辑。ProtocolFilterWrapper经过serviceBean引导后进入该类,这个地方注意下,Protocol使用的装饰模式,叶子只有DubboProtocol和RegistryProtocol,在中间调用中会绕来绕去,而且registry会走一遍这个流程,然后在RegistryProtocol中暴露服务再走一遍,注意每个类的作用,不要被绕昏了就行,第一次跟进代码的时候没留意就晕头转向的。在这之前其实还有个ProtocolListenerWrapper,封装监听器,在服务暴露后通知到监听器,没有复杂逻辑,如果没特殊需求可以先绕过。再来说ProtocolFIlterWrapper,这个类的作用就是串联filter调用链,如果有看过struts或者spring mvc拦截器源码的应该不会陌生。RegistryProtocol注册中心协议,如果配置了注册中心地址,每次服务暴露肯定首先引导进入这个类中,如果没有注册中心连接则会先创建连接,然后再引导真正的服务协议暴露流程,会再走一次ProtocolFilterWrapper的流程(这次引导到的叶子是DubboProtocol)。在服务暴露返回后,会再执行服务信息的注册和订阅操作。DubboProtocol这个类的export相对较简单,就是引导服务bind server socket。另外该类还提供了一个内部类,用于处理接收请求,就是下面要提到的ExchangeHandler。DubboProtocol$ExchangeHandler接收反序列化好的请求消息,然后根据请求信息找到执行链,将请求再丢入执行链,让其最终执行到实现类再将执行结果返回即整个过程完成。3.客户端客户端模块与服务端模块比较类似,只是刚好反过来,一个是暴露服务,一个是引用服务,然后客户端多出路由和负载均衡。ReferenceBean继承ReferenceConfig,维护配置信息和配置信息的校验,该功能与ServiceBean类似其本身还实现了FactoryBean,作为实例工厂,创建远程调用代理类;而且如果不指定为init的reference都是在首次getBean的时候调用到该factoryBean的getObject才进行初始化另外实现了InitializingBean,在初始化过程中引导配置信息初始化和构建init的代理实例InvokerInvocationHandler看到这个类名应该就知道是动态代理的handler,这里作为远程调用代理类的处理器在客户端调用接口时引导进入invoker调用链ProtocolFIlterWrapper与Service那边的功能类似,构建调用链RegistryProtocol与service那边类似,如果与注册中心还没有连接则建立连接,之后注册和订阅,再根据配置的策略返回相应的clusterInvoker比service那边有个隐藏较深的逻辑需要留意的,就是订阅过程,RegistryDirectory作为订阅监听器,在订阅完成后会通知到RegistryDirectory,然后会刷新invoker,进入引导至DubboProtocol的流程,与变更的service建立长连接,第一次发生订阅时就会同步接收到通知并将已存在的service存到字典DubboProtocol在订阅过程中发现有service变更则会引导至这里,与服务建立长连接,整个过程为了得到串联执行链InvokerClusterInvokerClusterInvoker由RegistryProtocol构建完成后,内部封装了Directory,在调用时会从Directory列举存活的service对应的Invoker,Directory作为被通知对象,在service有变更时也会及时得到通知调用时在集群中发现存在多节点的话都会通过clusterInvoker来根据配置抉择最终调用的节点,包括路由方式、负载均衡等dubbo本身支持的节点调用策略包括比如failoverClusterInvoker在失败时进行重试其他节点,failfastClusterInvoker在失败时返回异常,mergeableClusterInvoker则是对多个实现结果进行合并的等等很多DubboInvoker承接上层的调用信息,作为调用结构的叶子,将信息传递到exchange层,主要用来和echange交互的功能模块4.网络传输层从exchange往下都是算网络传输,包括做序列化、反序列化,使用Netty等IO框架发送接收消息等逻辑,先前看的时候没有做统一梳理,后续有机会再来编辑吧。
TA的最新馆藏[转]&[转]&
喜欢该文的人也喜欢Service Unavailable
Service Unavailable
HTTP Error 503. The service is unavailable.966,690 七月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
微观SOA:服务设计原则及其实践方式(下篇)
微观SOA:服务设计原则及其实践方式(下篇)
0&他的粉丝
日. 估计阅读时间:
智能化运维、Serverless、DevOps......2017年有哪些最新运维技术趋势?!
相关厂商内容
相关赞助商
CNUTCon全球运维技术大会,9月10日-9月11日,上海&光大会展中心大酒店,
由于SO接口通常要被远程访问,而网络传输,对象序列化/反序列化等开销都远远超过本地Object访问几个数量级,所以要加快系统的响应速度、减少带宽占用和提高吞吐量,选择高性能的远程调用方式经常是很重要的。
但是远程调用方式往往又要受限于具体的业务和部署环境,比如内网、外网、同构平台、异构平台等等。有时还要考虑它对诸如分布式事务,消息级别签名/加密,可靠异步传输等方面的支持程度(这些方面通常被称为SLA:service level agreement),甚至还包括开发者的熟悉和接受程度等等。
因此,远程调用方式往往需要根据具体情况做出选择和权衡。
以Java远程Service为例分析不同场景下,传输方式的某些可能较好选择:
内网 + 同框架Java客户端 + 大并发:多路复用的TCP长连接 + kryo (二进制序列化) (kryo也可以用Protostuff,FST等代替)
内网 + 不同框架Java客户端:TCP + Kryo
内网 + Java客户端 + 2PC分布式事务:RMI/IIOP (TCP + 二进制)
内网 + Java客户端 + 可靠异步调用:JMS + Kryo (TCP + 二进制)
内网 + 不同语言客户端:thrift(TCP + 二进制序列化)
外网 + 不同语言客户端 + 企业级特性:HTTP + WSDL + SOAP (文本)
外网 + 兼顾浏览器、手机等客户端:HTTP + JSON (文本)
外网 + 不同语言客户端 + 高性能:HTTP + ProtocolBuffer (二进制)
简单来说,从性能上讲,tcp协议 + 二进制序列化更适合内网应用。从兼容性、简单性上来说,http协议 + 文本序列化更适合外网应用。当然这并不是绝对的。另外,tcp协议在这里并不是限定远程调用协议一定只能是位于OSI网络模型的第四层的原始tcp,它可以包含tcp之上的任何非http协议。
所以,回答上面提到的问题,WebServices (经典的WSDL+SOAP+HTTP)虽然是最符合前述SOA设计原则的技术,但并不等同于SOA,我认为它只是满足了SOA的底线,而未必是某个具体场景下的最佳选择。这正如一个十项全能选手在每个单项上是很难和单项冠军去竞争的。更理想的SOA Service最好能在可以支持WebServices的同时,支持多种远程调用方式,适应不同场景,这也是Spring Remoting,SCA,Dubbo,Finagle等分布式服务框架的设计原则。
远程调用技术解释:HTTP + JSON适合SOA吗?
JSON简单易读,通用性极佳,甚至能很好支持浏览器客户端,同时也常被手机APP使用,大有取代XML之势。
但JSON本身缺乏像XML那样被广泛接受的标准schema,而一般的HTTP + JSON的远程调用方式也缺乏像Thrift,CORBA,WebServices等等那样标准IDL(接口定义语言),导致服务端和客户端之间不能形成强的服务契约,也就不能做比如自动代码生成。所以HTTP + JSON在降低了学习门槛的同时,可能显著的增加复杂应用的开发工作量和出错可能性。
例如,新浪微博提供了基于HTTP + JSON的Open API,但由于业务操作比较复杂,又在JSON上封装实现了各种语言的客户端类库,来减少用户的工作量。
为了解决这方面的问题,业界有很多不同方案来为HTTP + JSON补充添加IDL,如RSDL、JSON-WSP、WADL、WSDL 2.0等等,但事实上它们的接受度都不太理想。
另外值得一提的是,JSON格式和XML一样有冗余,即使做GZIP压缩之类的优化,传输效率通常也不如很多二进制格式,同时压缩、解压还会引入额外的性能开销。
远程调用技术解释:Apache Thrift多语言服务框架
Thrift是最初来自facebook的一套跨语言的service开发框架,支持C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, JavaScript, Node.js, Smalltalk, Delphi等几乎所有主流编程语言,具有极好的通用性。
Thrift被facebook,twitter等巨头以及开源社区都广泛使用,是非常成熟的技术。
Thrift的服务契约通过类似如下形式的IDL定义:
struct User {
1: i32 id,
2: string name,
3: string password
service UserService {
void store(1: User user),
UserProfile retrieve(1: i32 id)
非常类似于C语言,易读易写,比WSDL简单明了得多。比用java之类的编程语言也更方便,有时候可以把所有相关的接口和数据结构定义放到同一个文件,发布出去的时候不用再打一个压缩包之类,甚至可以直接粘贴到文档中
Thrift还提供工具,可以基于IDL自动生成各种语言对应的服务端和客户端代码:
[lishen@dangdang thrift]thrift --gen java user.thrift
[lishen@dangdang thrift]$ thrift --gen cpp user.thrift
[lishen@dangdang thrift]$ thrift --gen php user.thrift
[lishen@dangdang thrift]$ thrift --gen csharp user.thrift
我认为thrift是比WebServices更简单高效的技术,是在SOA中对WebServices最具有替代性的技术之一。
远程调用技术解释:多路复用的TCP长连接
这是一种追求极致高性能高伸缩的方式,这里只做简要介绍。
比较典型的是twitter的Mux RPC协议以及google的SPDY协议,在其中多个请求同时共用同一个长连接,即一个连接交替传输不同请求的字节块。它既避免了反复建立连接开销,也避免了连接的等待闲置从而减少了系统连接总数,同时还避免了TCP顺序传输中的线头阻塞(head-of-line blocking)问题。
另外,国内比较著名的开源dubbo框架的默认RPC协议,以及业界许多小型开源RPC框架也都是类似的思路。
采用多路复用机制后,一般就要求服务器端和客户端都支持额外的类似于会话层(即OSI网络模型第六层)的语义,导致它们必须要依赖于同一套RPC框架。
其他很多RPC机制都是使用TCP短连接。即使有些RPC使用了长连接,但一个连接同一时间只能发送一个请求,然后连接就处于闲置状态,来等待接收该请求的响应,待响应完毕,该连接才能被释放或者复用。
HTTP 1.1也支持一种基于pipeline模式的长连接,其中多个HTTP请求也可共用一个连接,但它要求响应(response)也必须按照请求(request)的顺序传输返回,即FIFO先进先出。而在完全多路复用的连接中,哪个的响应先ready就可以先传输哪个,不用排队。
当然,短连接、长连接和多路复用长连接之间不存在绝对的好坏,需要取决于具体业务和技术场景,在此不详细展开了。
远程调用技术解释:Java高效序列化
最近几年,各种新的Java高效序列化方式层出不穷,不断刷新序列化性能的上限,例如Kryo,FST等开源框架。它们提供了非常高效的Java对象的序列化和反序列化实现,相比JDK标准的序列化方式(即基于Serializable接口的标准序列化,暂不考虑用诸如Externalizable接口的定制序列化),在典型场景中,其序列化时间开销可能缩短20倍以上,生成二进制字节码的大小可能缩减4倍以上。
另外,这些高效Java序列化方式的开销也显著少于跨语言的序列化方式如thrift的二进制序列化,或者JSON等等
远程调用技术解释:RMI/IIOP和分布式事务
RMI/IIOP是Java EE中标准的远程调用方式,IIOP是CORBA的协议,只有IIOP上的RMI才支持两阶段提交的分布式事务,同时提供和CORBA的互操作。
当然,严格的两阶段提交事务并不高效,还可能严重影响系统伸缩性甚至可用性等等,一般只应用在非常关键的业务中。
远程调用技术解释:Google ProtocolBuffer跨语言序列化
ProtocolBuffer是google开发的跨语言的高效二进制序列化方式,其序列化性能和thrift比较类似。事实上thrift最初就是ProtocolBuffer的仿制品。但它和thrift最大的不同是他没有自带的RPC实现(因为google没有将RPC部分开源,但有大量第三方实现)。
由于不和RPC方式耦合,反而使得ProtocolBuffer能被方便的集成进大量已有的系统和框架中。在国内它也被百度、淘宝等广泛的应用在Open API中,和HTTP搭配作为一种高效的跨平台跨组织的集成方式。
服务设计原则2:消除冗余数据
同样由于service的远程调用开销很高,所以在它的输入参数和返回结果中,还要尽量避免携带当前业务用例不需要的冗余的字段,来减少序列化和传输的开销。同时,去掉冗余字段也可以简化接口,避免给外部用户带来不必要的业务困惑。
比如article service中有个返回article list的方法
List&Article& getArticles(...)
如果业务需求仅仅是要列出文章的标题,那么在返回的article中就要避免携带它的contents等等字段。
这里经典解决方案就是引入OO中常用的Data Transfer Object (DTO)模式,专门针对特定service的用例来定制要传输的数据字段。这里就是添加一个AriticleSummary的额外数据传输对象:
List&ArticleSummary& getArticleSummaries(...)
额外的DTO确实是个麻烦,而一般OO程序通常则可直接返回自己的包含冗余的业务模型。
服务设计原则3:粗粒度契约
同样由于远程调用开销高,同时service的外部使用者对特定业务流程的了解也比不上组织内部的人,所以service的契约(接口)通常需要是粗粒度的,其中的一个操作就可能对应到一个完整的业务用例或者业务流程,这样既能减少远程调用次数,同时又降低学习成本和耦合度。
而OO接口通常可以是非常细粒度的,提供最好的灵活性和重用性。
例如,article service支持批量删除文章,OO接口中可以提供
deleteArticle(long id)
供用户自己做循环调用(暂不考虑后端SQL之类优化),但SO接口中,则最好提供
deleteArticles(Set&Long& ids)
供客户端调用,将可能的N次远程调用减少为一次。
例如,下订单的用例,要有一系列操作
addItem -& addTax -& calculateTotalPrice -& placeOrder
OO中我们完全可以让用户自己来灵活选择,分别调用这些细粒度的可复用的方法。但在SO中,我们需要将他们封装到一个粗粒度的方法供用户做一次性远程调用,同时也隐藏了内部业务的很多复杂性。另外,客户端也从依赖4个方法变成了依赖1个方法,从而大大降低了程序耦合度。
顺便值得一提的是,如果上面订单用例中每个操作本身也是远程的service(通常在内网之中),这种粗粒度封装就变成了经典的service composition(服务组合)甚至service orchestration(服务编排)了。这种情况下粗粒度service同样可能提高了性能,因为对外网客户来说,多次跨网的远程调用变成了一次跨网调用 + 多次内网调用。
对这种粗粒度service封装和组合,经典解决方案就是引入OO中常用的Facade模式,将原来的对象屏蔽到专门的&外观&接口之后。同时,这里也很可能要求我们引入新的service参数/返回值的数据结构来组合原来多个操作的对象模型,这就同样用到前述的DTO模式。
一个简单Facade示例(FooService和BarService是两个假想的本地OO service,fa&ade将它们的结果值组合返回):
class FooBarFacadeImpl implements FooBarFacade {
private FooService fooS
private BarService barS
public FooBarDto getFooBar() {
FooBarDto fb = new FooBarDto();
fb.setFoo(fooService.getFoo());
fb.setBar(barService.getBar());
当然,有的时候也可以不用facade和DTO,而在是FooService和BarService之外添加另一个本地service和domain model,这要和具体业务场景有关。
服务设计原则4:通用契约
由于service不假设用户的范围,所以一般要支持不同语言和平台的客户端。但各种语言和平台在功能丰富性上有很大差异,这就决定了服务契约必须取常见语言、平台以及序列化方式的最大公约数,才能保证service广泛兼容性。由此,服务契约中不能有某些语言才具备的高级特性,参数和返回值也必须是被广泛支持的较简单的数据类型(比如不能有对象循环引用)。
如果原有的OO接口不能满足以上要求,则在此我们同样需要上述的Facade和DTO,将OO接口转换为通用的SO契约。
例如原有对象模型
class Foo {
Pattern是Java特有的预编译好的,可序列化的正则表达式(可提高性能),但在没有特定框架支持下,可能不好直接被其他语言识别,所以可添加DTO:
class FooDto {
服务设计原则5:隔离变化
虽然OO和SO都追求低耦合,但SO由于使用者范围极广,就要求了更高程度的低耦合性。
比如前述的article service,OO中可以直接返回article对象,而这个article对象在OO程序内部可能做为核心的建模的domain model,甚至作为O/R mapping等等。而在SO如果还直接返回这个article,即使没有前面所说的冗余字段,复杂类型等问题,也可能让外部用户与内部系统的核心对象模型,甚至O/R mapping机制,数据表结构等等产生了一定关联度,这样一来,内部的重构经常都会可能影响到外部的用户。
所以,这里再次对Facade和DTO产生了需求,用它们作为中介者和缓冲带,隔离内外系统,把内部系统变化对外部的冲击减少到最小程度。
服务设计原则6:契约先行
Service是往往涉及不同组织之间的合作,而按照正常逻辑,两个组织之间合作的首要任务,就是先签订明确的契约,详细规定双方合作的内容,合作的形式等等,这样才能对双方形成强有力的约束和保障,同时大家的工作也能够并行不悖,不用相互等待。因此SOA中,最佳的实践方式也是契约先行,即先做契约的设计,可以有商务,管理和技术等不同方面的人员共同参与,并定义出相应的WSDL或者IDL,然后在开发的时候再通过工具自动生成目标语言的对应代码。
对于WSDL来说,做契约先行的门槛略高,如果没有好的XML工具很难手工编制。但对于Thrift IDL或者ProtocolBuffer等来说,由于它们和普通编程语言类似,所以契约设计相对是比较容易的。另外,对于简单的HTTP + JSON来说(假设不补充使用其他描述语言),由于JSON没有标准的schema,所以是没法设计具有强约束力的契约的,只能用另外的文档做描述或者用JSON做输入输出的举例。
但是,契约先行,然后再生成服务提供端的代码,毕竟给service开发工作带来了较大的不便,特别是修改契约的时候导致代码需要重写。因此,这里同样可能需要引入Facade和DTO,即用契约产生的都是Facade和DTO代码,它们负责将请求适配和转发到其他内部程序,而内部程序则可以保持自己的主导性和稳定性。
另外,契约先行可能会给前面提到的多远程调用支持带来一些麻烦。
当然契约先行也许并不是能被广泛接受的实践方式,就像敏捷开发中&测试先行&(也就是测试驱动开发)通常都是最佳实践,但真正施行的团队却非常之少,这方面还需要不断摸索和总结。但我们至少可以认为Echo中Java2WSDL并不被认为是SOA的最佳实践。
服务设计原则7:稳定和兼容的契约
由于用户范围的广泛性,所以SO的服务契约和Java标准API类似,在公开发布之后就要保证相当的稳定性,不能随便被重构,即使升级也要考虑尽可能的向下兼容性。同时,如果用契约先行的方式,以后频繁更改契约也导致开发人员要不断重做契约到目标语言映射,非常麻烦。
这就是说SO对契约的质量要求可能大大高于一般的OO接口,理想的情况下,甚至可能需要专人(包括商务人员)来设计和评估SO契约(不管是否用契约先行的方式),而把内部的程序实现交给不同的人,而两者用Facade和DTO做桥梁。
服务设计原则8:契约包装
前述原则基本都是针对service提供端来讲的,而对service消费端而言,通过契约生成对应的客户端代码,经常就可以直接使用了。当然,如果契约本身就是Java接口之类(比如在Dubbo,Spring Remoting等框架中),可以略过代码生成的步骤。
但是,service的返回值(DTO)和service接口(Facade),可能被消费端的程序到处引用到。
这样消费端程序就较强的耦合在服务契约上了,如果服务契约不是消费端定义的,消费端就等于把自己程序的部分主导权完全让渡给了别人。
一旦契约做更改,或者消费端要选择完全不同的service提供方(有不同的契约),甚至改由本地程序自己来实现相关功能,修改工作量就可能非常大了。
另外,通过契约生成的客户端代码,经常和特定传输方式是相关的(比如webservices stub),这样给切换远程调用方式也会带来障碍。
因此,就像在通常应用中,我们要包装数据访问逻辑(OO中的DAO或者Repository模式),或者包装基础服务访问逻辑(OO中的Gateway模式)一样,在较理想的SOA设计中,我们也可以考虑包装远程service访问逻辑,由于没有恰当的名称,暂时称之为Delegate Service模式,它由消费端自己主导定义接口和参数类型,并将调用转发给真正的service客户端生成代码,从而对它的使用者完全屏蔽了服务契约,这些使用者甚至不知道这个服务到底是远程提供的的还是本地提供的。
此外,即使我们在消费端是采用某些手工调用机制(如直接构建和解析json等内容,直接收发JMS消息等等),我们同样可以用delegate service来包装相应的逻辑。
delegate service示例1:
// ArticlesService是消费端自定义的接口
class ArticleServiceDelegate implements ArticlesService {
// 假设是某种自动生成的service客户端stub类
private ArticleFacadeS
public void deleteArticles(List&Long& ids) {
stub.deleteArticles(ids);
delegate service示例2:
// ArticlesService是消费端自定义的接口
class ArticleServiceDelegate implements ArticlesService {
public void deleteArticles(List&Long& ids) {
// 用JMS和FastJson手工调用远程service
messageClient.sendMessage(queue, JSON.toJSONString(ids));
从面向对象到面向服务,再从面向服务到面向对象
总结上面的几个原则,虽然只是谈及有限的几个方面,但大致也可看出OO和SO在实际的设计开发中还是有不少显著的不同之处,而且我们没有打算用SO的原则来取代过去的OO设计,而是引入额外的层次、对象和OO设计模式,来补充传统的OO设计。
其实就是形成了这种调用流程:
service提供端:OO程序 &- SOA层(Facade和DTO)&- 远程消费端
service消费端:OO程序 -& Delegate Service -& SOA层(Facade和DTO 或者 其他动态调用机制)-& 远程提供端
Facade、DTO和Delegate Service负责做OO到SO和SO到OO的中间转换。
现在,可以回答Echo示例中的问题:通过&透明的&配置方式,将OO程序发布为远程Service,虽然可能较好的完成了从本地对象到远程对象的跨越,但通常并不能较好的完成OO到SO的真正跨越。
同时,透明配置方式也通常无法直接帮助遗留应用(如ERP等)转向SOA。
当然,在较为简单和使用范围确定很有限应用(比如传统和局部的RPC)中,透明式远程service发布会带来极大的便利。
另外,上面对SO的所有讨论都集中在RPC的方式,其实SO中也用message的方式做集成,它也是个大话题,暂时不在此详论了。
为什么不能放弃面向对象?
SO是有它的特定场景的,比如远程的,范围不定的客户端。所以它的那些设计原则并不能被借用来指导一般性的程序开发,比如很多OO程序和SO原则完全相反,经常都要提供细粒度接口和复杂参数类型以追求使用的使用灵活性和功能的强大性。
就具体架构而言,我认为SOA层应该是一个很薄的层次(thin layer),将OO应用或者其他遗留性应用加以包装和适配以帮助它们面向服务。其实在通常的web开发中,我们也是用一个薄的展现层(或者叫Web UI层之类)来包装OO应用,以帮助它们面向浏览器用户。因此,Fa&ade、DTO等不会取代OO应用中核心的Domain Model、Service等等 (这里的service是OO中service,未必是SO的)。
综合起来,形成类似下面的体系结构:
理想和现实
需要特别指出的是,上面提到的诸多SO设计原则是在追求一种相对理想化的设计,以达到架构的优雅性,高效性,可重用性,可维护性,可扩展性等等。
而在现实中任何理论和原则都可能是需要作出适当妥协的,因为现实是千差万别的,其情况远比理论复杂,很难存在放之四海而皆准的真理。
而且很多方面似乎本来也没有必要追求完美和极致,比如如果有足够能力扩充硬件基础设施,就可以考虑传输一些冗余数据,选择最简单传输方式,并多来几次远程调用等等,以减轻设计开发的工作量。
那么理想化的原则就没有意义了吗?比如领域驱动设计(Domain-Driven Design)被广泛认为是最理想的OO设计方式,但极少有项目能完全采用它;测试驱动开发也被认为是最佳的敏捷开发方式,但同样极少有团队能彻底采用它。但是,恐怕没有多少人在了解它们之后会否认它们巨大的意义。
理想化的原则可以更好的帮助人们理解某类问题的本质,并做为好的出发点或者标杆,帮助那些可以灵活运用,恰当取舍的人取得更大的成绩,应付关键的挑战。这正如孔子说的&取乎其上,得乎其中;取乎其中,得乎其下;取乎其下,则无所得矣&。
另外,值得一提的是,SOA从它的理念本身来说,就带有一些的理想主义的倾向,比如向&全世界&开放,不限定客户端等等。如果真愿意按SOA的路径走,即使你是个土豪,偷个懒比浪费网络带宽重要,但说不定你的很多用户是土鳖公司,浪费几倍的带宽就大大的影响他们的利润率。
延伸讨论:SOA和敏捷软件开发矛盾吗?
SOA的服务契约要求相当的稳定性,一旦公开发布(或者双方合同商定)就不应该有经常的变更,它需要对很多方面有极高的预判。而敏捷软件开发则是拥抱变化,持续重构的。软件设计大师Martin Fowler把它们归结为计划式设计和演进式设计的不同。
计划理论(或者叫建构理论)和演进理论是近代哲学的两股思潮,影响深远,派生出了比如计划经济和市场经济,社会主义和自由主义等等各种理论。
但是,计划式设计和演进式设计并不绝对矛盾,就像计划经济和市场经济也不绝对矛盾,非此即彼,这方面需要在实践中不断摸索。前面我们讨论的设计原则和架构体系,就是将SOA层和OO应用相对隔离,分而治之,在SOA层需要更多计划式设计,而OO应用可以相对独立的演进,从而在一定程度缓解SOA和敏捷开发的矛盾。
延伸讨论:SOA和REST是一回事吗?
从最本质的意义上讲,REST(Representational State Transfer)实际是一种面向资源架构(ROA),和面向服务架构(SOA)是有根本区别的。
例如,REST是基于HTTP协议,对特定资源做增(HTTP POST)、删(HTTP DELETE)、改(HTTP PUT)、查(HTTP GET)等操作,类似于SQL中针对数据表的INSERT、DELETE、UPDATE、SELECT操作,故REST是以资源(资源可以类比为数据)为中心的。而SOA中的service通常不包含这种针对资源(数据)的细粒度操作,而是面向业务用例、业务流程的粗粒度操作,所以SOA是以业务逻辑为中心的。
但是在实际使用中,随着许多REST基本原则被不断突破,REST的概念被大大的泛化了,它往往成为很多基于HTTP的轻量级远程调用的代名词(例如前面提到过的HTTP + JSON)。比如,即使是著名的Twitter REST API也违反不少原始REST的基本原则。
在这个泛化的意义上讲,REST也可以说是有助于实现SOA的一种轻量级远程调用方式。
SOA架构的进化
前面讨论的SOA的所有问题,基本都集中在service本身的设计开发。但SOA要真正发挥最大作用,还需要不断演进成更大的架构(也就是从微观SOA过渡到宏观SOA),在此略作说明:
第一个层次是service架构:开发各种独立的service并满足前面的一些设计原则,我们前面基本都集中在讨论这种架构。这些独立的service有点类似于小孩的积木。
第二个层次是service composition(组合)架构:独立的service通过不同组合来构成新的业务或者新的service。在理想情况下,可以用一种类似小孩搭积木的方式,充分发挥想象力,将独立的积木(service)灵活的拼装组合成新的形态,还能够自由的替换其中的某个构件。这体现出SOA高度便捷的重用性,大大提高企业的业务敏捷度。
第三个层次是service inventory(清单)架构:通过标准化企业服务清单(或者叫注册中心)统一的组织和规划service的复用和组合。当积木越来越多了,如果还满地乱放而没有良好的归类整理,显然就玩不转了。
第四个层次是service-oriented enterprise架构&&
至此,我们只是简要的探讨了微观层面的SOA,特别是一些基本设计原则及其实践方式,以期能够略微展示SOA在实践中的本质,以有助于SOA更好的落地,进入日常操作层面。
最后,打个比方:SOA不分贵贱(不分语言、平台、组织),不远万里(通过远程调用)的提供服务(service),这要求的就是一种全心全意为人民服务的精神&&
沈理,当当网架构师和技术委员会成员,主要负责当当网的SOA实施(即服务化)以及分布式服务框架的开发。以前也有在BEA、Oracle、Redhat等外企的长期工作经历,从事过多个不同SOA相关框架和容器的开发。他的邮箱:
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博()或者腾讯微博()关注我们,并与我们的编辑和其他读者朋友交流。
Author Contacted
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
非常深入的SOA分析文章
拜读大作,获益匪浅
拜读大作,获益匪浅
果真细致入微
微观SOA的服务设计和原则总结的很好!
微观SOA的服务设计和原则总结的很好!
微观SOA的服务设计和原则总结的很好!
微观SOA的服务设计和原则总结的很好!
微观SOA的服务设计和原则总结的很好!
微观SOA的服务设计和原则总结的很好!
Re: 微观SOA的服务设计和原则总结的很好!
整体思路清晰
Re: 整体思路清晰
Re: 整体思路清晰
想听到很多现实部分的故事
微观SOA的定义不严谨
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
SOA 微服务 数据访问层粒度问题
模块化、服务化、插件化开源开发平台-JXADF
说得很清晰
我们如何去划分服务?
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
订阅InfoQ每周精要,加入拥有25万多名资深开发者的庞大技术社区。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
找回密码....
InfoQ账号使用的E-mail
关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。
内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。
设置通知机制以获取内容更新对您而言是否重要
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。}

我要回帖

更多关于 soa接口 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信