当前位置: 首页 > 科技观察

Dubbo先启动客户端,再启动服务端,网上收银系统崩溃

时间:2023-03-12 04:40:18 科技观察

,线上出事故。前天晚上上线了,还发生了一件很有意思的事情。昨天复习,今天分享。晚上,我负责的系统和收银系统同时上线(用的是dubbo)。然后惊人的事情发生了。收银系统给我的接口注入了@Reference注解,然后这个接口的实现类其实是空的。其实我们当时并没有找出原因?“重启就好了,毕竟重启大法就好了。”但是为了不给用户充值造成障碍,我们还是要研究下代理对象是怎么为空的。的。《网上dubbo的版本是2.8.9,注意包名是(com.alibaba)》为了方便大家理解我说的,我简单说一下RPC框架的执行过程。Server向Registry注册服务信息,Client从Registry中拉取Server信息。客户端通过代理对象(ClientStub)发送网络请求,服务器通过代理对象(ServerStub)执行本地方法。在网络传输过程中有一个编码、解码和序列化的过程。“在Dubbo中,ClientStub和ServerStub都是Invoker对象。”继续,注入的接口实现类可以为空吗?我只是看了一下他写的代码,只用了一个@Reference注解,没有设置任何属性。@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD,ElementType.METHOD,ElementType.ANNOTATION_TYPE})public@interfaceReference{//省略其他属性booleancheck()defaulttrue;}然后check=true,即没有serviceprovided当使用后者时,服务消费者无法正常启动,因为会抛出IllegalStateException。既然可以正常启动,代理对象已经正常创建,不能为null//2.8.9版本//ReferenceConfig#createProxyBooleanc=check;if(c==null&&consumer!=null){c=consumer.isCheck();}if(c==null){c=true;//defaulttrue}if(c&&!invoker.isAvailable()){thrownewIllegalStateException("Failedtocheckthestatusoftheservice"+interfaceName+".Noprovideravailablefortheservice"+(group==null?"":group+"/")+interfaceName+(version==null?"":":"+version)+"fromtheurl"+invoker.getUrl()+"totheconsumer"+NetUtils.getLocalHost()+"usedubboversion"+Version。getVersion());}"那我同事说,会不会是客户端先启动了,代理对象是空的,因为没有服务提供者?"我说不可能,先启动客户端,check属性为true,不可能启动成功!再说了,每次上线,新服务正常启动后,旧服务就会关闭,服务商肯定会有。“为什么会这样,我实在是不明白,只能google“@Referenceinjectionobjectisnull””答案基本一样,没有服务提供者,代理对象为空,设置check属性即可@Reference到False就足够了。至于原因,没有一篇文章说“下一步要在网上验证方法”。先启动生产者,再启动消费者。正常调用,先启动消费者(check=true),再启动生产者。代理对象为空。完美再现。先启动消费者(check=false),再启动生产者,正常调用。学习dubbo时用的例子,测试了一段时间。dubbo的版本是2.7.3。请注意,包名称是(org.apache)。先启动生产者,再启动消费者。正常调用,先启动消费者(check=true),此时没有生产者。如果启动失败,先启动消费者(check=false),再启动生产者,正常调用“这符合我的想法”。真相大白。由于@Reference注入的对象为null,说明SpringBean生命周期中有一个属性赋值阶段。我们来分析一下@Reference注解的注入逻辑,和@Autowired、@Resource注解的注入逻辑基本类似。当你加入Dubbo的springbootstarter时,ReferenceAnnotationBeanPostProcessor会被注入到容器中。看一下这个类的继承关系,其中最重要的部分你只需要知道这个类重写了InstantiationAwareBeanPostProcessor#postProcessPropertyValues(这个方法在以后的版本中会被postProcessProperties方法代替),使用的就是这个方法属性赋值见上面的Bean生命周期图bean.getClass(),pvs);try{metadata.inject(bean,beanName,pvs);}catch(BeanCreationExceptionex){throwex;}catch(Throwableex){thrownewBeanCreationException(beanName,"Injectionof@Referencedependenciesfailed",ex);}returnpvs;}}然后执行到ReferenceFieldElement#inject方法,obj@Reference引入的ect会被转换为ReferenceBeanprivateclassReferenceFieldElementextendsInjectionMetadata.InjectedElement{@OverrideNameanbeperjectedvoValuespvs)throwsThrowable{ClassreferenceClass=field.getType();//获取referenceBean的逻辑在这里referenceBean=buildReferenceBean(reference,referenceClass);ReflectionUtils.makeAccessible(field);//通过反射域注入对象。set(bean,referenceBean.getObject());}}一系列方法调用后执行到下面的方法//AbstractAnnotationConfigBeanBuilder#buildpublicfinalBbuild()throwsException{checkDependencies();Bbean=doBuild();configureBean(bean);if(logger.isInfoEnabled()){logger.info(bean+"hasbeenbuilt.");}returnbean;}此时log会打印ReferenceBean对象,它继承了AbstractConfig,所以会执行AbstractConfig#toString方法publicabstractclassAbstractConfigimplementsSerializable{@OverridepublicStringtoString(){try{StringBuilderbuf=newStringBuilder();buf.append("");returnbuf.toString();}catch(Throwablet){logger.warn(t.getMessage(),t);returnsuper.toString();}}}好家伙,我在打印的时候执行了所有的get方法,然后执行了ReferenceBean#getObject方法异常(就是那里服务商抛出的也不例外),但是被trycatch了。”因为ReferenceBean是一个FactoryBean,所以需要调用getObject方法获取创建的对象privateclassReferenceFieldElementextendsInjectionMetadata.InjectedElement{@Overrideprotectedvoidinject(Objectbean,StringbeanName,PropertyValuespvs)throwsThrowable{ClassreferenceClass=field.getType();//获取referenceBean的逻辑在这里referenceBean=buildReferenceBean(reference,referenceClass);ReflectionUtils.makeAccessible(field);//通过反射。set(bean,referenceBean.getObject());}}"然后调用ReferenceBean#getObject方法,好了,这就是服务导出的逻辑!"不细说了,后续单开文章会执行到ReferenceConfig#get方法//ReferenceConfig#getpublicsynchronizedTget(){if(destroyed){thrownewIllegalStateException("Alreadydestroyed!");}if(ref==null){init();}returnref;}"此时代理对象为null,执行init方法,initialized默认为false,执行一次变为true(AbstractConfig执行toString方法时),所以第二次执行直接返回,此时代理对象为null,结束!”privatevoidinit(){if(initialized){return;}initialized=true;//省略部分代码}”为什么我用的版本学习正常工作?”publicfinalCbuild()throwsException{checkDependencies();CconfigBean=doBuild();configureBean(configBean);if(logger.isInfoEnabled()){logger.info("TheconfigBean[type:"+configBean.getClass().getSimpleName()+"]已构建。");}returnconfgBean;}表示打印时不执行getObject方法。“为什么把@Reference的check属性设置为false就可以正常调用了?”因为第一次调用成功执行了ReferenceBean#getObject方法,ref已经被赋值为代理对象,第二次执行可以返回这个代理对象//ReferenceConfig#getpublicsynchronizedTget(){if(destroyed){thrownewIllegalStateException("Alreadydestroyed!");}if(ref==null){init();}returnref;}至于为什么我们的线上系统没有获取到serviceprovider?我想这可能是网络的原因。解决方法是将@Reference注解的check属性设置为false(默认为true),因为当你的check属性为true并且没有服务提供者使用的时候,它不会起到任何作用,只是一个将注入空对象。当有可用的服务提供者时,此对象将始终为空。当check为false时,将注入一个代理对象。当有服务提供者时,这个代理对象会被刷新,可以正常发起调用。选择可以正常执行的版本。转载本文请联系Java石塘公众号。