当初创建简书账号的时候曾立下宏愿,希望保持周更,无奈现实残酷,整个 5 月都处于忙忙碌碌的状态,居然令这个本来并不算太宏伟的目标难以为继,最终导致 5 月份交了白卷!【好吧,我承认,是我意志不够坚定,太懒了,;)】
最终的负罪感导致了本文的诞生,同时也意味着一个新坑的开启。作为 Vert.x 的使用者,我会定期更新获得的经验,希望为同好提供一些小小的帮助,扫除前进的障碍。同时,作为一个懒人和见异思迁者,事先声明,我没有办法保证我的兴趣不会发生转移,所以,我也不知道这个坑能开多久,望大家注意。
那么,让我们进入主题。
这是在跟某运营商的 NB-IOT 平台对接时解决的问题,其实最终的整个代码并不复杂,类似下面(采用双向认证方式):
httpClient = vertx.createHttpClient(new HttpClientOptions()
.setSsl(true)
.setPfxKeyCertOptions(new PfxOptions().setPath("xxx.pkcs12").setPassword("xxx"))
.setTrustStoreOptions(new JksOptions().setPath("xx.jks").setPassword("xxx"))
.setVerifyHost(false));
基本上跟文档上的例子差不多,值得注意的地方就是最后一行代码:setVerifyHost(false),当时这个运营商的平台也处于测试状态,并没有域名,是自签证书,设置为 false,客户端不验证服务端证书的合法性,则可规避这一点,让整个开发可以不受影响继续下去。
随着微服务的流行,JWT 也变得流行起来,但有一点开发经常忽视的是直接将生成 JWT 要用到的证书包含在源代码中。这一点其实是非常不妥的,有潜在的安全问题。
一个常识是:开发测试用的证书和产品环境的证书要分开,由不同的人来负责管理!
比如,在dgate中,产品环境的证书由运维通过下面的环境变量指定(例子):
export dgate_key_store=./test1.jceks
export dgate_key_type=jceks
export dgate_key_password=test123
在生成 JWT 对象时,从环境变量里直接拿这些值,供未来使用。
这是在实现dgate遇到的需求,小伙伴们强烈要求支持 Form 提交和文件上传,于是就有了RelayHandler的诞生。它的实际作用就是请求的透传,即将请求内容原封不动的搬到后端服务那里去。
实现很简单,看代码就知道,但在编写测试代码时就遇到了需要发起 Form 和 Upload 的问题。这个需求要是放在现在很简单,因为 Vert.x 从 3.4 开始提供了WebClient 模块,用它可以很轻易的实现这样的需求。
可在当时,这个模块还没有出来咧!那就只能自己动手了,因为并没有那么麻烦,本质上就是按照 HTTP 协议的要求,向 Request 写入对应格式的内容就行了。这一点可以从RequestUtils的 form 和 upload 方法实现里看出来。
这是在另一个项目中遇到趣事,其实既不是需求也不是问题,而是锦上添花,方便调试。在这个项目中,我们用 Vert.x 实现了一个 UDP Server,用户的设备会直接向这个 Server 发包。在一定的条件下,如时钟不对,Server 会向设备发起一个校时命令。
作为负责任的开发,我们当然不会依赖用户的硬件设备来进行调试啦!也就是说,我们自己是实现了一个 MockClient 的,它完全实现了设备和服务器的通信协议。内测没问题之后就到了联调阶段了,可偏偏我们已经发出了校时命令(终端有输出),但硬件却说没有收到!
要命的是,通过 Linux 的 tcpdump 来抓包,居然在 UDP Server 的地址和端口上没有抓到对应的包!这真是浑身是嘴也说不清啊!而且,自己写的 Mock 与 Server 总是能够顺畅的通信!
遇到这么个怪问题那就不得不上网络调试的终极大杀器Wireshark了,看看到底发生了什么。很快,问题定位出来了,下发命令确实已经发出,只不过是从另一个端口发出去的。这是因为写代码时,觉得 UDP 反正就没有连接的概念,只要有对方的地址信息那就直接发送过去就好了。于是,在发送前重新创建了一个 DatagramSocket 实例,通过它直接发出去了。
实际的修改也很简单,用 UDP Server 对应的那个 DatagramSocket 实例发送就好了。最后的结果当然是皆大欢喜。
我之前不止一次的表达出对于 Ignite 的喜爱,觉得它比 Redis 要好(注:最近有所改变,因为 Redis 提供了对于 HpyerLogLog 开箱即用的支持,而 Ignite 则貌似没有。)。这次,在项目中终于采用了以它为基础的 Vert.x 集群方案。
整个集成和使用并不复杂,这是我们做过的事情:
@Override
public void beforeStartingVertx(VertxOptions options) {
//Force to use cluster mode
options.setClustered(true);
}
if (ignite == null) {
vertxInstance = vertx;
ClusterManager clusterManager = ((VertxInternal) vertx).getClusterManager();
String uuid = clusterManager.getNodeID();
ignite = Ignition.ignite(UUID.fromString(uuid));
...
}
有了 Ignite 实例之后,其他的功能,如 Read/Write Through、Cache 事件等等,就是按照 Ignite 文档的指示来做了。这部分的内容已经属于 Ignite 的领域且文档也有详细的阐述,这里就不再赘述。
作为服务器应用,怎么能不来一把压测来对其性能摸底呢?在实际过程中,新手最容易忘记的问题就是:没有修改操作系统的最大可用句柄数,导致压力还没上来就进行不下去了。
除此之外,用 Vert.x 自写压测代码时也需要注意:
vertx.createDatagramSocket(
new DatagramSocketOptions()
.setReceiveBufferSize(UDP_RECEIVE_BUFFER_SIZE)
.setSendBufferSize(UDP_SEND_BUFFER_SIZE))
.listen(..., "0.0.0.0", asyncResult -> { ... }
vertx.setPeriodic(20000, tid -> {
mockClients.forEach((uid, socket) -> {
vertx.setTimer(ThreadLocalRandom.current().nextInt(10, 10000)
, id -> {
logger.info("...", uid);
send(...);
vertx.cancelTimer(id);
});
});
});
至此,这段时间积压下来,觉得有必要写写的内容已经全部倾倒完毕,敬请期待下一期(如果真有的话,;))。
觉得有帮助的话,不妨考虑购买付费文章来支持我们 🙂 :
付费文章