事故

昨晚上准备把 sequences 服务扔到我的旧笔记本上然后写下单系统,然后发现一个很囧的事情……

消息序列化竟然失败了。

翻过去的代码,逐个排除问题,基本上结论是:因为过去我的测试写的太简单,所以没暴露出我写的基于 nippy 和 chesire 的序列器其实都是错的

因为我过去写的测试太简单,所以没正确的定位 kryo 序列化 Clojure 类型时遇到的问题

因为我过去写的测试太简单,没有正确的暴露出网络环境中不同操作系统的一些限制

因为我过去写的测试太简单,没有正确的暴露出 Akka remote 的一些配置约定

……

所以,测试还是很重很重要的呀`(~_~;)` 。

总结

本着能省事儿绝不折腾的精神,总结了几点体会:

网络绑定

惯例上我们会用 0.0.0.0 表示我们希望一个网络服务绑定环境内的所有地址,但是这个惯例在 Akka remote 里不成立 https://groups.google.com/forum/#!topic/akka-user/cRZmf8u\_vZY 。

Kryo 的对象管理

Kryo 的官方有给出一个建议,在项目开发的阶段打开 kryo-trace = true ,然后看哪些出现在 trace 日志中的类型没有被正确的分配id,而是给了一个较大的随机数,把这些类型都在 Init 代码中注册到 kryo 上,形如下例:

package liu.mars.market.serialization;

import clojure.lang.*;

import com.esotericsoftware.kryo.Kryo;

import com.fasterxml.jackson.databind.node.*;

import liu.mars.market.messages.CreateSequence;

import liu.mars.market.messages.DropSequence;

import liu.mars.market.messages.ListSequences;

import liu.mars.market.messages.NextValue;

import java.util.ArrayList;

import java.util.LinkedHashMap;

public class KryoInit {

public void customize(Kryo kryo){

kryo.register(CreateSequence.class);

kryo.register(DropSequence.class);

kryo.register(ListSequences.class);

kryo.register(NextValue.class);

kryo.register(ObjectNode.class);

kryo.register(ArrayNode.class);

kryo.register(LongNode.class);

kryo.register(TextNode.class);

kryo.register(ArrayList.class);

kryo.register(LinkedHashMap.class);

kryo.register(JsonNodeFactory.class);

kryo.register(PersistentArrayMap.class);

kryo.register(PersistentVector.class);

kryo.register(PersistentList.class);

kryo.register(Keyword.class);

kryo.register(Symbol.class);

kryo.register(LazySeq.class);

}

}

此时Sequences项目的server.conf如下:

akka {

extensions = ["com.romix.akka.serialization.kryo.KryoSerializationExtension$"]

actor {

provider = remote

serializers {

java = "akka.serialization.JavaSerializer"

kryo = "com.romix.akka.serialization.kryo.KryoSerializer"

}

serialization-bindings {

"liu.mars.market.messages.CreateSequence" = kryo

"liu.mars.market.messages.DropSequence" = kryo

"liu.mars.market.messages.ListSequences" = kryo

"liu.mars.market.messages.NextValue" = kryo

"com.fasterxml.jackson.databind.node.ObjectNode" = kryo

"com.fasterxml.jackson.databind.node.ArrayNode" = kryo

"clojure.lang.PersistentArrayMap" = kryo

"clojure.lang.PersistentList" = kryo

"clojure.lang.PersistentVector" = kryo

"clojure.lang.LazySeq" = kryo

"clojure.lang.Keyword" = kryo

"clojure.lang.Symbol" = kryo

"java.util.ArrayList" = kryo

"liu.mars.market.messages.Sequence" = kryo

}

kryo {

type = "graph"

idstrategy = "incremental"

buffer-size = 4096

max-buffer-size = -1

kryo-custom-serializer-init = "liu.mars.market.serialization.KryoInit"

kryo-trace = false

}

}

remote {

enabled-transports = ["akka.remote.netty.tcp"]

netty.tcp {

hostname = "192.168.50.22"

port = 25520

}

}

}

而 test.conf 如下:

akka {

extensions = ["com.romix.akka.serialization.kryo.KryoSerializationExtension$"]

actor {

provider = remote

serializers {

java = "akka.serialization.JavaSerializer"

kryo = "com.romix.akka.serialization.kryo.KryoSerializer"

nippy = "liu.mars.market.serialization.NippySerializer"

cheshire = "liu.mars.market.serialization.ChesireSerializer"

}

serialization-bindings {

"liu.mars.market.messages.CreateSequence" = kryo

"liu.mars.market.messages.DropSequence" = kryo

"liu.mars.market.messages.ListSequences" = kryo

"liu.mars.market.messages.NextValue" = kryo

"com.fasterxml.jackson.databind.node.ObjectNode" = kryo

"com.fasterxml.jackson.databind.node.ArrayNode" = kryo

"clojure.lang.PersistentArrayMap" = kryo

"clojure.lang.PersistentList" = kryo

"clojure.lang.PersistentVector" = kryo

"clojure.lang.LazySeq" = kryo

"clojure.lang.Keyword" = kryo

"clojure.lang.Symbol" = kryo

"java.util.ArrayList" = kryo

"liu.mars.market.messages.Sequence" = kryo

}

kryo {

type = "graph"

idstrategy = "incremental"

buffer-size = 4096

max-buffer-size = -1

kryo-custom-serializer-init = "liu.mars.market.serialization.KryoInit"

kryo-trace = true

}

}

remote {

enabled-transports = ["akka.remote.netty.tcp"]

// netty.tcp {

// hostname = "192.168.50.179"

port = 25521

// }

}

}

此时 test profile 的 config.edn 如下:

{:db-spec {:dbtype "postgresql"

:dbname "test"}

:remote {:sequences "akka.tcp://sequences@192.168.50.22:25520/user/sequences"}}

而 server profile 的 config.edn 如下:

{:db-spec {:dbtype "postgresql"

:dbname "sequences"

:user "market"

:password "market"}}

需要说明的是,这里的 ip 都是硬编码的我家里的局域网,读者们要运行示例的话需要自行编辑。数据库口令用了一个很弱的密码,这个在生产环境是要避免的。

另外 Ubuntu 桌面版默认不开放太大的端口号,而MacOS不开放太小的端口号,所以开发环境和测试环境的网络端口离的很远。

Clojure 对象的序列化

仔细看了一下 kryo 的报错,发现一个规律,它总是在反序列化Clojue的线性容器(vector/list/lazyseq 等等)的时候,提示无法反序列化 null。

这个消息么,让我想起一些很玄学的东西,比如 lisp 的 list 在语意上总有一个 nil 作为结尾……

但是现在走这条路就跑题太远了。

实际测试,Array 没有问题,ArrayList 没有问题,自定义的 POJO 也不会有问题,甚至 Jackson 的各种 Node 也都没有问题。

我在当前版本的 Sequences 服务中,就是把 LazySeq 用 into-array 变成数组,或者提取出其中的数据。总的来说避免Clojure List 类数据用 Kryo 来传递。

其实比较无脑的套路是用 Jackson 的 ObjectMapper 对象的 valueToTree 方法,经过实践是可以的。注意在开发过程中把前例没有加入的 Node 类型都注册到 kryo 就可以。

现在的 seq.clj 是这样的:

(nsliu.mars.market.seq

(:require [clojure.java.jdbc :as jdbc])

(:require [jaskell.sql :refer [selectfrom as]])

(:require [liu.mars.market.config :as config])

(:require [cheshire.core :refer [generate-string]]))

(defdb-spec (delay (config/db-spec)))

(defncreate-seq

[^String seq-name]

(->@db-spec

(jdbc/execute! (str"create sequence " seq-name))

first))

(defnlist-seq

[]

(let[sql (->(select["sequencename" :as :name " COALESCE(last_value, start_value)" as "last"] from :pg_sequences)

(.script))]

(->@db-spec

(jdbc/query [sql])

(into-array))))

(defnnext-val

[seq-name]

(->

(jdbc/query @db-spec ["select nextval(?)" seq-name])

first

(:nextval)))

(defndrop-seq

[seq-name]

(->@db-spec

(jdbc/execute! (str"drop sequence " seq-name))

first))

为了确保这次没有问题,我写了一个clojure的测试模块:

(nsliu.mars.market.remote-test

(:require [clojure.test :refer :all])

(:require [liu.mars.market.config :refer [conf]])

(:import (akka.testkit.javadsl TestKit)

(akka.actor ActorSystem)

(liu.mars.market.messages ListSequences CreateSequence NextValue DropSequence)

(java.util.function Supplier Function)))

(def^String url (->@conf

:remote

:sequences))

(testing "tests through akka tcp provider"

(let[system (ActorSystem/create "test")

test-kit (TestKit. system)

self (.getRef test-kit)

await#(.awaitCond test-kit (reify Supplier (get[this] (.msgAvailable test-kit))))

remote (.actorSelection system url)

seq-name "test"]

(testing "tests about a sequence lifetime"

(.tell remote (ListSequences.) self)

(await)

(.expectMsgPF test-kit "message should't include test"

(reify Function

(apply[this msg]

(let[data (vec msg)]

(is (not-any?#(=(:name %) seq-name) data))))))

(.tell remote

(doto(CreateSequence.)

(.setName seq-name))

self)

(await)

(.expectMsgClass test-kit Integer)

(.tell remote (ListSequences.) self)

(await)

(.expectMsgPF test-kit "message should include test"

(reify Function

(apply[this msg]

(let[data (vec msg)]

(is (some#(=(:name %) seq-name) data))))))

(dotimes[n 100]

(.tell remote (doto(NextValue.) (.setName "test")) self)

(await)

(.expectMsgPF test-kit "should get next value more than times"

(reify Function

(apply[this msg]

(is (=(incn) msg))))))

(.tell remote (doto(DropSequence.) (.setName seq-name)) self)

(await)

(.expectMsgClass test-kit Integer)

(.tell remote (ListSequences.) self)

(await)

(.expectMsgPF test-kit "message should't include test"

(reify Function

(apply[this msg]

(let[data (vec msg)]

(is (not-any?#(=(:name %) seq-name) data))))))

(TestKit/shutdownActorSystem system))))

代码风格不算太干净,简单的实验了一遍构造一个 sequence 然后调用最后删除的过程。

java 对象排重_现代化的 Java (八)——重说对象序列化相关推荐

  1. java中对象的生存期_深入理解Java虚拟机-判断对象是否存活算法与对象引用

    我们知道Java中的对象一般存放在堆中,但是总不能让这些对象一直占着内存空间,这些对象最终都会被回收并释放内存,那么我们如何判断对象已经成为垃圾呢?这篇文章会提出两种算法解决这个问题.另外,本文还要谈 ...

  2. java构造方法的签名_如何在 Java 中构造对象(学习 Java 编程语言 034)

    1. 构造器 Java 对象都是在堆中构造的. 先看看 Employee 类的构造器: public class Employee { private String name; private dou ...

  3. java多线程并行执行命令_深入理解Java多线程与并发框(第④篇)——重排序、屏障指令、as-if-serial规则...

    ![](http://img.blog.itpub.net/blog/2020/03/24/906435fda570a5e3.png?x-oss-process=style/bb) # 一.重排序 前 ...

  4. java 创建对象 设置属性_详解Java的对象创建

    1. 前言 在<还不清楚怎样面向对象?>和<面向对象再探究>两篇文章中,都介绍了关于面向对象程序设计的概念和特点.其中也涉及到了许多代码,比如: Dog dog = new D ...

  5. java如何实例化集合_如何在java中实例化一个Queue对象?

    Queue是一个接口,这意味着你不能直接构造一个Queue . 最好的select是构造一个已经实现Queue接口的类,如下所示: AbstractQueue , ArrayBlockingQueue ...

  6. java 防重_如何做一个防重设计

    前言 在业务设计中防重设计是一个关键点,以接口设计为例,防重就是防止接口被多次调用而产生脏数据,比如支付订单出现重复支付,所以说防重至关重要,在如何防重之前我们首先看一下是如何出现重复请求的. 何时出 ...

  7. java 死锁 内存消耗_详解Java中synchronized关键字的死锁和内存占用问题

    先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并 ...

  8. 新手学java 学哪方面_初学者学Java应从哪些方面学习?

    原标题:初学者学Java应从哪些方面学习? Java作为应用于网络的最好语言,前景无限看好.然而,就算用Java建造一个不是很烦琐的web应用,也不是件轻松的事情.那么,初学者学Java应从哪些方面学 ...

  9. java让线程空转_详解Java编程中对线程的中断处理

    1. 引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时--都需要通过一个线程去取消另一个线程正在执行的任务.Java没有提供一种安全直接的方法 ...

最新文章

  1. 精品软件 推荐 硬盘物理序列号修改专家
  2. 因子分析累计方差贡献率要在多少_R语言进阶之因子分析
  3. Codeforces Global Round 14 F. Phoenix and Earthquake 思维 + 并查集
  4. 抓住训练集中真正有用的样本,提升模型整体性能!
  5. 2019阿里云开年Hi购季云通信分会场全攻略!
  6. 通过Small Basic把儿子/女儿带入编程的世界
  7. Kotlin学习笔记28 Flow part2 Flow引入 Flow的执行 取消 构建器 中间操作符 终端操作符 默认执行顺序 上下文相关
  8. 10万人参加过的公开课(大数据、AI、云计算、5G、物联网),你都学了吗?
  9. flowable 查询完成的流程_中注协正在调试注册会计师成绩查询系统?
  10. C++实现LRU(Least-Recently Used)缓存算法
  11. 程序猿的键盘侠养成:macOS 常用快捷键分享
  12. 无法打开登录所请求的数据库 xxxx。登录失败。 用户 'NT AUTHORITY\SYSTEM' 登录失败。...
  13. PHP中根据汉字返回拼音
  14. [brew]切换brew源
  15. 2013年春节前订票经历及经验分享
  16. 记一次重大的生产事故
  17. YDLIDAR G4雷达的unity使用相关+北阳雷达
  18. 奇瑞汽车用鸿蒙,奇瑞正式确认:将搭载“华为鸿蒙车机系统”,鸿蒙系统真的要来了...
  19. MCE | HIV 衣壳蛋白有望成为 HIV 治疗新靶标
  20. 《时代周刊》2019年度100大最佳发明榜单发布!中国2项上榜

热门文章

  1. 支付宝招“找茬”程序员,年薪无上限;谷歌宣布实现“量子霸权”;node.js 13.0.3 发布 | 极客头条...
  2. 这本Python书被封年度神作!程序员:比女友强太多!
  3. 程序员的技术负债怎么还?
  4. 2018 年,JavaScript 都经历了什么?
  5. 开源项目的所有者去世了怎么办?
  6. 挣扎 7 年,苹果 Siri 还是被“抛弃”了
  7. 如果美图可以把妹,如何用技术手段做一个会拍照的程序员?
  8. DB与ES混合应用之数据实时同步
  9. 两平面平行但不重合的条件是_____2012江苏省数学竞赛《提优教程》教案:第77讲_组合几何...
  10. java socket 中文乱码_java-Socket接受中文乱码的解决 | 学步园