Kafka 发布消息时如何选择 Partition

本文旨在了解 Kafka 发送消息到有多个 Partition 的 Topic 时如何选择 Partition。或许多数人已经知道 Kafka 默认(当 key 为 null) 时采用 Round-robin 策略,也就是雨露均沾,风水轮流转,实现类是 DefaultPartitioner。但我们实际应用中为保持相关消息按序到,就必须送到指定的 Partition,方法可以有

  1. 指定 Partition 编号
  2. 指定 Key
  3. 自定义 Partitioner - 实现 org.apache.kafka.clients.producer.Partitioner, 并通过属性注册

还应考究当指定了 Key 或 Partition 编号发送消息后,后续消息 key 为 null 会选用哪个 Partition。最后再思考一个问题,Consumer 每次  poll 时是获得的消息列表是否只包含一个 Partition 源还是可以多个 Partiton 源。

为完成本次实验,可以本地搭建一个 Kafka 环境,参考 简单搭建 Apache Kafka 分布式消息系统。待 Zookeeper 和 Kafka 正常启动后,我们用下面的命令创建一个 Partition 数量为 3 的 Topic partition-test

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic partition-test

验证一下该 Topic 的信息 阅读全文 >>

谁说 HTTP GET 就不能通过 Body 来发送数据呢?

当我们被问及 HTTP 的 GET 与 POST 两种请求方式的区别的时候,很多答案是说 GET 的数据须通过 URL 以 Query Parameter 来传送,而 POST 可以通过请求体来发送数据,所以因 URL 的受限,往往 GET 无法发送太多的字符。这个回答好比在启用了 HTTPS 时,GET 请求 URL 中的参数仍然是明文传输的一样。

GET 果真不能通过 Request Body 来传送数据吗?非也。如此想法多半是因循着网页中 form 的 method 属性只有 get 与 post 两种而来。因为把 form 的 method 设置为 post, 表单数据会放在 body 中,而 method 为 get(默认值) 时, 提交时浏览器会把表单中的字符拼接到 action 的 URL 后作为 query parameter 传送。于是乎就有了这么一种假像:HTTP GET 必须通过 URL 的查询参数来发送数据。

其实 HTTP 规范并未规定说 GET 就不能发送 body 数据,在 RFC GET 中只是说

The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI.

只是说 GET 意味着通过 URI 来识别资源。

我也是本着传统上对 GET 与 POST 区别的误解很多年,今天突然意识到 GET 应该可以使用 body, 况且 HTTP 本身是一个纯文本的协议。没有测试就没有 100% 的发言权,所以做了如下的测试 阅读全文 >>

自定义类加载器动态加载 JDBC 驱动

我们可以用自定义的 URLClassLoader 从外部动态加载类,并使用它。但数据库驱动的管理类 DriverManager 却不比较苛刻,不承认非当前应用系统加载器加载的驱动类。见 DriverManager 的 JavaDoc 

When the method getConnection is called, the DriverManager will attempt to locate a suitable driver from amongst those loaded at initialization and those loaded explicitly using the same classloader as the current applet or application

对于有有应用自定义类加载器加载数据库驱动类的需求时,就要对原 Driver 简单包装一下。继续往后会说介绍为什么要这么做。

说明一下,DriverManager 能够根据 JDBC 连接字符串匹配到驱动类,所以一般来说都不需要显式调用 DriverManager.registerDriver() 方法。

先看 DriverManager 在应用外部驱动类时会出现什么情况 阅读全文 >>

再论机械式针对接口编程

两月前论及到 针对接口编程及敏捷编程,如今再一次老调重提。面向对象有一个很重要设计原则:

针对接口编程,而不是针对实现编程

这一原则关键在于理解什么是接口,请参照前文提到的上一篇。因为 Java 语言中有 interface 这个概念,于是 Java 的 interface 便躺枪了,好好的针对接口编程在 Java 中就机械式的变成了针对 interface 的编程。

以致于只要有实现的地方都可能变成像 UserDao.java(接口) 与 UserDaoImpl.java 成对出现,在以后的重构中基本上是为了实现而修改接口,这也进一步违背了针对接口编程的初衷,接口怎么一点也不稳固。

或许有人认为先声明一个 interface,然后我们可能会有多种实现(为将来考虑,不过多数时候只会是单一实现),这很像是 interface 存在的一个理由。可是当我们写下类似 UserDaoImpl 这个样的实现类名时直接宣告了该接口其实就只有一种实现,不然的话再加一个实现的话类名为哪般?UserDaoImpl2? 显然多种实现时 UserDaoImpl 需要重命名了。 阅读全文 >>

Vimer 的福音:Mac 下 Caps + hjkl 作为方向键

本人所用的键盘是 WASD VP3 61 Key 的 -- Poker 3 方案,可以不借助于软件直接键盘上设置为 Caps + hjkl 来作为方向键。Caps 跳线为 Fn, 并选择一个 Layer, 把原本 Fn + ijkl 的方向键组合键重新映射为 Fn + hjkl 即可。

办公室的 61 键的键盘借于朋友体验,所以又重新把 DAS 87 键摆了出来。由于习惯了 61 键的 Caps + hjkl 的方向键,在 87 键盘上每次按方向键时右手都要离开它的 Home Row,很是不爽。别说是 87 键,即便是有些键盘把方向键压缩在右 Shift 下方,都无法接受右手右下移去按方向键。

Caps 键没什么卵用(只会用 Caps 来输入大写字母的当我没说),却常年占据着黄金位置(左手掌无需移动就能按到),所以主意是如何把 Caps 另作他们,对于 Vimer 来说最理想的方向键是 Caps + hjkl。

Mac 下有个键盘映射的工具 Karabiner, 先前的版本 KeyRemap4MacBook 只支持到 Mac OS 10.8, 而 Karabiner 也只能支持 10.9 到 10.11。而我的 Mac 系统是 macOS Sierra 10.12, 有一个不完全特性的 Karabiner 叫做 Karabiner-Elements, 它不能像 KeyRemap4MacBook 和 Karabiner 那样支持直接组合键的映射。因此在 macOS Sierra 一直未能把方向键映射成某个功能键(如 Caps) + hjkl,不得不用物理方向键。

有两种方法把 Caps + hjkl 映射为方向键

阅读全文 >>

函数定义 Kotlin V.S. Scala

关注 Kotlin 已有段时日了,真是因为 Google 把它扶正而跑来跟风。因为进行想在 Java 与 Scala 间找一个折中的编程语言,也就是 Kotlin。这是一篇好几月前列的我 想像中理想编程语言的几个特征,琢磨来去当今也就 Kotlin 比较符合我的口味。很早就想买 《Kotlin IN ACTION》这本书,因那是 Kotlin 1.1 刚出,而出版的书只涵盖到了 Kotlin, 所以未出手。看看再有一本好的那样的书估计也不是一时半会儿,所以今天还是把那本书弄到手了,至于 Kotlin 1.1 后的特性自个去补充。

尽管书中未提及 Kotlin 语言的设计灵感来自于何种语言,  但我的直觉就是与 Scala 太多相似之处,但没有 Scala 简单,并揉合一些 Swift 的特性,因此我在阅读 《Kotlin IN ACTION》时更多的会和 Scala 相比较。

第一个主题是关于 Kotlin 函数的定义与约定。Kotlin 的基本定义格式与 Scala 是类似的

//Kotlin
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

注:Kotlin 也像 Scala 一样,if 不再是一个控制语句,而是一个表达式,所以它是有返回值的。与  Java 有不同的是,Kotlin 的赋值语句是没有返回值的,不能用作 val b = (a = 3), 而 Scala 的赋值语句总是返回 Unit 阅读全文 >>

针对接口编程及敏捷编程

本文发自于对平时编程习惯上的一些个人见解,还不至于牵扯到方法学的层面,尽管如此,也可能会招来许多不同的看法,只要是觉得经世致用就行。首先从耳熟能详的针对接口编程说起

是否总是针对接口编程

在初通软件设计时,针对接口编程这一理念似乎是宇宙真理(软件世界里并没有真理部),而且对它的解释是

具体类包含实现细节,而抽象类则只呈现概念

当然很在理,也很权威。

但针对接口的前提是什么呢?是在设计一个与外部系统交互的 API 情况下。比如要提供一个用户注册接口给外部,可以共同约定好接口为

void register(String username, String password) throws RegistrationException;

  并且这个接口应该是稳固的,然后各自去实现或完成调用细节,即使实现未完成调用端也可以通过 Mock 来进行单元测试。

然而实际中对针对接口编程的理解很容易变为凡是非工具类,数据类都先声明一个接口挂单一实现类,类结构中就类似下面那样 阅读全文 >>

Spring 定时任务(Schedule) 和线程

Spring 定时任务实例

Spring 中使用定时任务很简单,只需要  @EnableScheudling 注解启用即可,并不要求是一个 Spring Mvc 的项目。对于一个 Spring Boot 项目,使用定时任务的简单方式如下:

pom.xml 中

Application.java 阅读全文 >>

使用 avro-tools, jq 查看 Apache Avro 序列化文件

Apache Avro 是类似于 Google protobuf 那样的数据交换协议,但 Avro 可以用 JSON 格式来定义 Schema, 所以相比而言更容易上手。它也是 Hadoop, Kafka 所采用的交换格式。对于生成的 avro 序列化文件如果要编写代码来解读其中内容的话就太过于麻烦,Apache 给了我们一个便捷的工具来处理 Avro Schema 和数据。

Java 版的 Avro Tools 可点击链接 avro-tools-1.8.2.jar 下载,当前版本 1.8.2(发布于 2017/05/20),执行命令是

java -jar avro-tools-1.8.2.jar ..............

如果是 Mac 平台,还可以通过

brew install avro-tools

来安装,执行命令就只是 avro-tools 了。

在本文中还会用到一个 JSON 格式化高亮显示的工具 jq, 在 Mac 下通过以下命令安装

brew install jq

avro-tools 和 jq 已准备就绪,接下来演示下如何使用它们。

avro-tools 能做什么

阅读全文 >>

AWS Lambda 按序处理同一个 Kinesis Shard 中的消息

当 AWS Lambda 由 Kinesis 消息来触发时,一个 Kinesis Shard 会相应启动一个 Lambda 实例,比如说 Kinesis Stream 有 5 个 Shards, 那同时只会启动 5 个 Lambda 实例。那么把多条消息发送到同一个 Kinesis Shard 中去,这些消息会被如何消费呢?答案是按顺消息,不管这些消息是否被不同的 Lambda 实例处理。本文就是关于怎么去理解 https://aws.amazon.com/lambda/faqs/ 的下面那段话的内容:

Q: How does AWS Lambda process data from Amazon Kinesis streams and Amazon DynamoDB Streams?
AWS Lambda 如何处理来自于 Amazon Kinesis 和 DynamoDB 的数据

The Amazon Kinesis and DynamoDB Streams records sent to your AWS Lambda function are strictly serialized, per shard. This means that if you put two records in the same shard, Lambda guarantees that your Lambda function will be successfully invoked with the first record before it is invoked with the second record. If the invocation for one record times out, is throttled, or encounters any other error, Lambda will retry until it succeeds (or the record reaches its 24-hour expiration) before moving on to the next record. The ordering of records across different shards is not guaranteed, and processing of each shard happens in parallel.
从 Kinesis 和 DynamoDB 单个 Shard 上的记录会被 Lambda 严格的按序处理。这意味着如果你送两条记录到相同的 Shard, Lambda 将会保证第一条记录成功处理后才会处理第二条记录。假如处理第一条记录时超时,或超过资源使用上限,或碰到任何错误, Lambda 将会不断重试直到成功(或记录在 24 小时后过期), 而后才会去处理下一条记录。跨 Shard 的记录不保证到达顺序,且是并行处理多个 Shard 来的记录。

可以做几个试验,下面的代码可以保证消息总是被发送到同一个 Kinesis Shard,因为 PartitionKey 参数是一个常量 阅读全文 >>