大家好,今天给各位分享深度网站源码分享是什么的一些知识,其中也会对有深度的网站或论坛进行解释,文章篇幅可能偏长,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在就马上开始吧!
Fescar简介
常见的分布式事务方式有基于2PC的XA(e.g.atomikos),从业务层入手的TCC(e.g.byteTCC)、事务消息(e.g.RocketMQHalfMessage)等等。XA是需要本地数据库支持的分布式事务的协议,资源锁在数据库层面导致性能较差,而支付宝作为布道师引入的TCC模式需要大量的业务代码保证,开发维护成本较高。
分布式事务是业界比较关注的领域,这也是短短时间Fescar能收获6kStar的原因之一。Fescar名字取自Fast&EasyCommitAndRollback,简单来说Fescar通过对本地RDBMS分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是相对于XA模式是性能较好不长时间占用连接资源,相对于TCC方式开发成本和业务侵入性较低。
类似于XA,Fescar将角色分为TC、RM、TM,事务整体过程模型如下:
1.TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。\n2.XID在微服务调用链路的上下文中传播。\n3.RM向TC注册分支事务,将其纳入XID对应全局事务的管辖。\n4.TM向TC发起针对XID的全局提交或回滚决议。\n5.TC调度XID下管辖的全部分支事务完成提交或回滚请求。\n
其中在目前的实现版本中TC是独立部署的进程,维护全局事务的操作记录和全局锁记录,负责协调并驱动全局事务的提交或回滚。TMRM则与应用程序工作在同一应用进程。RM对JDBC数据源采用代理的方式对底层数据库做管理,利用语法解析,在执行事务保留快照,并生成undolog。大概的流程和模型划分就介绍到这里,下面开始对Fescar事务传播机制的分析。
Fescar事务传播机制
Fescar事务传播包括应用内事务嵌套调用和跨服务调用的事务传播。Fescar事务是怎么在微服务调用链中传播的呢?Fescar提供了事务API允许用户手动绑定事务的XID并加入到全局事务中,所以我们根据不同的服务框架机制,将XID在链路中传递即可实现事务的传播。
RPC请求过程分为调用方与被调用方两部分,我们将XID在请求与响应时做相应的处理即可。大致过程为:调用方即请求方将当前事务上下文中的XID取出,通过RPC协议传递给被调用方;被调用方从请求中的将XID取出,并绑定到自己的事务上下文中,纳入全局事务。微服务框架一般都有相应的Filter和Interceptor机制,我们来分析下SpringCloud与Fescar的整合过程。
Fescar与SpringCloudAlibaba集成部分源码解析
本部分源码全部来自于spring-cloud-alibaba-fescar.源码解析部分主要包括AutoConfiguration、微服务被调用方和微服务调用方三大部分。对于微服务调用方方式具体分为RestTemplate和Feign,对于Feign请求方式又进一步细分为结合Hystrix和Sentinel的使用模式。
FescarAutoConfiguration
对于AutoConfiguration部分的解析此处只介绍与Fescar启动相关的部分,其他部分的解析将穿插于【微服务被调用方】和【微服务调用方】章节进行介绍。
Fescar的启动需要配置GlobalTransactionScanner,GlobalTransactionScanner负责初始化Fescar的RMclient、TMclient和自动代理标注GlobalTransactional注解的类。
GlobalTransactionScannerbean的启动通过GlobalTransactionAutoConfiguration加载并注入FescarProperties。
FescarProperties包含了Fescar的重要属性txServiceGroup,此属性的可通过application.properties文件中的key:spring.cloud.alibaba.fescar.txServiceGroup读取,默认值为${spring.application.name}-fescar-service-group。txServiceGroup表示Fescar的逻辑事务分组名,此分组名通过配置中心(目前支持文件、Apollo)获取逻辑事务分组名对应的TC集群名称,进一步通过集群名称构造出TC集群的服务名,通过注册中心(目前支持Nacos、Redis、ZooKeeper和Eureka)和服务名找到可用的TC服务节点,然后RMclient、TMclient与TC进行RPC交互。
微服务被调用方
由于调用方的逻辑比较多一点,我们先分析被调用方的逻辑。针对于SpringCloud项目,默认采用的RPC传输协议时HTTP协议,所以使用了HandlerInterceptor机制来对HTTP的请求做拦截。
HandlerInterceptor是Spring提供的接口,它有以下三个方法可以被覆写。
/**\n*Intercepttheexecutionofahandler.CalledafterHandlerMappingdetermined\n*anappropriatehandlerobject,butbeforeHandlerAdapterinvokesthehandler.\n*/\ndefaultbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)\nthrowsException{\nreturntrue;\n}\n/**\n*Intercepttheexecutionofahandler.CalledafterHandlerAdapteractually\n*invokedthehandler,butbeforetheDispatcherServletrenderstheview.\n*CanexposeadditionalmodelobjectstotheviewviathegivenModelAndView.\n*/\ndefaultvoidpostHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,\n@NullableModelAndViewmodelAndView)throwsException{\n}\n/**\n*Callbackaftercompletionofrequestprocessing,thatis,afterrendering\n*theview.Willbecalledonanyoutcomeofhandlerexecution,thusallows\n*forproperresourcecleanup.\n*/\ndefaultvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,\n@NullableExceptionex)throwsException{\n}\n
根据注释,我们可以很明确的看到各个方法的作用时间和常用用途。对于Fescar集成来讲,它需要重写了preHandle、afterCompletion方法。
FescarHandlerInterceptor的作用是将服务链路传递过来的XID,绑定到服务节点的事务上下文中,并且在请求完成后清理相关资源。FescarHandlerInterceptorConfiguration中配置了所有的url均进行拦截,对所有的请求过来均会执行该拦截器,进行XID的转换与事务绑定。
/**\n*@authorxiaojing\n*\n*FescarHandlerInterceptor,ConvertFescarinformationinto\n*@seecom.alibaba.fescar.core.context.RootContextfromhttprequest’sheaderin\n*{@linkorg.springframework.web.servlet.HandlerInterceptorafterCompletion(HttpServletRequest,HttpServletResponse,Object,Exception)}\n*/\npublicclassFescarHandlerInterceptorimplementsHandlerInterceptor{\nprivatestaticfinalLoggerlog=LoggerFactory\n.getLogger(FescarHandlerInterceptor.class);\n@Override\npublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,\nObjecthandler)throwsException{\nStringxid=RootContext.getXID();\nStringrpcXid=request.getHeader(RootContext.KEY_XID);\nif(log.isDebugEnabled()){\nlog.debug(“xidinRootContext{}xidinRpcContext{}”,xid,rpcXid);\n}\nif(xid==null&&rpcXid!=null){\nRootContext.bind(rpcXid);\nif(log.isDebugEnabled()){\nlog.debug(“bind{}toRootContext”,rpcXid);\n}\n}\nreturntrue;\n}\n@Override\npublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,\nObjecthandler,Exceptione)throwsException{\nStringrpcXid=request.getHeader(RootContext.KEY_XID);\nif(StringUtils.isEmpty(rpcXid)){\nreturn;\n}\nStringunbindXid=RootContext.unbind();\nif(log.isDebugEnabled()){\nlog.debug(“unbind{}fromRootContext”,unbindXid);\n}\nif(!rpcXid.equalsIgnoreCase(unbindXid)){\nlog.warn(“xidinchangeduringRPCfrom{}to{}”,rpcXid,unbindXid);\nif(unbindXid!=null){\nRootContext.bind(unbindXid);\nlog.warn(“bind{}backtoRootContext”,unbindXid);\n}\n}\n}\n}\n
preHandle在请求执行前被调用,xid为当前事务上下文已经绑定的全局事务的唯一标识,rpcXid为请求通过HTTPHeader传递过来需要绑定的全局事务标识。preHandle方法中判断如果当前事务上下文中没有XID,且rpcXid不为空,那么就将rpcXid绑定到当前的事务上下文。
afterCompletion在请求完成后被调用,该方法用来执行资源的相关清理动作。Fescar通过RootContext.unbind()方法对事务上下文涉及到的XID进行解绑。下面if中的逻辑是为了代码的健壮性考虑,如果遇到rpcXid和unbindXid不相等的情况,再将unbindXid重新绑定回去。
对于SpringCloud来讲,默认采用的RPC方式是HTTP的方式,所以对被调用方来讲,它的请求拦截方式不用做任何区分,只需要从Header中将XID就可以取出绑定到自己的事务上下文中即可。但是对于调用方由于请求组件的多样化,包括熔断隔离机制,所以要区分不同的情况做处理,后面我们来具体分析一下。
微服务调用方
Fescar将请求方式分为:RestTemplate、Feign、Feign+Hystrix和Feign+Sentinel。不同的组件通过SpringBoot的AutoConfiguration来完成自动的配置,具体的配置类可以看spring.factories,下文也会介绍相关的配置类。
RestTemplate
先来看下如果调用方如果是是基于RestTemplate的请求,Fescar是怎么传递XID的。
publicclassFescarRestTemplateInterceptorimplementsClientHttpRequestInterceptor{\n@Override\npublicClientHttpResponseintercept(HttpRequesthttpRequest,byte[]bytes,\nClientHttpRequestExecutionclientHttpRequestExecution)throwsIOException{\nHttpRequestWrapperrequestWrapper=newHttpRequestWrapper(httpRequest);\nStringxid=RootContext.getXID();\nif(!StringUtils.isEmpty(xid)){\nrequestWrapper.getHeaders().add(RootContext.KEY_XID,xid);\n}\nreturnclientHttpRequestExecution.execute(requestWrapper,bytes);\n}\n}\n
FescarRestTemplateInterceptor实现了ClientHttpRequestInterceptor接口的intercept方法,对调用的请求做了包装,在发送请求时若存在Fescar事务上下文XID则取出并放到HTTPHeader中。
FescarRestTemplateInterceptor通过FescarRestTemplateAutoConfiguration实现将FescarRestTemplateInterceptor配置到RestTemplate中去。
@Configuration\npublicclassFescarRestTemplateAutoConfiguration{\n@Bean\npublicFescarRestTemplateInterceptorfescarRestTemplateInterceptor(){\nreturnnewFescarRestTemplateInterceptor();\n}\n@Autowired(required=false)\nprivateCollection<RestTemplate>restTemplates;\n@Autowired\nprivateFescarRestTemplateInterceptorfescarRestTemplateInterceptor;\n@PostConstruct\npublicvoidinit(){\nif(this.restTemplates!=null){\nfor(RestTemplaterestTemplate:restTemplates){\nList<ClientHttpRequestInterceptor>interceptors=newArrayList<ClientHttpRequestInterceptor>(\nrestTemplate.getInterceptors());\ninterceptors.add(this.fescarRestTemplateInterceptor);\nrestTemplate.setInterceptors(interceptors);\n}\n}\n}\n}\n
init方法遍历所有的restTemplate,并将原来restTemplate中的拦截器取出,增加fescarRestTemplateInterceptor后置入并重排序。
Feign
接下来看下Feign的相关代码,该包下面的类还是比较多的,我们先从其AutoConfiguration入手。
@Configuration\n@ConditionalOnClass(Client.class)\n@AutoConfigureBefore(FeignAutoConfiguration.class)\npublicclassFescarFeignClientAutoConfiguration{\n@Bean\n@Scope(“prototype”)\n@ConditionalOnClass(name=”com.netflix.hystrix.HystrixCommand”)\n@ConditionalOnProperty(name=”feign.hystrix.enabled”,havingValue=”true”)\nFeign.BuilderfeignHystrixBuilder(BeanFactorybeanFactory){\nreturnFescarHystrixFeignBuilder.builder(beanFactory);\n}\n@Bean\n@Scope(“prototype”)\n@ConditionalOnClass(name=”com.alibaba.csp.sentinel.SphU”)\n@ConditionalOnProperty(name=”feign.sentinel.enabled”,havingValue=”true”)\nFeign.BuilderfeignSentinelBuilder(BeanFactorybeanFactory){\nreturnFescarSentinelFeignBuilder.builder(beanFactory);\n}\n@Bean\n@ConditionalOnMissingBean\n@Scope(“prototype”)\nFeign.BuilderfeignBuilder(BeanFactorybeanFactory){\nreturnFescarFeignBuilder.builder(beanFactory);\n}\n@Configuration\nprotectedstaticclassFeignBeanPostProcessorConfiguration{\n@Bean\nFescarBeanPostProcessorfescarBeanPostProcessor(\nFescarFeignObjectWrapperfescarFeignObjectWrapper){\nreturnnewFescarBeanPostProcessor(fescarFeignObjectWrapper);\n}\n@Bean\nFescarContextBeanPostProcessorfescarContextBeanPostProcessor(\nBeanFactorybeanFactory){\nreturnnewFescarContextBeanPostProcessor(beanFactory);\n}\n@Bean\nFescarFeignObjectWrapperfescarFeignObjectWrapper(BeanFactorybeanFactory){\nreturnnewFescarFeignObjectWrapper(beanFactory);\n}\n}\n}\n
FescarFeignClientAutoConfiguration在存在Client.class时生效,且要求作用在FeignAutoConfiguration之前。由于FeignClientsConfiguration是在FeignAutoConfiguration生成FeignContext生效的,所以根据依赖关系,FescarFeignClientAutoConfiguration同样早于FeignClientsConfiguration。
FescarFeignClientAutoConfiguration自定义了Feign.Builder,针对于feign.sentinel,feign.hystrix和feign的情况做了适配,目的是自定义feign中Client的真正实现为FescarFeignClient。
HystrixFeign.builder().retryer(Retryer.NEVER_RETRY)\n.client(newFescarFeignClient(beanFactory))\nSentinelFeign.builder().retryer(Retryer.NEVER_RETRY)\n.client(newFescarFeignClient(beanFactory));\nFeign.builder().client(newFescarFeignClient(beanFactory));\n
FescarFeignClient是对原来的Feign客户端代理增强,具体代码见下图:
publicclassFescarFeignClientimplementsClient{\nprivatefinalClientdelegate;\nprivatefinalBeanFactorybeanFactory;\nFescarFeignClient(BeanFactorybeanFactory){\nthis.beanFactory=beanFactory;\nthis.delegate=newClient.Default(null,null);\n}\nFescarFeignClient(BeanFactorybeanFactory,Clientdelegate){\nthis.delegate=delegate;\nthis.beanFactory=beanFactory;\n}\n@Override\npublicResponseexecute(Requestrequest,Request.Optionsoptions)throwsIOException{\nRequestmodifiedRequest=getModifyRequest(request);\ntry{\nreturnthis.delegate.execute(modifiedRequest,options);\n}\nfinally{\n}\n}\nprivateRequestgetModifyRequest(Requestrequest){\nStringxid=RootContext.getXID();\nif(StringUtils.isEmpty(xid)){\nreturnrequest;\n}\nMap<String,Collection<String>>headers=newHashMap<>();\nheaders.putAll(request.headers());\nList<String>fescarXid=newArrayList<>();\nfescarXid.add(xid);\nheaders.put(RootContext.KEY_XID,fescarXid);\nreturnRequest.create(request.method(),request.url(),headers,request.body(),\nrequest.charset());\n}\n
上面的过程中我们可以看到,FescarFeignClient对原来的Request做了修改,它首先将XID从当前的事务上下文中取出,如果XID不为空的情况下,将XID放到了Header中。
FeignBeanPostProcessorConfiguration定义了3个bean:FescarContextBeanPostProcessor、FescarBeanPostProcessor和FescarFeignObjectWrapper。其中FescarContextBeanPostProcessorFescarBeanPostProcessor实现了SpringBeanPostProcessor接口。
以下为FescarContextBeanPostProcessor实现。
@Override\npublicObjectpostProcessBeforeInitialization(Objectbean,StringbeanName)\nthrowsBeansException{\nif(beaninstanceofFeignContext&&!(beaninstanceofFescarFeignContext)){\nreturnnewFescarFeignContext(getFescarFeignObjectWrapper(),\n(FeignContext)bean);\n}\nreturnbean;\n}\n@Override\npublicObjectpostProcessAfterInitialization(Objectbean,StringbeanName)\nthrowsBeansException{\nreturnbean;\n}\n
BeanPostProcessor中的两个方法可以对Spring容器中的Bean做前后处理,postProcessBeforeInitialization处理时机是初始化之前,postProcessAfterInitialization的处理时机是初始化之后,这2个方法的返回值可以是原先生成的实例bean,或者使用wrapper包装后的实例。
FescarContextBeanPostProcessor将FeignContext包装成FescarFeignContext。
FescarBeanPostProcessor将FeignClient根据是否继承了LoadBalancerFeignClient包装成FescarLoadBalancerFeignClient和FescarFeignClient。
FeignAutoConfiguration中的FeignContext并没有加ConditionalOnXXX的条件,所以Fescar采用预置处理的方式将FeignContext包装成FescarFeignContext。
@Bean\npublicFeignContextfeignContext(){\nFeignContextcontext=newFeignContext();\ncontext.setConfigurations(this.configurations);\nreturncontext;\n}\n
而对于FeignClient,FeignClientFactoryBean中会获取FeignContext的实例对象。对于开发者采用@Configuration注解的自定义配置的FeignClient对象,这里会被配置到builder,导致FescarFeignBuilder中增强后的FescarFeignCliet失效。FeignClientFactoryBean中关键代码如下:
/**\n*@param<T>thetargettypeoftheFeignclient\n*@returna{@linkFeign}clientcreatedwiththespecifieddataandthecontextinformation\n*/\n<T>TgetTarget(){\nFeignContextcontext=applicationContext.getBean(FeignContext.class);\nFeign.Builderbuilder=feign(context);\nif(!StringUtils.hasText(this.url)){\nif(!this.name.startsWith(“http”)){\nurl=”http://”+this.name;\n}\nelse{\nurl=this.name;\n}\nurl+=cleanPath();\nreturn(T)loadBalance(builder,context,newHardCodedTarget<>(this.type,\nthis.name,url));\n}\nif(StringUtils.hasText(this.url)&&!this.url.startsWith(“http”)){\nthis.url=”http://”+this.url;\n}\nStringurl=this.url+cleanPath();\nClientclient=getOptional(context,Client.class);\nif(client!=null){\nif(clientinstanceofLoadBalancerFeignClient){\n//notloadbalancingbecausewehaveaurl,\n//butribbonisontheclasspath,sounwrap\nclient=((LoadBalancerFeignClient)client).getDelegate();\n}\nbuilder.client(client);\n}\nTargetertargeter=get(context,Targeter.class);\nreturn(T)targeter.target(this,builder,context,newHardCodedTarget<>(\nthis.type,this.name,url));\n}\n
上述代码根据是否指定了注解参数中的URL来选择直接调用URL还是走负载均衡,targeter.target通过动态代理创建对象。大致过程为:将解析出的feign方法放入map,再通过将其作为参数传入生成InvocationHandler,进而生成动态代理对象。
FescarContextBeanPostProcessor的存在,即使开发者对FeignClient自定义操作,依旧可以完成Fescar所需的全局事务的增强。
对于FescarFeignObjectWrapper,我们重点关注下Wrapper方法:
Objectwrap(Objectbean){\nif(beaninstanceofClient&&!(beaninstanceofFescarFeignClient)){\nif(beaninstanceofLoadBalancerFeignClient){\nLoadBalancerFeignClientclient=((LoadBalancerFeignClient)bean);\nreturnnewFescarLoadBalancerFeignClient(client.getDelegate(),factory(),\nclientFactory(),this.beanFactory);\n}\nreturnnewFescarFeignClient(this.beanFactory,(Client)bean);\n}\nreturnbean;\n}\n
wrap方法中,如果bean是LoadBalancerFeignClient的实例对象,那么首先通过client.getDelegate()方法将LoadBalancerFeignClient代理的实际Client对象取出后包装成FescarFeignClient,再生成LoadBalancerFeignClient的子类FescarLoadBalancerFeignClient对象。如果bean是Client的实例对象且不是FescarFeignClientLoadBalancerFeignClient,那么bean会直接包装生成FescarFeignClient。
上面的流程设计还是比较巧妙的,首先根据Springboot的AutoConfiguration控制了配置的先后顺序,同时自定义了FeignBuilder的Bean,保证了Client均是经过增强后的FescarFeignClient。再通过BeanPostProcessor对Spring容器中的Bean做了一遍包装,保证容器内的Bean均是增强后FescarFeignClient,避免FeignClientFactoryBeangetTarget方法的替换动作。
Hystrix隔离
下面我们再来看下Hystrix部分,为什么要单独把Hystrix拆出来看呢,而且Fescar代码也单独实现了个策略类。目前事务上下文RootContext的默认实现是基于ThreadLocal方式的ThreadLocalContextCore,也就是上下文其实是和线程绑定的。Hystrix本身有两种隔离状态的模式,基于信号量或者基于线程池进行隔离。Hystrix官方建议是采取线程池的方式来充分隔离,也是一般情况下在采用的模式:
ThreadorSemaphore\nThedefault,andtherecommendedsetting,istorunHystrixCommandsusingthreadisolation(THREAD)andHystrixObservableCommandsusingsemaphoreisolation(SEMAPHORE).\nCommandsexecutedinthreadshaveanextralayerofprotectionagainstlatenciesbeyondwhatnetworktimeoutscanoffer.\nGenerallytheonlytimeyoushouldusesemaphoreisolationforHystrixCommandsiswhenthecallissohighvolume(hundredspersecond,perinstance)thattheoverheadofseparatethreadsistoohigh;thistypicallyonlyappliestonon-networkcalls.\n
service层的业务代码和请求发出的线程肯定不是同一个,那么ThreadLocal的方式就没办法将XID传递给Hystrix的线程并传递给被调用方的。怎么处理这件事情呢,Hystrix提供了个机制让开发者去自定义并发策略,只需要继承HystrixConcurrencyStrategy重写wrapCallable方法即可。
publicclassFescarHystrixConcurrencyStrategyextendsHystrixConcurrencyStrategy{\nprivateHystrixConcurrencyStrategydelegate;\npublicFescarHystrixConcurrencyStrategy(){\nthis.delegate=HystrixPlugins.getInstance().getConcurrencyStrategy();\nHystrixPlugins.reset();\nHystrixPlugins.getInstance().registerConcurrencyStrategy(this);\n}\n@Override\npublic<K>Callable<K>wrapCallable(Callable<K>c){\nif(cinstanceofFescarContextCallable){\nreturnc;\n}\nCallable<K>wrappedCallable;\nif(this.delegate!=null){\nwrappedCallable=this.delegate.wrapCallable(c);\n}\nelse{\nwrappedCallable=c;\n}\nif(wrappedCallableinstanceofFescarContextCallable){\nreturnwrappedCallable;\n}\nreturnnewFescarContextCallable<>(wrappedCallable);\n}\nprivatestaticclassFescarContextCallable<K>implementsCallable<K>{\nprivatefinalCallable<K>actual;\nprivatefinalStringxid;\nFescarContextCallable(Callable<K>actual){\nthis.actual=actual;\nthis.xid=RootContext.getXID();\n}\n@Override\npublicKcall()throwsException{\ntry{\nRootContext.bind(xid);\nreturnactual.call();\n}\nfinally{\nRootContext.unbind();\n}\n}\n}\n}\n
Fescar也提供一个FescarHystrixAutoConfiguration,在存在HystrixCommand的时候生成FescarHystrixConcurrencyStrategy。
@Configuration\n@ConditionalOnClass(HystrixCommand.class)\npublicclassFescarHystrixAutoConfiguration{\n@Bean\nFescarHystrixConcurrencyStrategyfescarHystrixConcurrencyStrategy(){\nreturnnewFescarHystrixConcurrencyStrategy();\n}\n}\n
参考资料
Fescar:https://github.com/alibaba/fescarSpringCloudAlibaba:https://github.com/spring-cloud-incubator/spring-cloud-alibabaspring-cloud-openfeign:https://github.com/spring-cloud/spring-cloud-openfeign
本文作者
郭树抗,社区昵称ywind,曾就职于华为终端云,现搜狐智能媒体中心Java工程师,目前主要负责搜狐号相关开发,对分布式事务、分布式系统和微服务架构有异常浓厚的兴趣。
季敏(清铭),社区昵称slievrly,Fescar开源项目负责人,阿里巴巴中件间TXC/GTS核心研发成员,长期从事于分布式中间件核心研发工作,在分布式事务领域有着较丰富的技术积累。
延伸阅读
微服务架构下,解决数据一致性问题的实践
https://mp.weixin.qq.com/s
作者:中间件小哥
OK,关于深度网站源码分享是什么和有深度的网站或论坛的内容到此结束了,希望对大家有所帮助。