无逻辑服务器:就是有这种操作
首先,定义一下“无逻辑服务器”,因为我读书少,不知道有没有什么比较常用的名词来形容这种操作。“无逻辑服务器”,就是服务器在起动后,没有任何逻辑,几乎不提供任何服务,然而当有业务需求的时候,会自动的被分配具体业务逻辑,然后执行这些逻辑,提供相应服务。
举个直观的例子,现在我在ip为:123.456.789.000(就是有这种ip)的主机的98765端口上起了一个Hava服务器。但是这个服务器只会处理Url为runCommand的请求,这个请求当然是要有权限认证的。当这个请求被调用之后,这个Hava服务器就会根据请求内容启动某些服务,比如开始处理getTime的这种请求。和toggle不同的是,getTime这个请求的处理一开始不在这个Hava服务器里,他的处理代码是由runCommand请求提供的。
然后由于我知识水平的确有限,读书不多,并不知道这叫什么,所以就叫他“无逻辑服务器”了,因为其实这个服务器的jar包或者war包或者exe文件开始运行的时候,其实自己没啥逻辑。
需求##
为什么搞这种骚操作?其实是因为穷……由于技术栈全部基于Java,都是吃内存的大户,然而各种云服务器的提供商感觉都是卖内存的,1G10刀,2G20刀,Java党表示起一个新应用和烧钱没啥区别。我们现在想起个“微服务”就专门发邮件,就是那种提供一个api来帮别的服务发邮件的服务。我们就会发现,起这个服务意味着一大堆内存被占用,就意味着钱。但是因为穷,除非是比较核心的应用,一些外围的服务根本不可能起一个独立的服务器的。
因此,我们需要的解决方案是:<b>要能让多个“微服务”运行于同一个服务器,但是又要让这些服务的开发、测试、部署流程完全独立。</b>所以一个骚操作就开始在脑海中浮现了。
解决方案##
当然,会有一种服务器,我称之为“Slave”,就是那种所谓的“无逻辑服务器”。Slave用来等待指令的接口接受Clojure脚本,至于这个脚本会做什么并不理会,只是单纯的跑这个脚本,而且只跑一次。当然,根据我其他应用的结构,这个脚本会去注册一些事件,类似于当收到了某请求时将做什么什么处理的事件。
但是一大堆Slave在那里,只是完成了让一堆服务跑在一个服务器里而已。但是,如果仅此而,开发、测试也许可以独立,但是部署显然是需要手动调接口去分配逻辑。而且,如果Slave重启、迁移之类的的话,还要重新调一次接口。所以我们会需要一个Master节点去自动的部署。
Master的主要就是帮忙管理这些跑在Slave上的Clojure脚本,然后去检查这些Slave有没有好好干活,是不是已经关机了,或者关服了。所以,所有的Slave需要到Master上注册,告诉Master有这么一个Slave存在。然后,所有的脚本当然是Master存下来,这些脚本被分配到了哪些Slave上当然也是Master的工作了,监控Slave是不是连的通,脚本有没有跑当然也是Master的锅,当然查询某个服务到底在哪个Slave跑着,也是Master的责任。
脚本的开发测试当然是独立的做,部署就交给Master去做就好了。因此完全满足我们的需求,一方面省服务器,二方面不同服务在整个流程上其实不会有什么交集。
技术实现与神坑##
由于我的所有应用的登陆和连接都必须要经过一个gateway服务器,就是所有流量会走这个gateway走,登陆也是在这个服务器上登陆,具体应用不会负责登陆,用户信息会由gateway提供给应用。虽然gateway本身是一个坑,但是在实现的时候会有另外一个坑。就是其实Slave不仅需要在Master注册,还得跑到gateway上注册,不然gateway才不会给你转发流量呢。而且,Master要给Slave推脚本,也要连接gateway,然后告诉gateway说要去某Slave,然后再告诉gateway说:“跟某Slave讲,这个脚本跑一下”。不过,gateway的目的就是不允许直接连接应用,同时做到调用应用的时候无感知,所以其实还行,就是注册两遍有点烦。(虽然Master其实也可以主动帮Slave注册gateway,但是懒不想弄)
然后就是slave并不会提供各种工具,也就是说,想引入一个第三方库,那么就得用class loader去动态加载,然后通过反射调用。所以在Slave里会有个根据url的包管理类,当需要一个第三方库的时候,把url给出来,调什么方法给出来,让后帮你去调一下。然后由于采用了docker,所以并不想把根据URL下载的包存下来,虽然不是不能做,但是意义不大。
由于要考虑到Master随时重启的可能,以及Slave随时重启的可能,以及他们中间gateway随时重启的可能,以及<b>Slave不存任何脚本</b>。就导致了,任意时刻,Master其实根本不知道Slave是个什么状态,除非Master和Slave保持连接,并且不停的确认Slave是不是跑了脚本。比如Slave突然连接不上了,此时Master并不能判断是gateway关了还是Slave关了,只能知道的是Slave连不上了。Slave恢复之后,Master并不能认定Slave没有跑脚本,因为可能是gateway重启,Slave其实一切正常。也不能认定Slave有跑脚本,因为显然有可能是Slave重启了。所以,Master会不断确认Slave的运行状态,而且Master不能知道Slave出现问题的原因,所以Master只能检查状态,发现状态不对就让Slave执行脚本。
同时由于Master几乎做了所有的事情,所以Master的内存占用感觉异常的大,当然也大不到哪去就是了。不过这个算是题外话了。
其他的可能##
其实,仔细想想,像不像我起了一堆主机,然后把脚本放到这些主机跑,只是主机变成了Java虚拟机,脚本变成了clojure脚本。所以其实可以考虑用ansible把clojure脚本发给各主机就好了?当然不是,别的就算了,除非把所有脚本打包到一起,不然内存就炸了,一个脚本一个Java虚拟机,还不如直接起应用来的方便。
再想,扔掉Master,slave的脚本直接从数据库里来。问题在于,slave的变动太大了,几乎是满世界跑,就跟游牧一样。其他服务如果想找slave,要么通过gateway找,要么通过数据库找,通过gateway找的话gateway根本不愿意负责这种工作,通过数据库的话完全可能各种应用同时访问一个字段,如果数据库本身有不良记录的话(比如某db,曾经居然只有全局锁。),根本不敢玩。
再或者Master做Slave化,也就是说,Master其实也是一个Slave,只是Master刚刚起服的时候就从火星要到了一份Master脚本,然后Master就从一个普通的Slave变成了Master。其实这个很靠谱,就是代价太大了,一来火星要么是运行参数,要么是配置,要么手动,反正都不是省油的灯。
然后……我读书少,想不到什么替代的可能了……
应用##
就目前来说,其实这玩意亏了。因为现在其实就只有一个邮件服务。所以某种意义上其实是有两个服务器在跑一个服务。不过可以预见的是,之后的外围系统基本都这么个节奏了。
但是,反正总算让一个应用调用这个邮件服务,可喜可贺。折腾了一周了,感觉累死了……可以好好歇一段时间了……