背景:
最近新入职公司,负责技术。由于各种原因现在项目全权交由我们团队负责,之前的研发团队不再参与(及以后可能完全联系不上)。作为技术负责人,又刚入职公司压力巨大。经过两个多月的改造,算是接手得还算行。该项目存在的问题比较典型,特此记录。欢迎各位大佬批评指教。技术能力有限,文中所说解决方案(思路)只代表笔者的愚见。
后端: spring-boot+dubbo+redis+mybatis+oracle+jeager+apollo+flyway
前段: node+react
整个项目存在的问题较多,基于自己多年研发的直觉,对问题进行简单的优先级划分。
★★★★★ : 风险规避——必须立即处理
★★★★ : 架构优化——保证业务进度的前提下,优先处理
★★★ : 性能优化——保证业务进度的前提下,尽快处理
★★ : 重构再调整——新服务但需兼容原有规范
★ : 坚持原有规范
现状
多个服务的代码量远超许多单体服务;
调整原因
使用微服务架构,就使用微服务理念
解决之道
需求评审,合理定位,严格按照微服务的理念设计服务(掌握粒度,避免过度拆分)。
现状
1.技术框架二次封装:由于spring框架提供的便利性,上个团队应该是出于对技术规范的考虑,对整个项目所使用的技术框架进行了二次封装,技术框架做了封装;
2.依赖第三方团队代码:项目中存在对三方非开源代码的依赖;
调整原因
相信这一点不用多说,不受控制的东西千万不要使用。使用一段无法控制的代码,不知道啥时候会爆雷;当没有利益做桥梁,很难期望别人会给予恩惠。要不被人锁住咽喉,唯有将不稳定的因素解决。
当然对于这样全面的技术栈替换,测试的支持必不可少。这也是我将优先级排到此处的原因。由于项目刚接受,刚好产品、测试都需要对项目进行全面了解。这样避免到以后真的被技术锁喉时,还需要来进行一次全方位的测试(这工作不是一般的小)。
解决之道
就框架的二次封装而言,相对比较容易解决;把项目中使用到的技术整理出来,逐一使用开源技术替换即可;好在是对开源技术的二次封装,大家的使用技术比较统一,项目中并没有太多依赖二次封装框架的代码存在。少量的存在也通过反编译的方式生成了源码。
其中最麻烦的是上个研发团队自己封装的工具包,整个项目涉及十几个。由于一些缘由无法获得该部分源码,没有办法对于这样,也只有使用最原始的方式——反编译(这无疑是整个项目最头疼的一部分)。
现状
项目框架老旧,项目框架被二次封装,导致依赖的项目主体技术版本老旧,不能及时更新。
调整原因
1.项目安全是任何一种技术被采用必须被考虑的问题。一个三年前更新的技术版本,让我无法放心使用;以后技术版本越老旧,以后升级成本就越高。这也是需要先行处理开源技术被二次封装的原因。
2.过时的版本,也意味着使用不到新的技术特性。
解决之道
由于已经处理好了项目框架被二次封装的问题,对于开源技术的升级相对来说比较愉悦,各个技术官网、论坛都能找到帮助文档。
现状
1.单体服务事务:使用不规范,部分CUD操作甚至没有开启事务;
2.分布式事务:虽然项目框架中有引入seata事务解决分布式事务,但可能是代码先行框架后用,代码中存在大量复杂SQL,导致seata不能使用。
调整原因
没有事务带来的可怕性,不言而喻。
解决之道
由于目前对整个项目业务并不熟悉,因此笔者目前还未深入处理这块;只是先行升级开源框架,使用spring-boot 提供的优雅停服务功能来减少由于直接关闭服务带来的数据错乱问题;
笔者目前对这一块的想法本来是想通过横向切面统一给所有非GET接口都加上事务,但由于考虑到整个项目目前可以正常使用,且重新发版时间还不是特别紧因此未做处理,在此提出仅供大家参考。
另外对于分布式事务,由于项目中存在大量复杂SQL,原有seata框架没有办法继续使用(这也是为啥之前引入了此框架没有使用的原因)。笔者考虑到跨服务的事务毕竟是少数再结合目前项目进度紧张的实际情况,暂时未做处理。目前的想法是等服务先行全面测试之后,再通过调用链路服务梳理出所有的跨服务写数据业务,逐一排查处理。
现状
没有字典服务,字典服务、用户信息服务、权限服务为同一个服务(暂且叫做“主数据服务”)没有分离;
需要说明下:
字典服务:指维护字典数据的服务,及简单的key-value数据,例如:性别(1:男,2:女),民族(1:汉,2:满,3,蒙古...)。
用户信息服务:指维护用户信息的服务;
权限服务:指用户权限信息的维护,设计各个子服务操作权限,菜单权限的单独维护。
服务是否需要如此拆分,各个业务场景不同,需慎重考虑。只是笔者评估目前接手的项目适合如此拆分。
调整原因
主数据服务过重,性能将成为短期可预见的瓶颈。尤其是包含了字典服务的功能,而且该项目原有的架构还存在大量重复解释同一字段的问题,必然加重服务负担。
解决之道
笔者结合项目的实际情况,对服务的拆分目前没有彻底进行,而是循序渐进(在迭代中重构)。目前只是将相对独立的字典服务先抽取出来。以后需要使用字典数据的时候直接调用字典服务。要求所有研发人员在迭代开发的过程中逐步替换掉原有走主数据的方式,改为直接调用字段服务。权限服务和用户信息服务,由于涉及到的业务相对复杂,考虑到项目稳定性的问题暂时未做处理(待后期业务熟悉)。
现状
后端没有统一的网关服务,每个服务单独校验用户登录信息,前段node服务只做了接口转发并没有承担应有的大前端工作。
调整原因
网关服务混乱,每个后端服务都有对用户登录信息的校验,这无疑是没有意义的,反倒使代码不够整洁。
之后权限认证任何的调整都将牵涉到所有的服务。
而且如果现阶段不准确的定位各个服务职能,由于每个人的喜好(代码习惯)不同,必然后导致这个服务越来越混乱。
由于“不信任原则”,每个服务都会过重的对客户端进行校验。
解决之道
前端虽有node服务,但由于并没有实际运用起来,在结合目前的人员问题。因此决定后端引入网关服务,将各个服务的权限认证业务抽离,统一由网关服务承担。
说明:
笔者将该服务重新拆分,放弃大前端模式的主要原因是:
1.原有本应由原有node服务所聚合分离不同客户端接口的功能,并没有在node中,而是在后端接口中看到各种webController,AppController代码;
2.受限于前端目前人手及人员技术。
3.笔者自身对前端架构不够熟悉,前端负责人也赞同此设计。
现状
未分库:整个工程四百多张表全部创建在同一个用户下;
未分表:用户基本信息表,字段上百,存在大量非常用字段;
调整原因
由于项目是整体交接,之前的团队退出,新来接手的团队对之前的项目完全不了解,因此为了避免项目变得更乱,数据库分离迫在眉睫。
因为所有子服务的表全部放到同一个用户下面,如果不从技术层面实现数据的分离,由于join操作的便利性,必然会产生更多的跨服务关联查询。
解决之道
1.通过python脚本(正则匹配)找出各个子服务中在使用的表,按照CRUD操作进行归类;
2.对于已经存在的多个服务同时存在CUD同一个表的情况,仔细确定表的归宿问题;对于无法确认归宿问题的表先保存记录,待后续业务熟悉之后再次确认(非常感谢之前的开发者,整个项目梳理下来这样的表也只有十几个);
3.将所有表都按照服务归类之后,通过使用数据库提供的公共同义词,对跨服务使用的表分配对应的CRUDE权限(除非像上文2中提到无法确认归宿的表之后,禁止分配CUD权限)。
现状
使用触发器
使用自定义函数
使用存储过程
调整原因
单一职责,数据库只存储数据,业务逻辑交由代码。
解决之道
禁止使用数据库存储之外的其他功能。
现状
项目中没有使用统一的序列化方式
调整原因
1.不同的序列化方式使得对缓存问题的排查,数据不够直观;
2.相同的数据由于序列化方式的不同导致同时存在多份;
解决之道
1.技术选型确定合理的序列化方式;
2.新开module,完成对redis客户端的统一封装RedisService;
3.应用spring-boot提供的自动装配技术,统一装配RedisService bean;
4.使用RedisService替换原有服务中所有使用redis客户端的地方。
现状
没有充分利用数据累类型,只使用String类型
例如用户基本信息,直接序列化成jSON字符串存储,而不是使用hash,导致当需要使用用户信息中的某个字段时得全部取出用户信息。
调整原因
无端的性能损耗,无端增加IO
解决之道
通过python脚本,分析统计出所有的大数据Key,在代码中逐一排查审核。
现状
批量执行CUD等操作时,没有使用批量操作执行器,而是使用SQL拼接,更有甚者在for循环中执行CUD;(笔者不是说SQL拼接有问题,而是SQL拼接和使用批量执行器需要权衡,一般大批量的CUD操作更推荐使用批量执行器。笔者这里强调的主要是for循环逐一进行CUD操作的情况)
调整原因
循环中调用接口是编码的大忌,无需多言,尤其还设计跨服务调用,IO,时间都是几何级增长。
解决之道
使用调用链路记录,统计出所有有问题的调用。再分析出所有循环调用接口的链路交由开发人员逐一处理。
现状
在循环中跨服务调用
调整原因
无法接受的性能损耗
解决之道
使用调用链路记录,统计出所有有问题的调用。再分析出所有循环调用接口的链路交由开发人员逐一处理。
现状
旧接口请求的数据返回不够精细,往往存在一个大的DTO在多个地方使用,而每个接口却只使用少量字段。
调整原因
1.返回大对象会暴露一些不必要的字段,存在泄漏数据的风向;
2.增加IO消耗。
3.三个月之后,各个接口的业务逻辑会让你头疼。
解决之道
VO独立,接口返回字段按需返回,避免大的DTO,VO设计。
现状
1.对字典数据的解释没有统一放到前端处理,而是由后端各个服务解释翻译;
2.后端接口没有分离,存在同样的字段A服务解释了之后B服务接着重复解释,
具体表现为:
请求:web——>A——>B;
响应:B解释字段——>A解释字段——>web;
AB服务对统一字段存在重复解释。
调整原因
重复的工作,无端的性能消耗
解决之道
接口分离,同时提供解释字段和不解释字段的接口(处于对现有接口的兼容,因此提供解释字段的接口),这样消费者可以按需获取。
说明:
笔者推荐的架构风格为,所有的接口不对字典数据做解释处理。全部交由前段展示时翻译。
这样的好处有:
1.对于字典数据的处理,只需要在用户登录系统时从后台请求一次返回保存到本地即可,一次请求多次使用。
2.将解释字段的工作交由各个客户端处理,减少服务器压力。
现状
1.命名不规范;
2.关键字段,关键逻辑缺少注释说明。
3.该使用枚举没有使用枚举;
4.存在魔法值;
调整原因
1.好的命名不需要注释;
2.没有注释的代码,三个月之后谁还看得懂;
3.没有好的命名,没有明确的注释,又不是枚举,谁会知道1,2,3具体表啥;
4.没有明确定义的1,2,3参与逻辑计算,任谁也不知道是干啥的。
解决之道
1.老服务的命名问题不做处理,干掉强迫症;
2.在迭代开发中增加代码注释;
3.新开发研发按照编码规范实施,使用sonar检测代码质量,不合格代码一律不予以合并。
现状
没有任何文档
解决之道
1.给产品和测试提需求,要求逐步完善项目需求相关文档。
2.技术相关文档在迭代开发中同步完善。
以上调整,只是笔者对当前项目存在问题处理方式;由于技术能力有限,此中难免有不合理之处,诚邀各位大佬批评指教。
以下连接为笔者平时对技术的整理归档也欢迎各位大佬评论交流!
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD