初入公司,从CRUD到运维支持
一年之前,我还是一个只会CRUD的普通程序员,常年与业务打交道,一套花式SSM框架三板斧从头玩到底。
我入职了一个初创型的互联网项目团队,在迅速融入工作环境以后,我就开始上手写起了CRUD代码。虽然不知道底层原理, 但是SSM模版代码已经烂熟于心,再加上有一些在以前工作时学习到的基础和避坑的经验,比如空值验证,防止重复提交等, 让我能够比较快地完成业务代码。
领导看到了这一点,把我安排到了运维支持部,开始让我干一些运维的活(公司在初创期用人是有这个特点)。 那个时候,开始逐个上线一个一个的服务。这是我从零开始了解公司技术现状的开端,也是一个加班到死的开端。其实加班的原因我是非常理解的,这个和公司的技术现状是离不开的,且听我慢慢道来。
艰难的系统部署:了解技术全貌
在一个晚上,领导拿了一份文档,列了一系列要上线的服务和服务要部署在的服务器,然后我们就逐渐开干了。
新服务器都是极为纯净的,连一些基础命令都没有,只好慢慢学,慢慢装。yum install xxxx,run xxx , ps aux|grep,telnet ....,一天过去了,什么jdk,tomcat,全都装好了。
(ps. 做运维这段时间***的收获就是linux命令玩得很熟练。)
把服务部署后,启动时就遇上了难题:以前的服务是打war包放到tomcat的,但是现在的服务,需要java -jar 来启动。 尝试了可以后台启动的nohup java -jar , 直接翻车。 无奈去询问之前的开发,人家说得用screen 命令后台启动程序。我是一个刨根问底的人,后来发现,开发在为了保持进程不退出,错误的使用了监听控制台事件的方式,导致了nohup启动异常。
那个时候,正在搞核心业务开发的人因为一直忙于开发和调试,和我们的沟通少之又少;而负责部署的我们,又不了解业务,有些问题当时的开发也懒得详细解释,于是我们只能靠猜,来部署程序。
混乱的程度可见一斑, 但这不是几个程序员所能左右的。
我在那个时候学习到的***件事就是:在信息不足和沟通受限的时候,你要尝试学会必要的自行推理,根据已有信息的上下文来补全缺少的信息。
上线要紧,暂时就screen启动程序吧。但是程序启动以后,又遇到了更加尴尬的事情。
当时服务之间的调用方式,全部都是通过HttpClient直接调用目标服务。假如,我先启动A服务,A服务依赖B服务,B服务没启动,A服务初始化时会报错。那么,到底先启动谁后启动谁成了一个棘手的问题。
我查看了每一个服务的工程配置示例,发现每一个服务的config.properties,都有一个配置为root的选项,标识该服务的发布路径,例如,用户服务,他的config.properties 会配置 root=/userservice/ ,我就知道了,调用该服务肯定是这样的http路径:http://ip:port/userservice/xxxx。config.properties中,也会有一些带有如下特征的配置 reference_user=http://ip:port/userservice/xxxx。我很自然的明白了,这肯定是配置了该服务依赖的其他服务的调用地址。因此,我就想到,我可以根据每一个服务的配置文件理顺服务之间的调用关系。
为了确保我的猜想是正确的,我用网上的工具反编译了一个工程,发现,果然原来服务之间都是通过HttpClient调用的。
然后我就画了工程依赖图,仔细抽丝剥茧的梳理出来了程序的具体的依赖关系。***,我终于对服务的部署顺序和方式有了思路,而有些程序员,私下已经有人开始说干不下去了要离职。
怎么部署终于明白了,可是紧接着的一个尴尬的问题就是,程序都启动了,也调通了,但是领导要求负载均衡,集群化,一个服务里直接调用另外一个服务使用了HttpClient直连的方式,怎么搞负载均衡?怎么搞?怎么集群?
当时离DeadLine不远了,上线重要,于是忍痛购买了阿里云的SLB实现每一个服务的负载均衡,当时最痛苦的事情就是要理顺这种蜘蛛网式的服务调用关系,好在我之前已经画了工程依赖图,这个也就好办了。
天天加班折腾到凌晨两点,一个半月以后,终于把全部的Service部署成功,难以想象的是,我一个完全不懂业务的人,完全根据日志信息和配置关系,搭建成功的服务,居然有80%的功能正常可用,这给了我这个苦哈哈加班的程序员不小的成就感,虽然搞不定的20%的功能后来由负责这块的开发亲自去搞了(领导出面,不能敷衍了)。虽然,后面还有一些小的插曲,虽然这个应用刚开始可能全是窟窿,但好在也终于如期上线。
这一个半月,我掌握了公司的技术架构,在一些程序员以不会为理由拒绝部署kafka,nginx,zookeeper,activemq等基础设施的时候,我出手去部署,其实我也不会,但是从学怎么用,到部署完,时间也是够的,我还学习了这些技术是干什么的,不亏。
最终,我整理了公司当时的技术全貌,一些网络转发和机房架构由于保密原因不予描述(虽然我也已经掌握了),我只单纯说一下代码层面的:
一,服务与服务之间,通过RESTful风格的HTTP调用,使用了HttpClient,需要自己维护蜘蛛网一样的服务调用关系。
二,配置文件有在本地的,有在数据库的,代码里每次获取配置每次都要load本地,select数据库。(影响性能)。
三,无用的配置文件和无用代码散落在各处,给运维造成了干扰,代码有历史遗留的味道。
四,技术栈不规范,有的人外置tomcat,有的人内置tomcat,有的人用了Netty,简直就是群魔乱舞。
五,命名不规范不统一,词不达意,公司用了elastic-job作为分布式任务调度平台,elastic-job要求每一个job启动时需要指定job-name,但是,运维经常在控制台上无法根据job-name来定位到底是哪个job,比如,工程job为payServiceAllJob,在控制台上却注册为AllJob,让人摸不着头脑。
痛点一:缺乏服务的自动发现
服务上线以后,由于本身项目就是在没有规范,没有制度的情况下放肆生长出来的,因此服务上线以后还是会疯狂的加班,来弥补一个又一个补不完的窟窿。那个时候我还没有接触业务,但是也要和开发一起加班,人工值班监控。
已经一个月没有写增删改查了,我就有机会开始搞一些事情了。那个时候,我请我的领导去说动技术部使用Dubbo,因为它能做服务自动发现,免配置扩容服务,是一个挺流行的RPC框架。但是由于当时我们没有技术权限,这个事情是很难推动的,一切以稳定为主。
我使用过Dubbo,但是我一直不理解的是,为什么Dubbo使用了ZooKeeper就能自动扩容?就能服务自动发现?这个肯定和ZooKeeper有关系。由于我是刨根问底型的开发,因此我开始在周末时间和晚上和上下班路上自己学习ZooKeeper,什么顺序节点,***节点,临时节点,什么树形存储,动态监听和通知。
终于,我就在没有看Dubbo源码的情况下突然恍然大悟:Dubbo的服务提供者和服务消费者都配置了服务name,如果我在ZooKeeper的一个name节点下存储服务路径集合,那么每次新增服务或者下线服务都会通知到任何监听这个name的客户端?(后来知道这叫做namespace命名服务),Dubbo肯定是使用了Zookeeper的命名服务来实现服务的动态发现的!
大部分程序员使用HttpClient都是使用的一个叫HttpHelper的工具类,我的改造就开始从HttpHelper这个工具类开始吧,让程序员传递自己的服务的ip,port,se rviceName,destinctName,zookeeperUrl,然后我在工具类里封装了获取调用路径列表,并封装了两个负载均衡算法:ip_hash,随机数。(其实我只会写这两种)。
在公司的四次迭代里,他们在一边飞速业务开发,一边逐渐把自己的HttpHelper工具类替换给我写的这个,终于可以卸下slb,实现简单的HTTP服务动态发现了。
当时的反对声音其实还是挺大的,不过运维的需求呼声更高,配置URL太麻烦了!
那个时候,公司架构重组,得领导赏识,我被重新划分到了技术部,做了基础平台研发部门的Team Leader,管理几个程序员。
痛点二:缺乏配置中心
解决了***个痛点以后,我一边写增删改查,一边又想解决第二个痛点:配置问题。
之前每次部署程序到线上时,每次都要一个服务器一个服务器的改配置,我就想,我把我的时间浪费在这种一个服务器一个服务器改配置的事情上,真的是不甘心。
而且这么改配置,还容易出错。虽然公司没有主动要求写什么。(公司的主要眼睛还是放在了实现功能和业务上)那个时候加班没有那么狠了,程序员写完增删改查就回家了,我还在想配置中心怎么实现。那个时候微服务的概念火热,我了解到了Spring-cloud-config。知道有叫做配置中心的这种东东,对,我们也需要配置中心,把配置集中抽出来管理。由于我已经掌握了ZooKeeper,自然知道了做配置中心的思路。
半个月时间我就写完了,时间主要浪费在了写页面上。(汗,作为一个后台程序员我承认我的页面能力比较差)当然,这期间也经过了一些改版。我竟然不知道ZooKeeper有Curator这么好用的客户端,之前一直使用的原生的org.apache.zookeeper这个原生客户端操作,监听消费以后还要重新监听。
配置中心写完以后,我也在一个周末发到了群里,并推广了出去。这次,由于公司的人员团结力已经大有改观,而且我也已经是组内Team Leader,先在组内普及了。
而且我这次组外的游说和推广也没有那么困难了,最终也都一个服务一个服务的落地了配置中心,并实现了一处改动,处处生效,也实现了传说中的配置热更新。这些,并没有什么高深的技术,仅仅就是依赖了一个ZooKeeper。
弄完这些,我已经感受到了做技术的乐趣,同时,我的领导也觉得好像让我写增删改查有点浪费,要求我把我手头的任务全部分给组员,自己则是去解决别人的问题......我赢得了更多的时间去自我驱动,改善公司的基础设置。
痛点三:缺乏缓存框架
接下来我本以为自己要没事做了,但是,得益于左耳听风的一个专栏,他说,描述一个业务(DSL),要比编写一个业务更具有维护性,公司很多的人都在使用Redis缓存,但是,用的库各不一样,还经常出各种奇怪的问题,调试起来及其繁琐。
当然,最终让我受不了开始决定写缓存框架的是因为我看到了我的组员写的代码与业务严重耦合,一会操作Redis,一会操作数据库。我发现了这个痛点,研究了一下Spring的AOP和他的@Transtional 注解如何实现以后,终于决定手写一个微型的缓存框架。基础思路就是通过可插拔的@CacheEnable(key=xxxx,timeout=xxx......)实现Redis缓存,完全不侵入业务代码。如果不用了,把注解在方法上移除了就好了。
手写这个Redis缓存框架使用了半个月,毕竟自己的技术能力还是有限,如何实现AOP和Spring集成,怎么抽象等等,都会让我每天都思考半天才下笔,甚至有时候一天写不了一行代码。
期间看了一本叫做《面向对象编程》的书籍,里面说,面向接口编程,依赖抽象,而不是依赖具体的实现......我突然就像打通了任督二脉。我在CacheHandler里面依赖了CacheStorage接口,而把RedisCacheStorage作为一个实现类注入了进去,因此,后来我这个框架可以同时支持redis,memcache,local。
写完这个框架以后,我总结了三个可抽象的点:
1. 序列化方式(jdk,protobuf,thift,json)
2. 脚本解析器方式(就是解析key的方式:比如,可以使用spel或者ognl:"userId"+#user.id )
3. 缓存实现方式(redis,memcache)。
后来我从刘大的码农翻身公众号的一篇将日志系统的设计里知道了正交一词,大概这就是正交吧。最尴尬的是,原来Spring早就实现了,叫做SpringCache。汗。
(码农翻身注:那篇文章叫做《一个著名的日志系统是怎么设计的》)
后来的事情,我也不详细的讲了,大概就是从私有框架,换成了公共成熟的库,我们也终于用上了Dubbo框架。
而对我自己本身,我只是更加理解了一些之前无法理解的事情:Dubbo为什么能改一个组件的配置那个组件就换了一种实现。(面向接口编程)看Mybatis的源码也没那么困难了,两年前完全下不去手。当然,我也变得更加热爱技术了。
我的心得体会
那就给大家分享一下我近两年来的心得体会。
1. 作为一个程序员,一定要懂得自我挖掘,而不是仅仅实现业务功能就好了,也不能干等着领导分配任务。
功能耦合了,写代码慢了,运维麻烦了,这些,都是潜在的需求,我们现在的加班,是为了以后不加班,是为了提高自己的效率,是为了不能原地踏步。
2. 一定要真正地学会一个技术,我在实际工作中,发现有的同学使用了一年的Git,竟然不知道如何把GitHub的项目拉取到本地,但是他会把GitLib的项目拉取到本地。
程序启动异常,有同学从网上找了一个jar包导入到了工程里,但是问他为什么把jar包导入到工程里代码就启动正常了,竟然不知道。
有的同学使用了一年的Maven,不知道mvn compile 是什么含义,只知道mvn package是打包。
这些,就是没有真正的学会了一个技术,仅仅是工作时机械式的使用了,仅仅是复制粘贴了,下一个项目再复制粘贴过来就行了,模仿着别人的代码写逻辑,这是学不到东西的。
我觉得,你一定会有时间去学习这些东西,网上大把大包的资料和教程。一定要知其然而知其所以然,一定不要一模一样的配置换了一种配置方式你就看不懂了。
3. 要掌握公司的技术栈,要刨根问底,公司用的什么rpc框架?怎么使用?原理是什么?公司用的nginx,nginx怎么配置的。公司用了配置中心,配置中心是什么?公司的负载均衡框架用什么做的,存在什么问题?
我写了一个缓存框架,你有没有冲动研究一下到底是怎么实现的?保持技术好奇心是十分重要的。我认识的一个同学,来我们公司以后,简历上写的,会SSM。后来经历了我的配置中心演进之路,还学会用了。后来他离职了,简历上写的还是会SSM,没别的了。本来,如果他把配置中心讲一讲会是一个很大的亮点。工作了一年,最怕的就是挥一挥衣袖,不带走一片云彩。
4. 要善于发掘,总有一些东西是亮点,是金子,只是你发现不了。
我在我们公司学到的一个安全方面的设计就是token机制,这改变了我的观念,以前一直以为做单点登陆只能session共享,现在才知道,还能使用时间换空间的方式,使用token签名代替sessionId,使登录这个过程变得成为无状态的计算......这是我发现的,很多人程序员根本不能意识到这些技术亮点,并融化到自己脑海里成为自己的东西。
5. 使用碎片化时间去学习,不要总抱怨没有时间。
学习是枯燥的,但是,当你收到了学习带来的巨大红利,你会越来越想学习。我亲身的体验就是,当我写的配置中心和缓存框架投放到了生产中,并得到了我想要的效果后,我现在更加喜欢学习,更加痴迷于技术了,我的技术能力并不强,很多的东西,都是因为我的兴趣逐渐被激发,因为我的热爱,才发生了质变。
6. 多思考,正确地实现业务。
有没有观察过,同样的业务,有的人写出来的实现就非常的丰满和稳定,而有的人写出来的接口可能直接调用一下就抛出了错误。很多程序员写代码只关心正常分支的逻辑,从来不考虑异常逻辑的处理。在写业务代码的时候多去分析用户场景,也会规避很多问题。
举个例子,接口的参数的验证,你可以根据使用场景来编写额外的适配和容错。比如,接口中有一个字段A,这个字段大小写敏感吗?这个字段万一用户多输入了一个空格怎么办?如果你能根据业务分析到,字段大小写不敏感,我要全部转成小写来比较,空格不是正常参数的一部分,我要进行去空格来简单的容错。那么,你可能会避免有一天某个业务人员跑过来跟你说,我明明写的正确为什么你告诉我参数不存在。
不要小看业务代码,不必掌握多么高深的技术原理,只要能把业务逻辑实现的健壮,也已经很考验能力了,说明了你是一个喜欢思考和注重细节的人。
结束语
现在的我,越来越喜欢研究源码,研究底层,并伴随着的是,我以前的一些疑惑都迎刃而解,这当然是因为我得到了正向的反馈,付出会有收获。现在被公司内部的部分程序员误认为是大神,当然,我还差得很远。
现在公司技术,代码逐渐在变好,工程规范逐渐在行程和标准化,统一化。这是值得欣慰的,我最近在研究API网关,公司的业务并发量在上升,我觉得需要一个入口统一管理API,负责鉴权,认证,限流,熔断等一系列的功能。(这些词都是在微服务课堂上学习的)。我已经开发了一部分了。使用Netty接收请求,使用HttpClientPool转发请求,中间使用责任链模式做Handler中转拦截处理,不知道何时能竣工。