天天看点

FastDFS 端口映射问题,---spring改变jar包中bean方法逻辑的另一种特殊方式

前言概述

最近在做一个系统,是面向事业单位的,其中文件系统,用的是FastDFS,因为这个自己之前用过,所以就用了这个,没有考虑与本项目契合的问题(这也是菜的表现,技术面广度窄,用过哪个就用哪个,没有进行技术选型),然后引出了一系列问题……。

因为系统是面向事业单位的,所以这些数据、文件都要放在内网里面 ,然后通过一个公网的ip和端口映射出来。比如:我在内网里面,MySQL在192.168.1.154这台机器上,端口是3360,那我在内网直接连192.168.1.154:3360,就能连上这个MySQL,但是当出了这个内网就不行。所以把这个192.168.1.154:3360通过一个公网的ip和端口映射起来,如:172.138.182.199:5735,这个172.138.182.199:5735是一个暴露在公网中的端口,所以外部可以访问这个地址,然后这个地址又被映射到了它局域网内的192.168.1.154:3360,所以我直接连172.138.182.199:5735,就能连上内网那台机器的mysql。

FastDFS 端口映射问题,---spring改变jar包中bean方法逻辑的另一种特殊方式

大概就是这么个意思,因为事业单位的对数据这些看得比较严,不会放到公网上去,所以就这么弄。项目的mysql、redis都是这么弄的,但是在用FastDFS这样搞的时候却出了问题

问题来了

我们在最开发的时候,都是在内网里面弄,后面有同学说,有时候回家也想弄一下,因为在内网里面,只有在公司的WiFi下,才能连上mysql、redis、FastDFS这些,所以只有在办公室里才能开发。既然有同学又这样的想法,反正我们最后也是这个样子部署的,所以把这些都映射到一个公网机器暴露出去。在弄mysql、redis的时候一切正常,但是在把FastDFS映射出去的时候,却碰到了问题。

FastDFS是分布式文件系统,所以是用分布式思想设计的,其中有两种类型的角色,一种tracker,调度中心,一种是storage,存储节点。tracker就像古代青楼中的老鸨,storage就像老鸨手下的姑娘,顾客有需求时,就去找老鸨,对老鸨说:我要一个xxx的姑娘,然后老鸨就把她手下某个满足条件的姑娘推给顾客,剩下的时候就是顾客和姑娘直接交流了。tracker和storage之间的关系也是这样,当客户端需要上传或者下载文件时,是先和tracker进行通信的,tracker把它下面某个可用的storage的地址返给客户端,然后客户端再和这个strong通信,进行存储或者下载文件。当然,在这个过程中,tracker也起到了负载均衡的作用。

FastDFS 端口映射问题,---spring改变jar包中bean方法逻辑的另一种特殊方式

原理大概就是这样子,想起来,这样子的过程,和映射也不冲突,不就是一个tracker和一个storage吗?我把他们两个都映射到公网,不就可以通信了吗?想想这样是可以,但是实际上客户端只能主动和tracker通信,不能主动去和storage通信,什么意思呢?就是在项目的配置中,只能配置tracker的地址,不能配置storage的地址。其实想想也是,你去找青楼找姑娘,不能直接去和姑娘联系吧?因为你没有姑娘的联系方式,你也不知道这些姑娘的性格脾气是否符合你的要求,所以你只能去找老鸨,老鸨再把姑娘的联系方式给你。放到FastDFS中也是这样,客户端肯定不能直接就去和strong通信的,因为客户端不知道有哪些storage,这些strong是否可用,而且如果绕过了tracker,那tracker的负载均衡不就成摆设了吗?所以客户端只有去和tracker通信, tracker把某个可用的storage地址返给客户端,然后客户端再和这个storage通信,进行文件的上传下载。所以呢,我们把tracker的地址映射出来,可以配置到项目,然后去通信,但是将storage映射出来没啥用,因为项目没有配置storage地址的地方,不能主动去和storage通信,客户端storage的地址都是和tracker通信,tracker返给客户端的。

问题分析

把问题分析得这么清楚,其实想想可能还是有解决办法,storage的地址不是tracker返给客户端的吗,那我就去改tracker,把tracker下所有的storage地址都改成映射之后的地址,那tracker返回给客户端的地址,就是映射过后的地址,客户端一样可以连上。然后我就去看他tracker的配置文件,找有没有配置storage地址的配置项,结果是令人失望的,没有这个配置项。

我再翻了一下storage的配置文件,发现了原因,FastDFS的设计中,tracker是不主动去配置storage的地址的,只是strong配置了tracker的地址。就像微服务一样,注册中心没有去配置所有服务的地址,是每个服务配置了注册中心的地址,然后服务定时向注册中心发送自己的心跳,如果某个服务不发送或者什么了,注册中心就认为这个服务挂了,就不会向外推送这个服务的地址,而是推送其他注册中心认为可用的地址。这一条路也被堵死了,不能去tracker里面配置storage的地址。

那又继续分析,FastDFS服务器那边改不了,那我客户端呢?无论怎样,最终客户端肯定是和一个strong通信的,就算tracker返回来的strong地址,那我对返回来的这个地址做手脚不是也可以吗?

不分析不知道,一分析问题又来了,在项目里面客户端和storage通信,可是在源码里面的,通过spring容器注入FastDFS客户端,然后来进行文件上传下载的。那我想修改源码里面的逻辑,那可就麻烦了。要对jar包进行反编译,修改之后再打成jar包或者其它什么的,然后再用。我觉得这个方式太麻烦了,没有去测试是否可行。还有别的方式,通过类的加载顺序,在项目写一个相同路径的类,然后就会优先加载本项目中的类,我也没有试。因为我想到,FastDFS中的那些逻辑,都是注入在spring容器中,那我能不能修改spring容器中某个bean的某个方法逻辑呢?这样能看起来更具有可行性。

就这样,从一个问题转变成了另外一个问题,现在的问题是:怎么修改容器中bean的某个方法,或者直接替换掉容器中的bean,这时我第一时间想到的方法

解决问题

把上面的问题分析过后,开始解决新的问题,如何修改或替换 通过扫描jar包进spring容器中的bean,我翻了很多博客文章,有什么BeanPostProcessor、ClassPathBeanDefinitionScanner子类的,搞得很麻烦,也不能直接用到项目中,要慢慢的测试可行性。弄着弄着,我突然想到,我能不能把我需要改的那个bean的全部源码复制出来,然后再掉我需要改的那部分,再注入ioc容器中,我使用时就用注入我自己写的这个bean其实我最开始闪过这个念头的,但是马上就被我否决了,因为我想到了两个问题:

  1. 如果需要修改的bean,不是直接被我注入使用的,而是被beanA注入,然后我再注入beanA来使用,那么完全行不通。
  2. 如果再往容器中注入一个我写的写的bean,那么这儿bean只能被我自己使用,可能不能被源码中的其他bean使用,什么意思呢?我画个图说明一下
    FastDFS 端口映射问题,---spring改变jar包中bean方法逻辑的另一种特殊方式

简单的说明一下:

  • jarbean1、jarbean2、jarbean3是jar包中注入到ioc的bean,这种bean只要在配置文件中配置好需要配置就能直接注入使用,比如org.springframework.jdbc.core.JdbcTemplate只需要配置好数据源,就能直接在service中注入使用。mybean1和myservice1是我自己写的bean并且注入到容器中的
  • mybean1、mybean4是我们自己写的bean并且注入到容器中,比如,service实现类,
  • 单向的箭头表示某个bean使用另一个bean,上图中jarbean2指向了jarbean1,表示jarbean1使用jarbean2。双箭头表示等效替换。

先忽略问题1,我们先分析问题2

在上图中,我引入某个依赖时,它就自动向容器中注入了三个bean,jarbean1、jarbean2和jarbean3,并且存在一些关系,jarbean1使用了jarbean2,jarbean3使用jarbean1。原本我在myservice1中使用了jarbean1,但是现在我发现jarbean1中某个方法的逻辑我需要改一下,于是乎,我就新建了一个类mybean1,将jarbean1中所有的代码都复制到mybean1,包括继承、实现关系,然后我将mybea1中我想要修改的地方改成了我自己想要的逻辑,再将mybean1注入到spring容器中。jarbean1中用到了jarbean2,mybean1中同样也能使用jarbean2,因为mybean1已经被注入到容器中了,mybean1使用jjarbean2就像controller中使用service那样简单,直接注入就行了。然后我再在我的myservice1中,把原本注入的jarbean1替换成了mybean1,因为mybean1是复制的jarbean1,所以该有的方法一个不少,以前怎么用jarbean1的,现在就直接怎么用mybean1,mybean1中的某些方法还被按照我的需求改了,简直美滋滋。

但是这样可能会存在问题,可能会存在什么问题呢?jarbean3中是使用了jarbean1的,但是现在容器中存在的jarbean1和mybean1这两个bean,jarbean3很可能注入不进去,为什么呢?因为注入bean,一般都是通过接口来类型来的,就像我们在controller中注入service,声明的service变量都是接口类型。jarbean1和mybean1肯定也是实现了相同类型的接口的,如果在jarbean3中使用jarbean1时,是通过接口类型注入的,那么就出问题了,因为现在容器中有两个相同类型的bean,jarbean1和mybean1。如果是通过名称注入的,就不会出现这样的问题。如果jarbean3通过名字成功注入jarbean1了,那jarbean3中使用的jarbean1中的和myservice1中使用的mybean1有些地方的逻辑就不一样了,这样会不会造成其他问题,我也不知道了。而且,有的jar包中的bean,不允许同时出现两个类型的bean。所以,如果我们想按照上面那种方法来替换,最好的是,保证被替换的那个bean在ioc容器除了我们自己的bean之外没有别的jar包中的bean在使用。

那么问题1说的是什么呢?

就是假如我需要修改的逻辑在jarbean2中,而我的myservice1不是直接注入jarbean2使用,而是通过注入jarbean1使用,jarbean1再使用jarbean2。那这时,我再自己复制一个jarbean2,修改想要修改的逻辑之后,注入到容器中,怎么能使jarbean1中注入的jarbean2是我复制修改过后的的bean呢?如果不能保证,那么我复制修改之后注入容器中的bean将毫无意义,甚至可能引起bean之间的冲突。

我最开始否决这个想法就是因为第一时间想到了这两个问题,但是我没有仔细分析,后来回过头来仔细分析,我现在的情况,这个两个问题都是完全不存在的。FastDFS中用来上传上传下载文件的bean是com.github.tobato.fastdfs.service.DefaultFastFileStorageClient,这个bean,除了我自己之外,没有源码在使用,而且我需要修改的逻辑就在这一层bean里面。

需要修改的逻辑是:这个bean里面有个方法是获取可用的storage,这个storage是通过tracker获取的,我在这里做了点改动,把获取到的strong,到映射配置文件里面拿到对应的映射ip和端口,然后用映射ip和端口构造成一个新的storage,返回给调用者,OK万事大吉。上传文件修改也是类似的修改逻辑,把从tracker获取到的storage用对应的映射ip和端口重新构造一下。

目前项目在开发阶段,没有发现问题。

反思

  1. 这样做虽然能达到我的目标,但其实出现这个问题的根本原因,是技术栈太窄、对FastDFS的理解太浅,用过FastDFS就直接用FastDFS,没有想过FastDFS的分布式设计理念是否符合项目的要求。
  2. 对spring容器的理解还不够深入,这样子的操作我始终觉得不是优雅,看看有没有更优雅的替换方式。

继续阅读