(马蜂窝技术原创内容,申请转载请在公众后后台留言,ID:mfwtech )

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

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

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

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

Part.1 准备篇  

终于要开始学习了!

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

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

OK,可以开始编程了。隔壁甲小白凑过来说:「IntelliJ 写 Java 很爽,买一个吧,才 1000+ RMB~」。

……

咬咬牙!

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

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

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

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

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

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

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

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

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

Part.2 实战挖坑篇

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

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

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

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

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

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

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

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

Part.3 边挖边填篇

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

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

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

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

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

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

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

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

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

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

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

但帅不过 3 分钟...唉!

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

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

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

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

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

「不行......」

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

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

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

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

Part.3 上线篇

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

「喂,小美,上线成功了,速度回归~」。天降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身...... 激动!痛快!为了表达此刻的心情,我要用表情包创建一个商品,放满了各种 Emoji,就是任性 !

我擦,怎么返回服务异常了?

「小美,你是不是大流程没覆盖到啊?」

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

果然在线上也找到了解决方案,原因是对应的数据表的编码方式是 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,没有反馈问题,结束了!

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

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

甲小蛙战记:PHP2Java 排雷指南的更多相关文章

  1. 小程序 & taro 踩坑指南

    小程序 & taro 踩坑指南 微信开发者工具, 不支持 react bug https://github.com/NervJS/taro/issues/5042 solution just ...

  2. 美团小程序框架mpvue蹲坑指南

    美团小程序框架mpvue(花名:没朋友)蹲坑指南 第一次接触小程序大概是17年初,当时小程序刚刚内侧,当时就被各种限制折腾的死去活来的,单向绑定, 没有promise,请求数限制,包大小限制,各种反人 ...

  3. 规范抢先看!微信小程序的官方设计指南和建议

    基于微信小程序轻快的特点,我们(微信官方)拟定了小程序界面设计指南和建议. 设计指南建立在充分尊重用户知情权与操作权的基础之上.旨在微信生态体系内,建立友好.高效.一致的用户体验,同时最大程度适应和支 ...

  4. 微信小程序开发填坑指南V1

    近期用了一星期的时间,开发了一个小程序.小程序名称是:小特Jarvis,取自钢铁侠的管家. 后台采用C#编写,WebAPI接口.其实开发时间并不多,小程序本身提供的API,相比公众号的API来说,已经 ...

  5. 【微信小程序】踩坑指南(持续更新)

    前言 说明: 基于mpvue框架:mpvue官方文档 语法同vue框架:vue官方文档 小程序中会有一些坑点,这里会就工作中遇到的坑一一列举出来 无说明时请直接看代码注释 v-show无法使用在小程序 ...

  6. 微信小程序零基础制作指南

    第一步 准备工作 下载腾讯官方的微信web开发工具并且安装 第二步 登录已经注册小程序的微信账号并且选择合适的路径新建小程序项目. 新建一个页面文件夹,每一个页面需要一个文件夹,里面包括写逻辑脚本的j ...

  7. 不知如何摧毁Kendo UI for jQuery小部件?这份指南不得不看

    [Kendo UI for jQuery最新试用版下载] Kendo UI目前最新提供Kendo UI for jQuery.Kendo UI for Angular.Kendo UI Support ...

  8. win10中,vscode安装go插件排雷指南

    最近学习go,想着使用强大的vscode编写go,在安装go插件过程中,遇到了很多问题.下面记录解决方案. 1)win10环境,安装go,vscode,git 配置GOPATH环境变量,在我的电脑-& ...

  9. 微信小程序学习指南

    作者:初雪链接:https://www.zhihu.com/question/50907897/answer/128494332来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

随机推荐

  1. 2018.8.2 python中is和==的区别

    一.is 和==的区别 1.is 比较的是左右两边的内存地址, ==比较的是左右两边的值. 2.id() 通过id()可以查看一个变量表示的值得内存中的地址. s = 'alex' s1 = 'ale ...

  2. WinDbg命令系统

    WinDbg命令系统 WinDbug三种命令 WinDbug是一个强大的调试器,大部分很多功能都是通过命令来实现的,命令在命令窗口中输入,主要分为以下三类: 标准命令 标准命令提供了调试器的基本功能, ...

  3. 使用linq实现回调函数

    通过输入的Id找到parentId是该Id的列表,然后找到parentId是上面那个Id的列表,以此类推,找到第一目录下所有子目录的的列表. 通过传入第一目录的Id,得到该目录下的所有子目录. 通过回 ...

  4. yii2中commands的简单应用

    class HelloController extends Controller { /** * This command echoes what you have entered as the me ...

  5. 调用微信js sdk

    场景:需要调用微信获取当前位置的借口. 途径:查看微信 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 .后 ...

  6. [考试反思]1016csp-s模拟测试76:自知

    要打对拍. 要打对拍. 要打对拍. 要手模数据. 要手模数据. 要手模数据. 不要相信样例. 不要相信样例. 不要相信样例. 不要飘. 不要飘. 不要飘. 跟skyh学坏了.最近不打对拍. 连续十几次 ...

  7. CSPS模拟 100

    我又挂分了T_T 这么吉利的数字..本来想考的好一点的 T1 没加当前弧优化(其实也不会),若志了 各种低错连篇而且没想到点不联通..没有奇度点就直接从1开始搜了 于是喜提70(犯了这两个若志错误应该 ...

  8. 大厂面试经:说一下你们线上JVM是如何优化的?

    JVM(Java虚拟机)简单来说就是运行Java代码的解释器,作为螺丝钉程序员JVM其实了解下就差不多啦,不懂JVM内部细节照样能写出优质的代码!但是一到造火箭.飞机的场景(面试)不懂JVM的你,会被 ...

  9. Python的看门狗实现自动化实时对服务器、Windows或Linux文件夹的实时监控

    众所周知,在运维过程中,实时获取目标文件夹至关重要,Python的watchdog是用程序来监视文件系统事件Python库,所以用该库可以实现对文件夹的实时监控,filenotify.py代码如下: ...

  10. [LINQ2Dapper]最完整Dapper To Linq框架(一)---基础查询

    此例子是使用LINQ2Dapper封装,效率优于EntityFramwork,并且支持.NetFramework和.NetCore框架,只依赖于Dapper 支持.net framework4.5.1 ...