45

甲小蛙战记:PHP2Java 排雷指南

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=Mzg5MTA4Mzg5NA%3D%3D&%3Bmid=2247484213&%3Bidx=1&%3Bsn=c6b569a936c885e2a61f9965798a6ae8
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

F3AVjqj.gif

点击上方“马蜂窝技术”,关注订阅更多优质内容

大家好,我是来自马蜂窝旅游网旅游平台研发的甲小蛙。从前是一名 PHP 工程师,现在可能是一名 PHJ 工程师,以后......

vQBVvqQ.jpg!web

前阵子,我从大道消息听说公司商品订单技术栈要推 Java。我是一个喜欢走在时代前列线上的人,凡是要做到领先。我对 Java 也是仰慕已久,于是花了两天时间学习 Java,并调研各种框架和解决方案,决心要把商品和订单的主要功能用 Java 重构掉。

在经历了 798 难后现在这些东西都踉跄上线了,我也成了马蜂窝的顶梁柱。虽然表面看来风光无限,但是这一路走来相当不容易,累到有上觉没下觉,踩坑把腿踩断,才有了今天这篇战记。希望大家看完后不要吸取任何教训,抱着不撞南墙不回头的心态,继续从头踩坑。

风险提示 :文章会先带大家入坑,然后出坑,请保持秩序不要拥挤;如果文章看了一半就去实践,有被队友打死的风险!

准备篇

1

YzaYJjF.png!web

终于要开始学习了!

fauY3ab.jpg!web

9 月 1 号,趁着开学季,买了《两天精通 Java》、《三天精通 SpringBoot》两本书,看到书名仿佛感觉胜利在向我招手。

uM7VjiU.gif

9 月 2 号书到了,两天没睡觉把书看完了。原来 Java 这么简单,也是各种 class,interface,abstract class。Java 还有一个响亮的口号——「万事万物皆对象」。

OK,可以开始编程了。旁边的甲小白说:「IntelliJ 写 Java 很爽,买一个吧,才 1000+ RMB~」。

YjuYfqf.jpg!web

咬咬牙!

环境搞好了,来,写个 Demo:

IZj6Frz.jpg!web

SpringBoot 果然名不虚传,一定有一个很懂用户的 PM,为我们省去了原来需要在 Spring 中配置的一堆 XML 文件,上手真的灰常简单。比较奇怪的是 Java 居然没有 echo,想对 Java 世界说声「你好」,居然还得写 System.out.println("哈喽,窝的")。切!【 划重点:Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域 (rapid application development) 成为领导者

JFZrQvv.jpg!web

感觉 Java 对于无所不能的我来讲真是太简单了,就是有点繁琐。万事万物皆对象,还建议把对象属性设置为 private。偶买尬!可是对象属性也太多了吧!商品对象有近 100 个属性,你让我给他们挨个写 getter,setter 方法?别闹了!

为了显得专业点,我开始给这 100 个属性写 getter、setter 方法。花了一个小时写完了,可累死我了,一旁的甲小白看着我的代码说,「你在刷代码量么,为什么不用 @Getter @Setter 注解?」这什么东西?别跑!为什么不早说!【 划重点:@Getter @Setter 是 Lombok 中的两个编译器注解

qi6Rnye.jpg!web

话说,自从用了这个叫做注解的东西,手指也不疼了,腰也不酸了,好评!

好了,看来 Java 和 SpringBoot 已经被我研究通透了,突然想起来个事儿,商品要调用好多部门的 PHP 接口,可是他们没有 Java 版的接口,这可肿么办!然而机智的队友早已看穿一切,做了一个叫做 PHP 网关的东西,可以把 PHP 接口全部包装成 HTTP 请求。

ZBvMneN.jpg!web

万事俱备只欠南风,其他也都调研的差不多了,该动真格的了。试试连接数据库吧。听说 Java 有个连库的好东西,叫 Druid,整!【 划重点:Druid 是阿里巴巴开源平台上一个数据库连接池实现,它结合了 C3P0、DBCP、PROXOOL 等 DB 池的优点,同时加入了日志监控,可以很好的监控 DB 池连接和 SQL 的执行情况,可以说是针对监控而生的 DB 连接池,据说是目前最好的连接池

R3QZr2E.jpg!web

调试没问题,继续。我要连线上数据库了,但是没有账号密码,跟 DBA 要吧,结果 DBA 以盗取数据库密码「叛窝」罪名把我暴打一顿。最后扔下一张卡片——「健身游泳了解下」的上面赫然写着一行小字:SkipperClient。这是什么东东?咱也不知道咱也不敢问,Gitlab 一下吧,原来用这个就可以根据服务名换取对应的 DB 库的账号密码,懂了( 还有更方便的用法,可以直接用微服务的配置中心+Spring 原生配置即可完成 )。但是怎么链接到我们的内部 maven-repository 呢?找身边的同学 Copy 一份 Maven 的配置文件就好了。( 划重点:Maven 是一个项目管理工具,可以对 Java 项目进行构建、依赖管理

到此,我已经顺利完成学业,准备出师开始搬砖了!

实战挖坑篇

2

环境和项目已经搭建好,要开始写逻辑部分了。

首先我有这么个需求:保存商品的时候流程走得比较多,很多流程都要用到该商品的基本信息,但是又不想通过一层层 set 值进行传递。So 来个单例吧,Spring 有一个强大的东西叫做容器,配合 IOC 可以完美满足我的需求。【 划重点:@Service 把需要用来承载商品关键信息的类注册为 Bean,由 Spring 容器管理其生命周期;@Resource 注解下要进行注入的变量即可完成依赖注入,再也不需要自己手动 set 了

veIJjuU.png!web

BvqUj2Y.png!web

是的,就这么几行简单的代码就可以满足我的需求了,跑起来。

IrQJfq2.png!web

咦,怎么会有 NullPointerException 呢?一顿搜索之后才知道,原来想要使用这个 Bean,那使用这个 Bean 的类也要注册在 Spring 容器中,由 Spring 创建才行……就没别的办法吗?又一顿搜,找到了解决方案,可以手动获取 Spring 容器的上下文,终于让我毫不费神,非常费力地完成了逻辑部分的开发。

因为项目用到了很多反射(跟 PHP 反射类似),所以我很机智地给每一个可能跑异常的方法增加了 try catch 代码块,以体现我对异常的敏锐嗅觉,报出异常后可以第一时间锁定异常代码,同时加上了给用户的提示「服务出错」。

6JZ3quU.jpg!web

好了,测试下,没问题,可以看到异常日志。等等,好像哪里不对劲,为什么最内层的报异常后,记录了 4 条异常 log 呢?定睛一看,发现内层给用户返回的异常提示又被外层捕获了,导致多次日志记录。嗯…我承认这个问题有点傻了~但是我确实是想记录日志并提示用户,怎么办?请教了身边的甲小白。小白一把把我推开,在我的青轴键盘上一顿动次打次后,说:改好了,只需要把异常抛出,让最外层捕获就好了。【 划重点:如果选择了让方法 throws Exception(指 Java 让强制抛的,非自定义的 throw new XXXException),那调用该方法的方法只要不处理异常,就需要继续 throws Exception,所以尽量不要嵌套 100 层方法

zAFRRb3.jpg!web

还有一个头疼的问题,PHP 里字段大多使用的是蛇形 字段(goods_info),而 Java 里好像更常用驼峰(goodsInfo)。我总不能让蛇形字段类来接收参数,然后再转成我的内部驼峰类吧。我本能地搜了一下,居然真的有解决方案,只需要用 @JSONField @JsonProperty 注解就可以搞定这个问题了。不得不说 Java 的生态还是比较完善的,提供了各种问题常用的解决方案。【 划重点:@JSONFIeld @JsonProperty 是两个运行时注解,前者是 fastjson 包的注解,后者是 jackson 包的注解

uARN32n.jpg!web

好了,遇到的问题基本都解决了,自测通过。提测。测试环境也部好了!

eUrm2yf.png!web

边挖边填篇

3

甲小美同学开始测试了。我跟她讲解了目前的服务调用情况是这样的:PHP->Java->PHP(即 PHP 接收用户端请求,然后封装参数调用 Java 微服务,Java 微服务再调用一些 PHP 的接口进行数据校验)。

刚开始测试,就遇到个小问题。由于部署的 Java 微服务没有部署为上线状态,而是「内测中」,该怎么访问呢?这个时候想到了我们的浏览器插件。装插件,选分支,搞定!但是测试同学还是说没访问到,怎么办?这时候想到了浏览器插件的工作原理,带 Cookie。【 划重点:PHP 如果要访问内测中 Java 微服务,PHP 中访问 Java 微服务时一定要把 Cookie 携带上

甲小美同学埋头测了好久,我心想,看来是基本没什么问题,明天上线!

「甲小蛙,为什么我新创建了一个商品,列表页里没有新增呢,而且好像是把我刚才创建好的商品给改掉啦!」

mmYBzyr.jpg!web

听完赶紧把本地程序运行了起来,完美复现!瞬间背后一凉。我意识到可能是 @Service 的问题,发现注解后的类全局单例,也就是无数个用户会共享这一个对象。习惯了 PHP 进程内单例的我有些无法接受,这可咋整,顿时有点懵,其他同学也是刚刚入坑,好像没有好的办法,怎么办…实力不够,体力来凑,还是乖乖改造成了一层层传递对象的方式。(这个方式一直被沿用到线上,后来才发现还有个叫做 @Scope 的注解,可以控制 Bean 的作用域,一股悲凉袭上心头)【 划重点:@Scope 可以指定 4 中 SpringBean 的作用域,有:单例(singleton)、原型(prototype)、会话(session)、请求(request)

刚修好没多久,同样的问题又来了,真是一 Bug 未平一 Bug 又起。切换到开发环境,获取和保存了商品,咦,没问题啊。

我:你是不是打开方式不对啊?

甲小美:打开方式肯定没问题!

只好把小美同学拉到屏幕前,一次又一次地刷着链接:「你看,没问题吧!你看,你看,你看,崩了吧......」

Rr2qMri.jpg!web

报错信息:想不起来了。大致意思就是说连接无效,链接丢了。问了下 DBA,说我们 MySQL 的链接空闲断开时间是 30s,也就是说链接到数据库后,30s 内并没有再执行 SQL,这个链接就会被断开。搜了解决方案,如下:【划重点: setTestWhileIdle = true,是否在获得链接后检测其可用性;setTestOnBorrow = true,是否在连接放回连接池后检测其可用性

3Ibq2mN.jpg!web

果然好了,链接通畅,又能愉快地测试了。

果然帅不过 3 分钟...唉!

按照场景复现了下,发现基本都是一个问题,也是 PHP 转 Java 比较头疼的问题。由于 PHP 的弱类型以及和没有前端同学约定好数据格式,导致前端可以传来各种各样的数据类型。原本以为的 Integer(如:10),前端可以传 Float(如:10.5);原本以为的 Float(如:999.9),前端可以传 String(如:999.9 元/人)。总之,这是一个比较大的坑,还是且行且珍惜啊。PHP 确实是世界上最好的语言,但 Java 应该是世界上最严谨的语言。【 划重点:使用 Java 的好处之一,是在设计数据格式时,可以让我们的数据更加规范和严谨

nmuIfe6.jpg!web

「为什么开发环境商品下线抛了事件,但是对应的行为没有执行呢?」我强忍住心中的痛楚,先是检查了事件的监听,又确认了 PHP 的订阅确实被执行了,但是发现 PHP 调用 Java 返回了错误。

RZnM7nZ.png!web

怎么会 404?本地跑的好好的啊,明明有这个 Action,怎么回事?首先,这个 Action 是 Java 微服务中新增的,404 意味着没找到,有可能是访问到「已上线」的服务中去了,是不是没有带上 Cookie?带着疑问去验证,果然。【 划重点:消息总线订阅者在访问 Java 微服务时是不会携带 Cookie 的,默认会走「服务中」的服务;如果 Java 服务在此过程中还需要访问 PHP,还需要在 Java 微服务中指定要访问哪个 docker,要不然会迷路的

「为什么商品编辑后,详情页的内容没有更新啊?」

FFbEvmy.jpg!web

「是不是更新详情页的事件没执行啊,你多保存几次,更新下数据」

「不行......」

好吧,来到最熟悉的 PHP 环境,各种 Debug,发现 PHP 里好多接口都加了一层缓存,突然间恍然大悟,在保存完商品后更新了下这个接口的缓存。( 划重点:默认情况下 Ko 会在 aGet,aMultiGetList 等接口中增加一层 memcache 的主键缓存,如果用 Java 服务更新了数据,记得来清下 Ko 的主键缓存

甲小美:「为什么1......」

甲小美:「为什么2......」

甲小美:「为什么3......」

3mYbMbM.jpg!web

Rr2qMri.jpg!web

q2yYRvb.jpg!web

上线篇

3

终于熬到这一天,我自信地站在镜子前,笨拙系上红色领带的结,将头发梳成大人模样,穿上一身帅气西装,等会儿上线一定比想像顺~

YR3eMjb.gif

AjIVvuJ.png!web

「喂,小美,上线成功了,速度回归~」,天降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身...... 激动!痛快!为了表达此刻的心情,我要用表情包创建一个商品,放满了各种 Emoji,就是任性 !我擦,怎么返回服务异常了?「小美,你是不是大流程没覆盖到啊?」

「你给我冷静点~先看日志」,「哦」。我以迅雷不及掩耳盗铃之势把流量开关给关掉了,线上又走回了 PHP 的流程。日志显示如下:【 划重点:对于一些大流程的改造,建议加上一个 switch 开关,方便线上出问题后马上切流量,比修改代码提 MR 再发布要高效和准确

ZvmM73Y.png!web

果然在线上也找到了解决方案,原因是对应的数据表的编码方式是 utf8,而 Emoji 存入数据库的编码是 utf8mb4,所以异常了【 划重点:MySQL 在 5.5.3 版本以后增加了 utf8mb4 编码,其中 mb4 是 most bytes 4 的含义,用来兼容四个字节的 Unicode(万国码)。utf8mb4 是 utf8 的一个扩展,可以给数据表换个编码方式来解决这个问题 】。但是为什么 PHP 可以把 Emoji 存入 utf-8 的表中,而 Java 不能?这个问题还在困扰着我...... 最好的语言,名不虚传。

1 个小时后......

2 个小时后......

4 个小时后......

8 个小时后......

Yeah,没有反馈问题,结束了!

rUrqUrA.jpg!web

当马蜂窝的顶梁柱真是不容易啊!

本文作者:马蜂窝旅游网-旅游平台研发团队。

(责任编辑:于雪)

bI7Jjqi.png!web

「在看」 我吗?

YZjANrm.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK