java 对象排重_现代化的 Java (八)——重说对象序列化
事故
昨晚上准备把 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 (八)——重说对象序列化相关推荐
- java中对象的生存期_深入理解Java虚拟机-判断对象是否存活算法与对象引用
我们知道Java中的对象一般存放在堆中,但是总不能让这些对象一直占着内存空间,这些对象最终都会被回收并释放内存,那么我们如何判断对象已经成为垃圾呢?这篇文章会提出两种算法解决这个问题.另外,本文还要谈 ...
- java构造方法的签名_如何在 Java 中构造对象(学习 Java 编程语言 034)
1. 构造器 Java 对象都是在堆中构造的. 先看看 Employee 类的构造器: public class Employee { private String name; private dou ...
- java多线程并行执行命令_深入理解Java多线程与并发框(第④篇)——重排序、屏障指令、as-if-serial规则...
![](http://img.blog.itpub.net/blog/2020/03/24/906435fda570a5e3.png?x-oss-process=style/bb) # 一.重排序 前 ...
- java 创建对象 设置属性_详解Java的对象创建
1. 前言 在<还不清楚怎样面向对象?>和<面向对象再探究>两篇文章中,都介绍了关于面向对象程序设计的概念和特点.其中也涉及到了许多代码,比如: Dog dog = new D ...
- java如何实例化集合_如何在java中实例化一个Queue对象?
Queue是一个接口,这意味着你不能直接构造一个Queue . 最好的select是构造一个已经实现Queue接口的类,如下所示: AbstractQueue , ArrayBlockingQueue ...
- java 防重_如何做一个防重设计
前言 在业务设计中防重设计是一个关键点,以接口设计为例,防重就是防止接口被多次调用而产生脏数据,比如支付订单出现重复支付,所以说防重至关重要,在如何防重之前我们首先看一下是如何出现重复请求的. 何时出 ...
- java 死锁 内存消耗_详解Java中synchronized关键字的死锁和内存占用问题
先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并 ...
- 新手学java 学哪方面_初学者学Java应从哪些方面学习?
原标题:初学者学Java应从哪些方面学习? Java作为应用于网络的最好语言,前景无限看好.然而,就算用Java建造一个不是很烦琐的web应用,也不是件轻松的事情.那么,初学者学Java应从哪些方面学 ...
- java让线程空转_详解Java编程中对线程的中断处理
1. 引言 当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时--都需要通过一个线程去取消另一个线程正在执行的任务.Java没有提供一种安全直接的方法 ...
最新文章
- 精品软件 推荐 硬盘物理序列号修改专家
- 因子分析累计方差贡献率要在多少_R语言进阶之因子分析
- Codeforces Global Round 14 F. Phoenix and Earthquake 思维 + 并查集
- 抓住训练集中真正有用的样本,提升模型整体性能!
- 2019阿里云开年Hi购季云通信分会场全攻略!
- 通过Small Basic把儿子/女儿带入编程的世界
- Kotlin学习笔记28 Flow part2 Flow引入 Flow的执行 取消 构建器 中间操作符 终端操作符 默认执行顺序 上下文相关
- 10万人参加过的公开课(大数据、AI、云计算、5G、物联网),你都学了吗?
- flowable 查询完成的流程_中注协正在调试注册会计师成绩查询系统?
- C++实现LRU(Least-Recently Used)缓存算法
- 程序猿的键盘侠养成:macOS 常用快捷键分享
- 无法打开登录所请求的数据库 xxxx。登录失败。 用户 'NT AUTHORITY\SYSTEM' 登录失败。...
- PHP中根据汉字返回拼音
- [brew]切换brew源
- 2013年春节前订票经历及经验分享
- 记一次重大的生产事故
- YDLIDAR G4雷达的unity使用相关+北阳雷达
- 奇瑞汽车用鸿蒙,奇瑞正式确认:将搭载“华为鸿蒙车机系统”,鸿蒙系统真的要来了...
- MCE | HIV 衣壳蛋白有望成为 HIV 治疗新靶标
- 《时代周刊》2019年度100大最佳发明榜单发布!中国2项上榜
热门文章
- 支付宝招“找茬”程序员,年薪无上限;谷歌宣布实现“量子霸权”;node.js 13.0.3 发布 | 极客头条...
- 这本Python书被封年度神作!程序员:比女友强太多!
- 程序员的技术负债怎么还?
- 2018 年,JavaScript 都经历了什么?
- 开源项目的所有者去世了怎么办?
- 挣扎 7 年,苹果 Siri 还是被“抛弃”了
- 如果美图可以把妹,如何用技术手段做一个会拍照的程序员?
- DB与ES混合应用之数据实时同步
- 两平面平行但不重合的条件是_____2012江苏省数学竞赛《提优教程》教案:第77讲_组合几何...
- java socket 中文乱码_java-Socket接受中文乱码的解决 | 学步园