Paul Paul Public Builder & 全栈工程师
search

全部文章

共 19 篇,按发布时间倒序。

2022-05-28 · 约 1 分钟

我在阿里创业的经历

前言 去年我加入阿里的一个创业项目。最开始,大家都信心满满,当时大家挂在嘴边的口号是“我要把这个业务做上市”。在经历第一次大促、疫情好转、换帅、集团战略调整等事件之后,最近这个项目发生大的人员调整,项目上所有集团内部技术岗位人员(P6~P8)都被裁了一个不剩。我以一个开发的视角来复盘整个过程,顺便也讲讲我的看法。 创业背景 2019年年关发生的疫情导致国外供应链体系遭受重创,中国在体制优势下疫情防控工作做的很好,最先恢复过来。彼时国外疫情的扩散势态仍然没有得到有效的抑制,大量工厂无法复工复产。同时,为了防止疫情扩散,很多出口国也关闭了商品的出口通道,进一步加剧了短缺现象。 (比如:2020年初的国内猪价大涨,正是因为猪肉无法进口而短期内又受到猪的生长周期的限制,导致猪肉大涨) 在疫情肆虐的大环境下,大量消费场景由线下转到线上。正是在这个大背景下,中国多年的的电商经验以及最先复工复产的优势非常明显,大量创业者开始入局跨境电商,当时最流行的一句话就是“在风口上,猪都能飞起来”。 前期的基建 我们选择的赛道是轻时尚,针对国外Z世代的用户提高供性价比的女装。轻时尚本身就是一个比较新的领域,加上我们的供应链体系既不是纯粹的自营也不是纯粹的商家仓发, 可以说是介于两者之间的一种新的形式。供应链体系的创新加上对轻时尚领域的陌生导致根本无法借鉴集团现有的经验和已有的产品能力,无法快速的定位市场的需求。 所以,在这个阶段,我们借助集团中台,搭建了自己的供应链体系,开始摸索轻时尚领域。整个基建过程中大致经历3到4个月,到这个阶段我们的整个体系大致搭建起来了,整个系统也在运转,业务也在不停地探索创新。 准备大促 大概是在7月,整个团队的业务和技术还有产品一起开了一个KO大会。会议的大概主题是确立今年的考核指标以及第一次大促的时间,想通过大促来检验我们的阶段性成果,方便后续调整。 当然,我们定的大促时间是在黑五(黑色星期五)可以说有很长的准备时间。我们还专门组织了outing(团建,一人1000经费),提升团队凝聚力。 去中台化 在准备大促的过程中发生了好几起大的生产事故,都是由于中台自身设计问题,坐在家里也有故障发生(轻则数据错乱,重则服务不可用)。 抛开线上事故来说,当时使用中台开发也有很多痛点,大致体现如下几点: 开发效率低下。 我当时刚上手,花了2天才懂了如何给响应数据中添加一个字段,扩展点太多而且没有文档介绍,找中台的人问问题,回复也很慢。 系统边界不清晰。 中台分为平台和站点,按照我的思路应该是将平台作为一个SDK集成到站点,站点作为一个应用对外提供服务。但是实际上却是将平台作为一个应用,将站点作为一个jar直接导入到平台中。 单基线+对全体开发开放平台代码提交权限。 这意味着所有人都可以对平台中共用的功能点做更改,一旦某个业务线提交的代码不兼容其它业务线,就会导致其他业务线在发布之后出现生产事故。同时,因为要快速迭代需求,所以业务开发没兴趣去抽取其他业务线也会用到的能力,把它放到平台层。反而是把正对自己业务特性的代码直接放到平台层,导致平台和站点的界限越来越模糊,代码更是越来越像米田共 球”。 中间件未隔离。 这是因为很多共用功能是读取同一个分布式配置,如果某一个业务线变更了这个配置,那其他业务线也跟着受影响。 问题排查困难。 几乎所有功能都是通过扩展点进行集成,一旦出现错误,根本没法确定哪里报错,因为错误日志里没有打印错误堆栈!!!所以只有两种方式排查,一是根据请求入参、出参,自己去看代码。二是远程debug,但是debug不能卡太久(大致是2分钟),否则健康检测机制会认为机器出现故障,导致机器被重启,debug会断掉。 正式基于这样的一个背景下,我们CTO决定去中台化。当然,这也是有个漫长的过程,大概是从十月开始做这个事情,但是直到写博客的今天为止,也并不是所有功能都从中台抽离出来。 因为,我们还得迭代业务,所以资源投入业有限。不过,我所在的商品团队已经将所有功能迁移。但迁移过程也是很心酸,预计一个半月上线,然后花一周做灰度发布。实际情况却是,开发两个月,测试一个月。 因为这期间前端投入不足,导致一直在等待前端资源。同时,上线完后,我们灰度也用了两周,才完全切换过来。也正是有这个经历之后我才明白,在运转的系统中换系统架构,无异于在一架飞行中的飞机上换引擎。 大促成果 老实说,整个黑五期间,我并没有太多感受。一是本身业务量不太大,没有需要应急处理的特殊情况。 二是,在黑五期间都会封网(非必要不发布,发布要走审核)。而B端系统,在单量很少的情况下,基本很少出故障。 在黑五复盘大会上,CEO也是跟我们晒了一些成果。大致讲的是“取得了阶段性成果,但尚未成功,同志们仍需努力”。不过,对当时的我来讲,因为我没有过创业经历,我也不知道这到底是好还是不好?好在哪里?有多好?换句话说就是“心里没数”。 但是,通过这种战果通晒的方式确实能鼓舞(迷惑)人心。后面一段时间里,技术人员都认为业务前景一片大好(至少我和我身边的同事都这样认为),有机会直接走向人生巅峰。 团队换帅 在大促之后不久,我们CTO转岗回到菜鸟,换了一个“负责人”。因为之前CTO是P9,新来的老大是P8,所以大家只用“负责人”来称呼他。 其实,这个时候大多数技术、业务都失去了信心。因为团队突然换帅,会打击大家的信心。 同时,也会传递一些消极信息,大家可能会担心这个业务是否还能继续做下去。但是,我当时没多想,也很少和业务交流,所谓“两耳不闻窗外事,一心一意写代码”。 当然,这么做的不止我一个。我清楚的记得,12月的时候还和至易师兄一起吃晚饭。当时,我们聊到这个业务的前景。 我说:“留给这个业务的时间不多了。国外疫情恢复之后,风口的风会停下来,这个时候如果我们的品牌力不够,那就会被市场淘汰。不过,即使最后做不好,可能会以一个子部门的形式存在,毕竟集团没有轻时尚的其他业务部门,而且目前现有平台模式不太适合轻时尚。” 师兄却说:“我们不会失败,因为我们是阿里,我们能创造不可能”。 外部环境变化 在过去的一年里发生了很多大事儿。反垄断力度加大、阿里各个阵地失守、经济周期性调整、海外疫情恢复等等这些问题加上业务单量冲不上去,这让我们部门雪上加霜。2022年阿里开启裁员模式(其实不止阿里,其他地方也在裁),首先就是在4月,裁掉了之前还信心满满的至易师兄,还有一个P8和一个P6。 就在我们以为裁员风波过了的时候, 4月的最后一个星期,项目中所有集团内部技术人员都被拉去做“预期管理”。裁员的名额不是固定的,是一个区间值。所谓“预期管理”,就是通知你,你被加入到了裁员名单。 很不幸,最终这个项目中的所有集团内部技术,无一幸免。令我感到惋惜的是,一起奋斗的闻安师兄和袖石小姐姐最终也逃不过被裁掉的命运。 写在最后 致一起奋斗过的同事 我觉得很后悔,因为我从未想到过大家会以这种方式离别。我以为大家还有大把在一起的时间,所以我留了好多问题还没来得及问。 我想知道关于师兄们晋升的事儿、我想知道集团曾经里面发生的趣事儿、我想知道如何平衡工作和生活。现在看来,这些问题都只能留给我自己了。 还有,最重要的是苟富贵,勿相忘!!! 致逃过一劫的人 在大风波里逃过一劫的人,都是幸运的。但是,很多时候其实我们应该更“敏感”。比如:业务团队在去年10月份开始就产生一些对业务不看好的想法,我们却是直到别人被裁了才知道业务营收不好。 这里其实也是有很大一部分是做技术人的通病,这里以我自身为例,列举下。(个人观点,不喜可喷) 业务不敏感。 只关注程序实现,很少关注业务。比如:让你开发一个功能,你会不会有这些疑惑。这个功能有多少人用?这个功能带来的实际价值是多少?怎么衡量功能的价值? 不喜欢社交。 很少和不经常打交道的人搭话,即使只是简单的打个招呼。最明显的例子是,我通过outing认识了业务侧的TL,但也是就见过那一次,后面有几次遇到了,都假装不认识,扭头就跑。 表达能力差。 很多东西和业务沟通不了,需要通过产品。只针对功能来表达观点,很多表达方式没有和业务侧对齐,导致很难和业务进行交流。因此,你就白白丢掉了能最直观的了解到业务运转状况的时机。 看低软技能。 只培养写代码能力,认为软能力不重要。这里的软技能指跟人打交道的能力。很多人认为和程序员打交道最多的是程序,但是回头看看一周的时间表,会发现和你打交道最多的是人,大部分时间都在和人在打交道。 不敢和领导交流。 天然的惧怕领导。我工作到现在为止,从来没有和领导一对一谈过话。后面我才明白,因为领导也是人,你约他去喝个咖啡,谈谈最近的工作,就当闲聊,领导也很愿意的。对你而言,好处就是,你总能得到一些有用的信息,对领导而言,他能更好的了解你。

阅读全文 arrow_forward
2022-05-24 · 约 1 分钟

上市公司的创业形式

上市公司为什么需要创新 当一家公司主营业务增长速度放缓之后,为了保持营收的增长,有效的办法便是寻找其他能带来增长的业务。 (当然如果是商业模式本身出现问题也可能会进行私有化以便于调整,可以参考戴尔的私有化案例) “创新”是一种寻找能带来增长的业务的高效方式,大体上是围绕技术、产品组织形式、生产方式等方面展开。 上市公司创新的掣肘点 对上市公司来说,创新的掣肘点主要体现在资金的投入在短期内没法得到回报。创新是一件长周期、要烧很多钱、有很大概率失败的一件事。 而上市公司对短期的投资回报是比较敏感的,因为投资回报影响着季度财务报表,股民们会时刻关注每个季度的报表,营收出现下跌会打击股民的信心,容易造成公司股价下跌。 所以上市公司会受到股市的约束,使公司更倾向于追求短期有回报的投资。 创新的形式 收购 上市公司自己创新会面临大量风险。所以,在考虑自己做这件事儿之前,会先看看市场上是否有同类型且已经有成绩的创业公司。通过收购,可以将创业公司的创新成果集成到 自己的业务体系中,从而完成业务体系的升级 风投 当然,并不是所有的创新都有人提前在摸索并且已经做出成绩了。对于一些新兴领域来说,整个行业的人都在不停的探索。如果有些小公司前景很好,但是没有资金继续下去,上市公司可以 用风险投资的方式,以最小的投资换来最大的股权占比,这同样也是一件很好的降低风险的事情。 内部赛马 这通常是公司内部人员发起。即有人想创新,他会召集一些有相同兴趣,且在公司内部有影响力的人来一起探讨这件事。确立要做之后会形成一个发起团队,然后开始进行团队扩张、研发、市场检验、反馈、调整的几轮循环 由于可能会有不同团队在做类似的事情,这些团队的投资人都是公司,公司的投入资金有限。因此,就跟赛马一样,拼的是谁能更快的抢占市场,只有最快的人才能获得新一轮的投资。 小步快跑 由于是集团内部创业,所以初期的资源非常有限。(集团的创业启动资金也是有预算的)可能就20多个人,集团内部创业的优势是 可以复用现有的技术体系、借鉴其他业务线相关领域的经验,但是这同样也是掣肘点。举个例子,目前集团内部的技术战略点是大中台,小前台。 但是对于创新业务来说,现有的中台不一定能完全满足需要。我所在的部门最开始也是采用的中台战略,确实在一开始能快速的跑起来。随着业务的深耕, 中台迭代慢、上手难度高、排查问题困难的问题逐渐暴露出来。因此,CTO果断弃用中台,转而使用自建的应用。同时,由于是创新业务,内部经验不一定适用, 我清楚记得CEO跑遍集团各个业务部门都没找到一个能借鉴 企划 经验的部门 天使轮 前面小步快跑的目的就是在最低成本下看到跑通这个商业模式。而当商业模式跑通之后,部门总裁会评估这个模式的前景然后决定天使轮的投入。 天使轮所做的就是在模式跑通的情况下去探探这个市场的底,确定这个市场有多大,这个模式最终能跑成什么样子。

阅读全文 arrow_forward
2022-05-10 · 约 1 分钟

《阿里巴巴java规范》 Result 方式杂谈

前言 上个月开始和同事一起做一个为期一个月的项目。期间同事看了我的代码,给我投来了一个奇异的眼神,问道“你的代码没有错误码吗?我调你的方法,我怎么知道成功与否呢?”,今天我就这个话题来讨论一下为什么系统内部调用时不需要封装请求结果,出错直接抛出异常就好。 核心争论点 系统的内部调用是否需要封装错误码以及请求返回结果。换句话说,我同事认为一个成功的系统调用需要像下面这样返回数据对象(这里只是以json作为示例,并不代表我们调用的结果是json)。 json content_copy 复制代码 { "code": 200, "success": true, "msg": "", "data": {} } 而一个失败的系统调用是否又需要像下面这样返回。 json content_copy 复制代码 { "code": 500, "success": false, "msg": "error message", "data": {} } 使用结果类 这里我觉得可能她是受《阿里巴巴开发手册》里面的一条关于 RPC 调用规范的影响 跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess()方法、“错误码”、“错误 简短信息”;而应用内部推荐异常抛出。 说明:关于 RPC 方法返回方式使用 Result 方式的理由: 1)使用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。 2)如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用端解决问题 的帮助不会太多。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。 系统内部调用要使用异常 文中也提到,系统内部调用还是应该通过抛出 异常 的方式,而不是用 Result 方式来返回错误结果。 (这里的 Result 方式就是上文我们提到的,使用结果类来封装请求结果)。 在系统内部使用 异常,相对于使用 Result 方式来说有个非常明显的优点–在使用异常时,不需要对调用成功与否进行判断。 如果调用失败直接抛出异常,调用者处理这个异常就好。 看到这里大家可能有疑惑,这么简单的问题为什么还需要和同事讨论? 我认为有这个讨论的根源问题在于每个人开发习惯的不同。方法调用时同事更喜欢使用 Result 方式来处理,而我更喜欢使用 异常 的方式。 其实,我这个时候做个让步也没什么,毕竟只有提供给她的部分函数需要按照这个方式,改动量不大。但是最近我在读 Sonmez 的《Soft Skills》,我对里面提到的一个观点很有感触,或许是受这个观点潜移默化的影响,我觉得有必要坚持我的观点。

阅读全文 arrow_forward
2022-03-26 · 约 1 分钟

顺序消息的解决思路

场景 在日常开发中各个系统之间的交互,除了系统调用之外消息也是一个比较常见的机制。比如:商品发布、商品上架、商品下架等,这种场景下为了与其他系统解耦所以选择使用消息中间件来进行消息的发布与订阅机制。除了这种场景之外,我们经常遇到另一个场景,比如有A和B两个系统,A系统负责商品的生命周期管理,比如发布、审核、上架、下架。B系统负责对商品的价格进行计算与价格审核相关的功能。我们知道商品在上架前肯定得有价格,而价格肯定是需要商家先提报然后需要审核之后B系统再对商家提报的价格进行一定的溢价计算才能生成最终在平台上的销售价格。那么商品提报之后不能说要等商家把价格信息完善好之后再继续往下进行商品运营工作,所以商品提报之后流程继续走,等到商家提报价格以及B系统生成价格之后,通过消息监听的机制来感知价格的变化情况。 分析 在前面我们提到,需要依赖商家提报价格以及B系统生成价格,而这些事件都是通过消息的机制来感知的。A系统的商品的价格状态是有两个,一个是商家提报的供货价,一个是商品的销售价。可以想到这里A系统的设计就天然的依赖了消息的顺序,A系统的价格状态的推进是现有供货价,然后再有销售价,这符合业务的逻辑。但是,仔细想想,这里就有一些分布式的问题。首先,价格的计算时间肯定很短,这两条消息若是同时发出,那这两条消息不一定能被同一个消费者消费。那么肯定会有“供货价没有提报,但是销售价已经生成”的情况(因为供货价提报的消息被另一台机器消费了,而且这个时候还没有写入数据库)。其二,即使两条消息能被同一个机器消费到,但我们其实知道MQ的消息可能会乱序(因为网络问题导致消费者没消费到,MQ会触发重发机制,但是这个时候消息就已经乱序了) 解决 数据冗余加补偿 将价格相关的数据冗余在自己系统里面,当B系统发出消息的时候我们将消息存数据库,然后我们再通过定时任务去补偿数据 消息重发 在系统判定还没有收到供货价的时候将消息转为延迟消息再次发送。不太推荐这样做,这样会影响到原来topic里面的数据,后续排查非常困难

阅读全文 arrow_forward
2022-03-20 · 约 2 分钟

Enable模块驱动

前言 很多同学在使用Springboot时都会看到一个注解“@EnableXXX”,该注解用于显示的装配指定的模块,如@EnableScheduling用来装配spring的定时任务模块、@EnableCaching用于激活缓存等等。但是,不知道大家有没有思考过其背后的原理?本篇文章会从代码层面来解释Enable模块驱动的原理,同时也会讲解如何编写自定义的Enable注解来实现Enable模块驱动。 在正式开始讲解之前先讲解“Enable的前世今生”,便于让更加清楚的了解其背后的设计理念。 自定义Enable模块驱动 后面的例子都是围绕CustomServer这个接口来展开,该接口的功能很简单,就是启动和停止“自定义服务器”这两个功能。下面展示的是CustomServer接口的定义,同时也展示了两个该接口的具体实现。 java content_copy 复制代码 public interface CustomServer { void start(); void stop(); } java content_copy 复制代码 public class ServletCustomServer implements CustomServer{ @Override public void start() { System.out.println("servlet custom server start."); } @Override public void stop() { System.out.println("servlet custom server stop."); } } java content_copy 复制代码 public class ReactCustomServer implements CustomServer{ @Override public void start() { System.out.println("react custom server start."); } @Override public void stop() { System.out.println("react custom server stop."); } } 至此,CustomServer模块的功能已经实现完毕,那么接下来的工作就是要通过Enable模块驱动来将CustomServer模块集成到Spring应用中了。

阅读全文 arrow_forward
2022-03-20 · 约 5 分钟

SpringBoot可执行jar原理

前言 随着SpringBoot的流行,越来越多的企业开始使用SpingBoot。我认为之所以这个框架越来越流行,社区越来越活跃,跟这个框架为开发者带来的便利性有关。它不仅改变了原来我们的编程习惯,简化了业务编程模型,而且还大大的降低了搭建一个应用程序的复杂度。 但随着对这个框架的使用的深入,我发现这个框架远非其表面那么简单,日常所用的SpringBoot的功能特性仅仅是其冰山一角,我不禁有了疑问。SpringBoot到底为我们带来了哪些便利?它与Spring Framework的差别是什么?由于篇幅有限,本文着重讲解SpringBoot的可执行jar,其他特性也会顺带聊一聊。 要了解一个框架,首先要知道这个框架的功能特性,以及框架设计的目的,正所谓“名不正则言不顺”。我第一时间想到的是它的官方文档,官方总结的SpringBoot的特性包括如下: Create stand-alone Spring applications Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files) Provide opinionated ‘starter’ dependencies to simplify your build configuration Automatically configure Spring and 3rd party libraries whenever possible Provide production-ready features such as metrics, health checks, and externalized configuration Absolutely no code generation and no requirement for XML configuration https://spring.io/projects/spring-boot#overview 根据官方文档再结合个人经验总结后的SpringBoot的特性如下: 创建独立运行的Spring应用 — 可执行jar、SpringApplication 嵌入式web容器 —SpringApplication中的一部分 提供固化的starter,简化构建配置 —starter特性 自动装配Spring和第三方的依赖 —自动装配 提供Production-Ready特性(如Metrics、健康检查、外部化配置) 完全的无中间代码生成,也不再需要XML配置 —注解驱动 从上面可以看到SpringBoot可执行jar正是其提供的六大核心功能之一。不知大家是否还记得,在使用SpringMVC构建应用时想要运行自己的应用,必须要先将应用代码打包成一个WAR,再借助一个独立部署的Web容器(Tomcat、Jetty、UnderTow等等)才能运行。可是,当使用SpringBoot时,既不需要将应用打包成WAR,也不需要独立部署的Web容器,反而是直接将应用打包成一个JAR,然后直接通过java -jar命令就能启动。大家是否和我一样有疑问,为什么直接打成JAR就能运行?下面我将揭开它的神秘面纱。(可执行jar只是指jar的运行,本文不讨论嵌入式Web容器相关内容)

阅读全文 arrow_forward
2022-03-20 · 约 2 分钟

SpringBoot外部化配置

前言 在前面的《SpringBoot可执行jar原理》里面我提到过SpringBoot的几个核心功能,而今天的主角外部化配置(Externalized Configuration)正是其中之一。 Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources, include Java properties files, YAML files, environment variables, and command-line arguments. Spring官方给的解释是“外部化”你的配置文件,能让同一套的程序在不同的环境下工作。官方还给出properties、YAML、环境变量、还有command-line参数这些例子。 首先,这个外部化的配置肯定不是指我们自定义配置文件,而是指SpringBoot的系统配置文件。也就是我们常说的application.properties(或spring.yaml、spring.yml)。但是,“外部化”究竟是什么意思?SpringFramework和SpringBoot的配置到底有何差异? 差别对别 文本内容 SpringFramework xml content_copy 复制代码 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:p="http://www.springframework.org/schema/p" xmlns:websocket="http://www.springframework.org/schema/websocket" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd"> <!-- <mvc:annotation-driven /> 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,是spring MVC为@Controllers分发请求所必须的。它提供了数据绑定支持,读取json的支持 --> <mvc:annotation-driven /> <!-- 配置jsp视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="jspViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value=""/> <property name="suffix" value=".jsp"/> </bean> </beans> 这里我们看到了一些熟悉的配置,注解驱动、Componenet扫描、jsp相关配置等等。这些是我们在使用SpringFramework时经常看到的配置,可是在SpringBoot中我们似乎看不到了(忽略掉配置的内容,这里只是举个例子,想告诉你springFramework是通过这种方式来配置一个bean)。

阅读全文 arrow_forward