范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

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

  模块间的调用问题
  由于我们的模块是可以任意组合的,所以就会有一个问题:当两个模块是打包在一起的时候,相当于是内部调用当两个模块是在两个不同的服务的时候,就变成的远程调用
  也就是说,我们需要为每一种组合都适配一遍
  这不要了命了么?
  不要急,我有办法,一套代码适配两种情况用户接口示例
  我们在之前实现的juejin-pin(沸点)模块中,就有用户模型,比如发布沸点的用户,评论的用户等等
  而用户的相关业务我们有单独的juejin-user(用户)模块,所以juejin-pin(沸点)模块中的用户信息就需要从juejin-user(用户)模块中获取
  这里就会出现我们之前说的问题
  如果juejin-pin(沸点)和juejin-user(用户)是合并在一起的,就像juejin-appliaction-single,那么可以直接进行内部调用
  如果juejin-pin(沸点)和juejin-user(用户)是分开的,就像juejin-appliaction-system和juejin-appliaction-pin,那么需要通过远程服务调用抽象模块接口
  对于获得用户信息这个功能,我们先定义一个接口UserApipublic interface UserApi {      /**      * 通过id获得用户信息      */     UserRO get(String id); } 复制代码
  其中UserRO是user remote object,表示远程的,非本模块的用户对象
  我们可以把这个接口放在juejin-basic中,这样其他的模块也能进行复用RemoteUserRepository
  我们为juejin-pin(沸点)模块中的UserRepository实现一个RemoteUserRepository@Repository public class RemoteUserRepository implements UserRepository {      @Autowired     private UserApi userApi;          /**      * 根据 id 获得一个领域模型      */     @Override     public User get(String id) {         return ro2do(userApi.get(id));     }      public User ro2do(UserRO ro) {         //模型转换     }          //省略其他代码 } 复制代码
  当我们的juejin-pin(沸点)模块调用UserRepository#get(id)时,实际是通过UserApi#get(id)来获得用户信息,再通过ro2do将UserRO转为我们juejin-pin(沸点)模块中指定的User模型实现UserApi
  接下来我们分别实现内部调用和远程服务调用这两种用户获取方式InnerUserApi
  在juejin-user(用户)模块中实现InnerUserApi@Component public class InnerUserApi implements UserApi {      /**      * 这个是juejin-user中的UserRepository      */     @Autowired     private UserRepository userRepository;      /**      * 这个是juejin-user中的UserFacadeAdapter      */     @Autowired     private UserFacadeAdapter userFacadeAdapter;      @Override     public UserRO get(String id) {         User user = userRepository.get(id);         return userFacadeAdapter.do2ro(user);     } } 复制代码
  我们只需要直接调用UserRepository就行了
  这条链路是这样的:
  如果模块是合并的,那么直接通过内部的juejin-user(用户)模块提供的InnerUserApi就能获得用户信息了FeignUserApi
  在juejin-basic(基础)模块中实现FeignUserApipublic class FeignUserApi implements UserApi {      @Autowired     private UserFeignClient userFeignClient;      @Override     public UserRO get(String id) {         Response response = userFeignClient.get(id);         if (response.isSuccess()) {             return response.getObject();         }         throw new RuntimeException(response.getMessage());     } }  @FeignClient(name = "juejin-user") public interface UserFeignClient {      @GetMapping("/user/{id}")     Response get(@PathVariable String id); } 复制代码
  这里需要集成UserFeignClient,通过Feign的方式来获得用户的信息
  这条链路是这样的:
  如果模块间是分开的,分别位于不同的服务中,就需要通过Feign等RPC方式了Feign路由映射
  我们的juejin-user(用户)模块对应的服务实际上是juejin-appliaction-system,或者是其他的名称(不同的模块组合可能会有不同的命名)
  但是如果每种组合方式都要手动修改对应的名称,那肯定不行,太麻烦了
  我们可以看到在上面的示例中指定为对应的模块名称juejin-user,也就是UserFeignClient上的注解@FeignClient的参数是juejin-user
  但是只是这样还不行,毕竟我们没有一个叫juejin-user的服务
  所以我们要想办法让juejin-user能够根据不同模块组合动态的映射为对应的服务名称
  这个功能其实我们已经在 网关路由模块化支持与条件配置 实现过了,大概的流程是在build.gradle中添加额外的脚本生成router.properties,其中记录当前服务包含的模块processResources {     //资源文件处理之前     doFirst {         Set mSet = new HashSet<>()         //遍历所有的依赖         project.configurations.forEach(configuration -> {             configuration.allDependencies.forEach(dependency -> {                 //如果是我们项目中的业务模块则添加该模块名称                 if (dependency.group == "com.bytedance.juejin") {                     mSet.add(dependency.name)                 }             })         })         //移除,基础模块不需要路由         mSet.remove("juejin-basic")         //如果包含了业务模块         if (!mSet.isEmpty()) {             //获得资源目录             File resourcesDir = new File(project.projectDir, "/src/main/resources")             //创建路由文件             File file = new File(resourcesDir, "router.properties")             if (!file.exists()) {                 file.createNewFile()             }             //将模块信息写入文件             Properties properties = new Properties()             properties.setProperty("routers", String.join(",", mSet))             OutputStream os = new FileOutputStream(file)             properties.store(os, "Routers generated file")             os.close()         }     } } 复制代码读取router.properties将数据同步到注册中心@Component public class RouterRegister {      /**      * 监听服务注册前置事件      */     @EventListener     public void register(InstancePreRegisteredEvent event) throws Exception {         //读取 router.properties 资源文件         ClassPathResource resource = new ClassPathResource("router.properties");         //加载到 Properties 中         Properties properties = new Properties();         try (InputStream is = resource.getInputStream()) {             properties.load(is);         }         //获得 routers 值         String routers = properties.getProperty("routers");         //写入 metadata 中         Map metadata = event.getRegistration().getMetadata();         metadata.put("routers", routers);     } } 复制代码
  (上面两块更详细的内容可以看 网关路由模块化支持与条件配置 中的实现)监听心跳事件刷新模块和服务的映射关系
  这里我们只要把网关的路由刷新逻辑移过来就行了@Slf4j public class RouterLoadBalancerClientFactory extends LoadBalancerClientFactory {      private final DiscoveryClient discoveryClient;      private volatile Map routerMap = Collections.emptyMap();      public RouterLoadBalancerClientFactory(LoadBalancerClientsProperties properties, DiscoveryClient discoveryClient) {         super(properties);         this.discoveryClient = discoveryClient;     }      @Override     public  T getInstance(String name, Class type) {         String router = getRouter(name);         log.info("Router mapping: {} => {}", name, router);         return super.getInstance(router, type);     }      protected String getRouter(String name) {         return routerMap.getOrDefault(name, name);     }      /**      * 监听心跳事件      */     @EventListener     public void refreshRouters(HeartbeatEvent event) {         //新的路由映射         Map newRouterMap = new HashMap<>();         //获得服务名         List services = discoveryClient.getServices();         for (String service : services) {             //获得服务实例             List instances = discoveryClient.getInstances(service);             if (instances.isEmpty()) {                 continue;             }             //这里直接拿第一个             ServiceInstance instance = instances.get(0);             //获得 metadata 中的 routers             String routersMetadata = instance.getMetadata()                     .getOrDefault("routers", "");             String[] routers = routersMetadata.split(",");              for (String router : routers) {                 newRouterMap.put(router, service);             }         }         if (!this.routerMap.equals(newRouterMap)) {             log.info("Update router map => {}", newRouterMap);         }         //更新缓存         this.routerMap = newRouterMap;     } } 复制代码
  通过监听服务注册的心跳,同步模块和服务的映射关系
  扩展LoadBalancerClientFactory,在中间添加一步将模块名称映射为服务名称的逻辑
  这里高版本的Spring Cloud用的是spring-cloud-loadbalancer做的负载均衡,所以我们扩展LoadBalancerClientFactory就行了
  如果是低版本,用的是ribbon,扩展的类是不一样的,有需要的话可以看 【Spring Cloud】协同开发利器之动态路由|Ribbon & LoadBalancer 解析篇,也可以参考这个库的源码来扩展ribbon条件配置
  最后还需要添加一个配置类@Configuration @AutoConfigureBefore(LoadBalancerAutoConfiguration.class) @EnableFeignClients(basePackages = "com.bytedance.juejin.basic.rpc.feign") public class FeignAutoConfiguration {      @Bean     @ConditionalOnMissingBean     public UserApi userApi() {         return new FeignUserApi();     }      @Bean     public LoadBalancerClientFactory routerLoadBalancerClientFactory(LoadBalancerClientsProperties properties,                                                                      DiscoveryClient discoveryClient) {         return new RouterLoadBalancerClientFactory(properties, discoveryClient);     } } 复制代码
  用@ConditionalOnMissingBean标记FeignUserApi
  当juejin-pin(沸点)和juejin-user(用户)是合并在一起的时候,Spring会识别到InnerUserApi,于是不会注入FeignUserApi,所有的用户接口都会走本地用户模块的UserRepository
  当juejin-pin(沸点)和juejin-user(用户)是分开的时候, FeignUserApi会被注入,所有的用户接口都会走Feign
  这样我们只需要根据需求定义对应的xxApi,然后分别实现InnerApi和FeignApi或是DubboApi的方式,之后无论我们对模块进行怎么样的自由组合都能够自动适配,不需要额外的手动处理分布式事务问题
  如果我们的模块间调用需要用到分布式事务是否存在一些方式能够做到兼容呢,当两个模块合并在一起的时候就用本地事务,当两个模块分开的时候就用分布式事务,根据模块间的组合方式自动识别切换
  目前我的答案是不太好做(当然如果有大佬想到比较好的方式也可以分享一下)
  现在有如下的代码@PostMapping("/test") @SmartTransactional//我们自己实现事务切面 public void test() {     a.a();//本地调用     b.b();//本地调用或服务间调用 } 复制代码
  如果我们自己实现事务切面
  我们什么时候能知道是不是服务间调用?b.b()调用的时候,我们可以根据不同的实现确定是本地调用还是服务间调用
  当我们调用b.b()确定了服务间调用需要选择分布式事务的时候,a.a()已经执行了
  所以我们其实没办法在方法开始之前确定方法中是否会有服务间调用,更何况还会有嵌套事务等复杂场景
  如果一定要用分布式事务的话,还是单独处理比较好,可以额外加一个方法@PostMapping("/test-local") @Transactional public void testLocal() {     a.a();//本地调用     b.b();//本地调用 }  @PostMapping("/test-seata") @GlobalTransactional public void testSeata() {     a.a();//本地调用     b.b();//服务间调用 } 复制代码
  这样的写的话也不需要频繁修改,只需要让前端调不同的接口就行了
  而且一般来说需要用到分布式事务的也就几个核心场景,不会特别多
  所以这种方式虽说加入了一些人工判断但应该也不会特别麻烦总结
  要一套代码适配不同的场景其实就是定义一个接口然后进行多种实现,其优势在于借助接口的特性在不同场景下适配不同的实现,不仅不需要频繁修改代码,还可以实现InnerUserApi,FeignUserApi,DubboUserApi等多种方式,甚至其他系统的用户信息,如DouYinUserApi
  同时借助已有的组件为我们服务,如Spring的条件配置,注册中心的组件能力等

8万左右的车,有什么推荐吗?经济汽车选择顺序一定是轿车优于SUV,同等价位轿车的配置一定是比SUV高的,而且功能也更齐全。如果一定要买SUV那就选自己喜欢的,因为同等价位的SUV都差不多。比如一个品牌在你看到一提到湖南省永州市,你首先会想到什么?我曾经是一个吃货,所以现在十分悲哀地得了糖尿病!捂脸一提到湖南省永州市,我首先会到永州血鸭和东安鸡。永州血鸭是湖南永州当地家家户户都会做的一道传统名菜。仔鸭下锅翻炒,淋入新鲜的鸭血哈尔滨最值得吃的饭店都有哪些?酒香不怕巷子深!作为一个哈尔滨的本地人,必须要为你推荐一些本地人经常去吃的饭店,好吃到不行!推荐一轩辕回民。哈尔滨的回民饭店还是相当有特色的,这家是一家隐藏在小区里的小饭店。门脸不准备西安到海南自驾游,请问各位大侠能否介绍沿途景区?我推荐的路线如下西安十堰襄阳常德永州桂林湛江海口一为什么推荐这条线路呢?1这是一条旅游文化的线路读万卷书,行万里路这样的古语可以用在我本人身上,我是非常喜欢这条线路的。我出门旅游的青岛有哪些好吃的餐厅推荐?赵家牛肉砂锅据说这是全青岛最好吃的牛肉砂锅,是老字号听说老板曾经在团岛早市卖了十几年的甜沫,但是却做得一手超级好吃的牛杂砂锅。用原汤炖的牛杂超级入味,超级香。牛肉每一块都带着筋,特家庭宽带短期内还有可能上升到1000兆以上吗?目前我这里农宽带最低100兆,普遍300兆。公司正在大规模推广1000兆宽带。别人那些理论和想象我不知道怎么来的结果,我这个是正在发生和已经发生的事实!可以上海有5000兆的你不在是不是只要注册了滴滴,然后跑过几单滴滴,保险公司就不理赔了?具体还要看签的合同,我翻了一下我的保险合同的确有这么一段话该车出险时,如为营业性用途,我公司不承担一切赔偿责任。法律的条款写得很清楚,但是解释的权利归谁却很模糊。那么保险公司是如何我考上了山师的提前批,但我如果不报提前批我可以去东北电力大学,我有点后悔报提前批怎么办?我家闺女也是提前批录到山师定向济南,报之前我们充分了解过公费生的利与弊,毕竟是关乎孩子人生的重大决择我和孩她爸慎之又慎。因为孩子的分数挺尬尴不上不下,走211也只能录些工科类专业甚高一入学考试重要吗?认真准备下。高一入学考试,主要是为摸底,根据学生实际层次再微调。因为中考科目多,加之水分大,学生的实际水平并不见得很明朗。一些好学校,可能会自己就语数外物等主要学科进行再测,本校老焦虑症每天应该干什么比较好?患有焦虑症的人到底要怎样做?不看不知道。有一位网友在网络发出了自己患有焦虑症并且痊愈的经历,文字如下最初诊断为焦虑症,然后,服用一种叫做文拉法辛的药物,直到去年才停止服药。切记,药骨折后会有什么后遗症吗?回答这个问题之前,想给大家提个问题您认为我们的身体之所以能灵活活动最关键的结构是什么?相信看了上面的这个动图,大家应该就会明白答案了,那就是我们的关节。无论是我们的颈椎,还是上肢下
杨艳秋中国式现代化道路的世界意义来源经济日报走自己的路,是党的全部理论和实践立足点。在现代化道路上,中国共产党带领中国人民用几十年时间走完了发达国家几百年走过的发展历程,创造了世所罕见的经济快速发展和社会长期稳定美国南北战争期间海战和陆战的重要性,带来了怎样的影响和启示文字天文叙史编辑天文叙史引言南北战争是美国历史上一场重要的内战,它发生在1861年至1865年之间。战争的背景可以追溯到美国的殖民时期,随着英国的殖民扩张和黑奴贸易的发展,南方的经国产麒麟芯片上线?华为新机,马上来了!距离2023年华为春季新品发布会还有3天当大家的目光集中于华为两款最新旗舰机华为P60系列和折叠屏华为MateX3之时,黑马却注意到了此次发布会上的另一款新机华为畅享60系列。华为大V实测骁龙7Gen2,参考性能表现与新机段位,2000以下很香一直以来,高通旗下的骁龙8系列都是高端旗舰的御用芯片,而骁龙7系列则是普遍用于主流中端机型上,受众面相对要广得多,所以每一代的骁龙7系芯片表现如何都很受关注。近日,高通发布了最新一2022年全球高端智能手机市场华为销售额下滑44,份额降至3!3月21日消息,根据Counterpointresearch最新公布的报告显示,尽管宏观经济形式并不好,2022年全球智能手机整体销量同比下滑了12,但是2022年全球高端(600机构2月全球智能手机出货量同比下降11,三星重回第一市场研究机构TechInsights报告显示,2023年2月全球智能手机出货量(批发)和销量(零售)分别同比下降11和5。尽管数字有所下降,但由于中国重新开放和经济前景改善,智能手2000元以内,目前这4款手机最值得买,快看看你用上了哪部?头条创作挑战赛现在手机的性能都有了很大的提升,同时随着新款手机的发布,原来的一些老款手机价格都会有所下调,目前来看这四款手机最值得购买,运行流畅,基础体验都非常好,能够胜任日常的各不能超频的24线程处理器也很强!英特尔酷睿i713700实战继去年10月20日发布第一批可以超频的第13代酷睿K系列处理器后,为了满足更多普通用户对高性能处理器的需求,英特尔在今年也发布了大量型号不带K,不能进行处理器超频的第13代酷睿普通凑数镜头排第一!网友认为最烂最丑的手机设计排名,你担心的都有早前,外媒GSMArena发起了一项调查,让网友们投票选出最糟糕的手机设计,根据调查评选的结果来看,有4个最烂最丑的手机设计,快来看看有没有你认为丑的设计。凑数镜头不知道什么时候开惊喜!4000粒太空芙蓉种子正在萌芽去年,成都市植物园优选出来的四个种类4000粒芙蓉种子分批搭乘神舟十四号和神舟十五号飞船飞上太空,在完成太空育种之旅后,它们已于2022年底返回地球。2023年3月9日,成都市植物猎鹰九火箭四小时两连射,日民企月球着陆器完成轨道修正猎鹰9火箭4小时成功执行两次发射任务美国东部时间3月17日下午,一枚8手猎鹰9号火箭从范登堡太空军基地发射52颗星链卫星。当天晚间,一枚6手猎鹰9号火箭从卡纳维拉尔角太空军基地发射