专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

服务端模块化架构设计RPC模块化设计与分布式事务

  模块间的调用问题
  由于我们的模块是可以任意组合的,所以就会有一个问题:当两个模块是打包在一起的时候,相当于是内部调用当两个模块是在两个不同的服务的时候,就变成的远程调用
  也就是说,我们需要为每一种组合都适配一遍
  这不要了命了么?
  不要急,我有办法,一套代码适配两种情况用户接口示例
  我们在之前实现的juejinpin(沸点)模块中,就有用户模型,比如发布沸点的用户,评论的用户等等
  而用户的相关业务我们有单独的juejinuser(用户)模块,所以juejinpin(沸点)模块中的用户信息就需要从juejinuser(用户)模块中获取
  这里就会出现我们之前说的问题
  如果juejinpin(沸点)和juejinuser(用户)是合并在一起的,就像juejinappliactionsingle,那么可以直接进行内部调用
  如果juejinpin(沸点)和juejinuser(用户)是分开的,就像juejinappliactionsystem和juejinappliactionpin,那么需要通过远程服务调用抽象模块接口
  对于获得用户信息这个功能,我们先定义一个接口UserApipublicinterfaceUserApi{通过id获得用户信息UserROget(Stringid);}复制代码
  其中UserRO是userremoteobject,表示远程的,非本模块的用户对象
  我们可以把这个接口放在juejinbasic中,这样其他的模块也能进行复用RemoteUserRepository
  我们为juejinpin(沸点)模块中的UserRepository实现一个RemoteUserRepositoryRepositorypublicclassRemoteUserRepositoryimplementsUserRepository{AutowiredprivateUserApiuserApi;根据id获得一个领域模型OverridepublicUserget(Stringid){returnro2do(userApi。get(id));}publicUserro2do(UserROro){模型转换}省略其他代码}复制代码
  当我们的juejinpin(沸点)模块调用UserRepositoryget(id)时,实际是通过UserApiget(id)来获得用户信息,再通过ro2do将UserRO转为我们juejinpin(沸点)模块中指定的User模型实现UserApi
  接下来我们分别实现内部调用和远程服务调用这两种用户获取方式InnerUserApi
  在juejinuser(用户)模块中实现InnerUserApiComponentpublicclassInnerUserApiimplementsUserApi{这个是juejinuser中的UserRepositoryAutowiredprivateUserRepositoryuserRepository;这个是juejinuser中的UserFacadeAdapterAutowiredprivateUserFacadeAdapteruserFacadeAdapter;OverridepublicUserROget(Stringid){UseruseruserRepository。get(id);returnuserFacadeAdapter。do2ro(user);}}复制代码
  我们只需要直接调用UserRepository就行了
  这条链路是这样的:
  如果模块是合并的,那么直接通过内部的juejinuser(用户)模块提供的InnerUserApi就能获得用户信息了FeignUserApi
  在juejinbasic(基础)模块中实现FeignUserApipublicclassFeignUserApiimplementsUserApi{AutowiredprivateUserFeignClientuserFeignClient;OverridepublicUserROget(Stringid){ResponseUserROresponseuserFeignClient。get(id);if(response。isSuccess()){returnresponse。getObject();}thrownewRuntimeException(response。getMessage());}}FeignClient(namejuejinuser)publicinterfaceUserFeignClient{GetMapping(user{id})ResponseUserROget(PathVariableStringid);}复制代码
  这里需要集成UserFeignClient,通过Feign的方式来获得用户的信息
  这条链路是这样的:
  如果模块间是分开的,分别位于不同的服务中,就需要通过Feign等RPC方式了Feign路由映射
  我们的juejinuser(用户)模块对应的服务实际上是juejinappliactionsystem,或者是其他的名称(不同的模块组合可能会有不同的命名)
  但是如果每种组合方式都要手动修改对应的名称,那肯定不行,太麻烦了
  我们可以看到在上面的示例中指定为对应的模块名称juejinuser,也就是UserFeignClient上的注解FeignClient的参数是juejinuser
  但是只是这样还不行,毕竟我们没有一个叫juejinuser的服务
  所以我们要想办法让juejinuser能够根据不同模块组合动态的映射为对应的服务名称
  这个功能其实我们已经在网关路由模块化支持与条件配置实现过了,大概的流程是在build。gradle中添加额外的脚本生成router。properties,其中记录当前服务包含的模块processResources{资源文件处理之前doFirst{SetStringmSetnewHashSet()遍历所有的依赖project。configurations。forEach(configuration{configuration。allDependencies。forEach(dependency{如果是我们项目中的业务模块则添加该模块名称if(dependency。groupcom。bytedance。juejin){mSet。add(dependency。name)}})})移除,基础模块不需要路由mSet。remove(juejinbasic)如果包含了业务模块if(!mSet。isEmpty()){获得资源目录FileresourcesDirnewFile(project。projectDir,srcmainresources)创建路由文件FilefilenewFile(resourcesDir,router。properties)if(!file。exists()){file。createNewFile()}将模块信息写入文件PropertiespropertiesnewProperties()properties。setProperty(routers,String。join(,,mSet))OutputStreamosnewFileOutputStream(file)properties。store(os,Routersgeneratedfile)os。close()}}}复制代码读取router。properties将数据同步到注册中心ComponentpublicclassRouterRegister{监听服务注册前置事件EventListenerpublicvoidregister(InstancePreRegisteredEventevent)throwsException{读取router。properties资源文件ClassPathResourceresourcenewClassPathResource(router。properties);加载到Properties中PropertiespropertiesnewProperties();try(InputStreamisresource。getInputStream()){properties。load(is);}获得routers值Stringroutersproperties。getProperty(routers);写入metadata中MapString,Stringmetadataevent。getRegistration()。getMetadata();metadata。put(routers,routers);}}复制代码
  (上面两块更详细的内容可以看网关路由模块化支持与条件配置中的实现)监听心跳事件刷新模块和服务的映射关系
  这里我们只要把网关的路由刷新逻辑移过来就行了Slf4jpublicclassRouterLoadBalancerClientFactoryextendsLoadBalancerClientFactory{privatefinalDiscoveryClientdiscoveryClient;privatevolatileMapString,StringrouterMapCollections。emptyMap();publicRouterLoadBalancerClientFactory(LoadBalancerClientsPropertiesproperties,DiscoveryClientdiscoveryClient){super(properties);this。discoveryClientdiscoveryClient;}OverridepublicTTgetInstance(Stringname,ClassTtype){StringroutergetRouter(name);log。info(Routermapping:{}{},name,router);returnsuper。getInstance(router,type);}protectedStringgetRouter(Stringname){returnrouterMap。getOrDefault(name,name);}监听心跳事件EventListenerpublicvoidrefreshRouters(HeartbeatEventevent){新的路由映射MapString,StringnewRouterMapnewHashMap();获得服务名ListStringservicesdiscoveryClient。getServices();for(Stringservice:services){获得服务实例ListServiceInstanceinstancesdiscoveryClient。getInstances(service);if(instances。isEmpty()){continue;}这里直接拿第一个ServiceInstanceinstanceinstances。get(0);获得metadata中的routersStringroutersMetadatainstance。getMetadata()。getOrDefault(routers,);String〔〕routersroutersMetadata。split(,);for(Stringrouter:routers){newRouterMap。put(router,service);}}if(!this。routerMap。equals(newRouterMap)){log。info(Updateroutermap{},newRouterMap);}更新缓存this。routerMapnewRouterMap;}}复制代码
  通过监听服务注册的心跳,同步模块和服务的映射关系
  扩展LoadBalancerClientFactory,在中间添加一步将模块名称映射为服务名称的逻辑
  这里高版本的SpringCloud用的是springcloudloadbalancer做的负载均衡,所以我们扩展LoadBalancerClientFactory就行了
  如果是低版本,用的是ribbon,扩展的类是不一样的,有需要的话可以看【SpringCloud】协同开发利器之动态路由RibbonLoadBalancer解析篇,也可以参考这个库的源码来扩展ribbon条件配置
  最后还需要添加一个配置类ConfigurationAutoConfigureBefore(LoadBalancerAutoConfiguration。class)EnableFeignClients(basePackagescom。bytedance。juejin。basic。rpc。feign)publicclassFeignAutoConfiguration{BeanConditionalOnMissingBeanpublicUserApiuserApi(){returnnewFeignUserApi();}BeanpublicLoadBalancerClientFactoryrouterLoadBalancerClientFactory(LoadBalancerClientsPropertiesproperties,DiscoveryClientdiscoveryClient){returnnewRouterLoadBalancerClientFactory(properties,discoveryClient);}}复制代码
  用ConditionalOnMissingBean标记FeignUserApi
  当juejinpin(沸点)和juejinuser(用户)是合并在一起的时候,Spring会识别到InnerUserApi,于是不会注入FeignUserApi,所有的用户接口都会走本地用户模块的UserRepository
  当juejinpin(沸点)和juejinuser(用户)是分开的时候,FeignUserApi会被注入,所有的用户接口都会走Feign
  这样我们只需要根据需求定义对应的xxApi,然后分别实现InnerApi和FeignApi或是DubboApi的方式,之后无论我们对模块进行怎么样的自由组合都能够自动适配,不需要额外的手动处理分布式事务问题
  如果我们的模块间调用需要用到分布式事务是否存在一些方式能够做到兼容呢,当两个模块合并在一起的时候就用本地事务,当两个模块分开的时候就用分布式事务,根据模块间的组合方式自动识别切换
  目前我的答案是不太好做(当然如果有大佬想到比较好的方式也可以分享一下)
  现在有如下的代码PostMapping(test)SmartTransactional我们自己实现事务切面publicvoidtest(){a。a();本地调用b。b();本地调用或服务间调用}复制代码
  如果我们自己实现事务切面
  我们什么时候能知道是不是服务间调用?b。b()调用的时候,我们可以根据不同的实现确定是本地调用还是服务间调用
  当我们调用b。b()确定了服务间调用需要选择分布式事务的时候,a。a()已经执行了
  所以我们其实没办法在方法开始之前确定方法中是否会有服务间调用,更何况还会有嵌套事务等复杂场景
  如果一定要用分布式事务的话,还是单独处理比较好,可以额外加一个方法PostMapping(testlocal)TransactionalpublicvoidtestLocal(){a。a();本地调用b。b();本地调用}PostMapping(testseata)GlobalTransactionalpublicvoidtestSeata(){a。a();本地调用b。b();服务间调用}复制代码
  这样的写的话也不需要频繁修改,只需要让前端调不同的接口就行了
  而且一般来说需要用到分布式事务的也就几个核心场景,不会特别多
  所以这种方式虽说加入了一些人工判断但应该也不会特别麻烦总结
  要一套代码适配不同的场景其实就是定义一个接口然后进行多种实现,其优势在于借助接口的特性在不同场景下适配不同的实现,不仅不需要频繁修改代码,还可以实现InnerUserApi,FeignUserApi,DubboUserApi等多种方式,甚至其他系统的用户信息,如DouYinUserApi
  同时借助已有的组件为我们服务,如Spring的条件配置,注册中心的组件能力等

常规赛增加至42轮CBA新赛季10月10日打响京报体育记者陈嘉堃王洋刘平9月23日,20222023赛季CBA联赛发布会在北京举行,新赛季将于10月10日在杭州打响,第一阶段仍为赛会制。后续比赛将根据疫情防控形势,继续努力推动新疆大漠发现7个神秘太阳,3800年依然光芒四射,耀眼夺目罕见的天文奇观九星联珠漫步美丽新疆,聆听千年故事,我是天山老叔。古代神话后羿射日的故事,在中国可谓家喻户晓。据楚辞章句记载尧时十日并出,草木焦枯,尧命羿射十日,中其九日,日中九乌皆事业单位笔试第1!女排丁霞考出好成绩,网友希望不是萝卜招聘中国女排国家队正在全力准备世界锦标赛,本次世界锦标赛对中国女排来说是一次证明自己的好机会,如果能够在世界锦标赛上拿到冠军,那么中国女排将会书写新的辉煌。目前来看,中国女排正处于低谷秋分秋思太行山山西建武离开上当有三十多年了一直都想着回长治转转看看曾经住过的老营房握握还健在老首长的手走走英雄街逛逛英雄台老顶山的秋天满山枫叶已泛滥成红的海洋走在弯弯曲曲的山道上酸溜溜拥挤着向我转骨趁秋天,秋季是一年之中长高的最后黄金季,你家孩子抓住了么我在头条搞创作第二期大家好,我是张女子育儿!女生是一白遮百丑,男生则是一高遮百丑!为了孩子能够长个子,父母大多都会抓住春季这个孩子身高发育的黄金时期,一旦错过了,就会后悔自责不已。喝醋能软化血管?错!搭配这2样,秋季养生功效加倍醋是一种深受人们喜爱的调味品网上也有许多关于醋的好处比如喝醋能软化血管?熏醋可以杀菌预防感冒?鱼刺卡喉喝醋就管用?醋真的有这么神奇吗?吃多了也没事?快跟着小圈一起来看看史文丽中国康6小时4签约!麦科勒姆续约,太阳18人大名单,联盟总裁被挖角北京时间9月25日,NBA新赛季临近,各支球队逐渐开启训练营,自由市场仍有签约达成,过去6小时又有4笔签约完成。鹈鹕与麦科勒姆完成续约,太阳签下卢瓦乌,骑士续约迪恩韦德,快船再签1书是一种知识深奥的老师书,是一位知识渊博的老师,它带我畅游理性世界,领略大自然风光,了解大自然奥秘,它能让我懂得许多人生哲理。书,用它丰富的知识甘露,浇灌了我求知的心田。文言文经典句子1好学近乎知,力行散文中年如秋,诗意更浓作者子墨一叶知秋,更是一夜大风之后,便已是深秋。听说张家口下雪了,可以想象那种雪花飘飞的感觉,仿佛我所在的小城也受了影响,一夜之间,就到了秋的尾声。那只是一种错觉,实际上离暮秋还远晨读金子般的花开了苦难已经远去,幸福树开出了无法形容的花,它是金子般的花。当初我离开办公室时只是想,不过是小别十天八天,我为室内每盆花草浇足了水,花儿草儿不会有不测。谁能料想,我的预测是那般苍白那般香菇最下饭的做法,鲜香爽滑,营养好吃还入味,出锅全家抢着吃大家好,我是慧慧,今天用香菇分享一个超好吃的做法,色香味俱全,比吃大鱼大肉还要过瘾,下面一起来看看吧。准备适量的香菇,放在大碗里,用简单把根蒂剪掉不要,再放入一勺食用盐,再来点小苏
二牛相争,老农说了算公婆各执一词,裁判说了算周未一场火星撞地球的比赛,曼城德比,牵动数千万球迷的心,比赛结果让砖家们大跌眼镜,两方球迷冰火两重天。比赛结果既意料之外而情理之中,曼联前段时间陷入我罗与球社保卡常见误区,你听过几个?社保卡只能在户籍地办理每换一个城市工作,就需要办一张新社保卡儿童办不了社保卡这些社保卡的常见误区你听说过吗?今天,小编带你正确了解社保卡!误区一社保卡只能在户籍地办理!不对!可以异年夜饭没有肉丸子怎么行,裹满酱汁端上桌,全家人都赞不绝口!如果说逢年过节有一样必吃的美食,那肉丸子一定是榜上有名,过年吃丸子象征着一家人团团圆圆,好运连连,丸子一出锅,更显年味浓了,我家年夜饭桌上每每都会给它留个位置。传统的丸子油炸做法不懒人也能精致养生?领尚茶饮机助你一键实现现如今这个时代,越来越爱惜健康的年轻人,对养生的关注度越来越高。像我平时就喜欢清晨空腹一杯温水促进肠道蠕动上午泡杯咖啡为工作加满活力慵懒的午后冲泡一杯茶通过补充足够的水分,保持身体新款M2Macmini和MacBookPro14和16英寸机型支持HDMI上的8K输出wccftech报道,好消息如果您想在Mac上使用8K显示器,那么新更新的阵容将对您有好处。12023Macmini和MacBookPro都支持通过HDMI实现惊人的8K输出新发布开机会显示广告的所谓智能电视机,你能忍下去吗?广告有开机广告待机(壁纸)广告APP广告视频广告等等各种形式,对于电视机尤其是智能电视机来说,避免不了。现在能有开机广告免掉就不错了,一线品牌TCL海信小米和索尼索尼TCL没有,其世界首台!新飞成功研发120空冷超低温冰箱新飞电器官方消息,新飞研发团队研发出世界首台空气制冷超低温冰箱,实现制冷温度突破120。据其介绍,目前市场销售的低温制冷产品,均基于复叠式蒸汽压缩制冷技术或非共沸混合工质节流制冷技17合1集大成,拆解联想子品牌一款多功能扩展坞,看看做得如何前言来酷科技推出了一款支持4K分辨率的扩展坞,这款扩展坞支持两台主机连接,支持PD供电,4K60Hz视频,具备USB接口,3。5mm音频接口和千兆网口,还支持高速读卡器功能。支持U钉钉企微飞书登录注册功能的设计用户及其产生的数据,是科技公司的核心资产。登录注册几乎是所有APP都会有功能,每个账号的背后代表一个用户。注册登录功能,则是用户提出申请,并获授权使用产品的起点。随着APP的发展和你会选购冷柜吗?小编带你盘盘它玉兔将临门,福气照华宇,又是一年春节将至,家家户户都是其乐融融,忙碌着准备各种美食,大包小包的各种食材买回家,这时候就出现一个问题,小容量冰箱一放就满,还必须保持食材(特别是肉类)驼背的人(小小说)不知从何时起聂驼子爱驼着个背,他说这样弯着腰非常舒服。开初一段时间,有人看不过提醒几句。聂驼子倒虚心接受,使劲绷直腰,但过不了一会儿,他又弯成了一把弓。于是提醒的人便緘口不言,懒得
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网