使用 Zerotier 组建虚拟局域网实现内网穿透

首先 Zerotier 是什么呢? Zerotier 是一拨人为了解决网络复杂,以及因特网过于中心化而提出的解决方案。他们使用点对点 的网络,并且在上面附加一层 VXLAN-like 虚拟网络层 来提高局域网的可见性,安全性。

所以简单来说,Zerotier 通过创建一个虚拟网络,把你的设备添加到这个网络,那么在这个网络内,你的设备就像在同一个路由器内那样可以使用各种端口。

免费版 Zerotier 支持局域网内 100 个设备。Zerotier 支持 Windows、macOS、Linux 三大桌面平台,iOS、Android 两大移动平台,QNAP(威连通)、Synology(群晖)、Western Digital MyCloud NAS(西部数据)三个 NAS 平台,还支持 OpenWrt/LEDE 开源路由器项目。

官网地址:

使用

  • 注册 ZeroTier
  • 创建 Network 私有局域网,得到一个 16 位的 Network ID
  • 在需要加入虚拟局域网的设备上安装各个平台客户端,设备会生成一个 10 位的 ZeroTier address
  • 在设备上加入刚刚创建的 Network ID zerotier-cli join <network id>(或在网页上添加 ZeroTier address)
  • 在官网 network 页面上找到设备,在设备前勾选,信任设备,获得局域网 IP

Windows 系统用命令 IPconfig,Linux/Unix 用命令 ifconfig,然后会看到一个虚拟网卡,有一个 IP 地址。这个 IP 和在官网看到的 network 下的 IP 是一致的,只有同在该虚拟网络下的机器才能访问。

接下来,你可以设置远程桌面(端口号 3389),或者 FTP 服务(端口 21),或者搭建内网网站(端口 80),各种玩法都可以尝试咯。

图文教程可以参考这篇文章

对比

和 ngrok 和 frp 功能类似,但是 ZeroTier 明显入手简单。ZeroTier 只有客户端开源,服务端并没有开源。而 ngrok 和 frp 都是完全开源。但是 ngrok 和 frp 严重依赖于公网固定 IP 的 VPS,是一个中性化的内网穿透工具,一旦中心挂掉,那么所有的节点都无法工作。Zerotier 帮助用户实现了服务端,虽然安全性有待考验,但至少还是能用状态。

另外很多人提到的 N2N 开上去也不错,不过我还没怎么用过。等以后尝试过后再补上。

建立中转服务器 moon

Zerotier 的官方服务器在国外,国内客户端使用时延迟较大,网络高峰期时甚至各个客户端节点之间访问不了。此时,“自定义根服务器”,又称 moon 中转服务器就显得非常重要,它的主要功能是通过自定义的服务器作为跳板加速内网机器之间的互相访问。

Zerotier 定义了几个专业名词:

  • PLANET 行星服务器,Zerotier 各地的根服务器,有日本、新加坡等地
  • moon 卫星级服务器,用户自建的私有根服务器,起到中转加速的作用
  • LEAF 相当于各个枝叶,就是每台连接到该网络的机器节点。

在使用 zerotier-cli listpeers 命令时能看到这几个名词。充当 moon 的机子最好有公网 IP,现在我们尝试用 qnap 搭建一个 moon 中转:

获取 moon.json 文件

zerotier 安装好之后会带有 zerotier-idtool 这个命令,之后的内容需要依赖该命令。假设现在有一台公网固定 IP 的 VPS,在上面安装完 Zerotier 之后。

cd /var/lib/zerotier-one
zerotier-idtool initmoon identity.public > moon.json

获得 moon.json 文件。查看文件内容,其中 id 为 VPS 的 Zerotier ID。 vi 编辑 moon.json,修改 “stableEndpoints” 为 VPS 的公网的 IP,以 IPv4 为例:

"stableEndpoints": [ "8.8.8.8/9993" ]

8.8.8.8 为公网 IP,9993 为 Zerotier 默认端口。

另外,记录下 json 中的 id 值,是一个 10 位的字符串。

生成签名文件

用到上一步中的 moon.json, 执行

zerotier-idtool genmoon moon.json

执行之后生成 000000xxxx.moon 文件。

将 moon 节点加入网络

在 VPS 的 Zerotier 安装目录下(/var/lib/zerotier-one)建立文件夹 moons.d,将生成的 .moon 文件拷贝进去。

重启 zerotier,重启电脑。至此,VPS 上(moon 服务器)配置完成。

其他客户端机器连接 moon 节点

其他虚拟局域网中的机器想要连接到 moon 节点的话有两种方法,第一种方法就是使用 zerotier-cli orbit 命令。连接 moon 节点方法一,使用之前步骤中 moon.json 文件中的 id 值 (10 位的字符串)

分别在客户端机器里执行:

zerotier-cli orbit <id> <id>

完成

第二种方法是需要在 /var/lib/zerotier-one 目录下新建 moons.d 文件夹和 moon 节点一样,将 000000xxxx.moon 文件放到其中,并重启 zerotier。

测试

zerotier-cli listpeers

如果有 moon 服务器 IP 地址的那一行后面有 moon 字样,证明 moon 节点已经被本机连接。

不同系统下的 ZeroTier 目录位置:

  • Windows: C:\ProgramData\ZeroTier\One
  • Macintosh: /Library/Application\ Support/ZeroTier/One)
  • Linux: /var/lib/zerotier-one
  • FreeBSD/OpenBSD: /var/db/zerotier-one

2018-06-14 zerotier , linux , nas , network , virtual-networks

Celery 又一坑:时区错误

Celery 使用过程中又遇一坑,最近升级项目中使用的 Celery 到 4.1.1,突然发现一些定时任务突然不执行了。开始还以为代码哪里做了变化,尝试找了很久,然后打开 scheduler 的日志观察了一段时间。

Celery config 中的配置是如下所示,理论上,早间的任务应该在 8 点到 12 点 每隔 5 分钟执行一次,然后午间和晚间的以此类推。

app.conf.timezone = 'Asia/Shanghai'

app.conf.beat_schedule = {
    'morning': {
        'task': 'worker.xxx.get_xxx',
        'schedule': crontab(minute='*/5', hour='8-12'),
        'args': ('早间',),
        'options': {
            'queue': 'xxx'
        }
    },
    'afternoon': {
        'task': 'worker.xxx.get_xxx',
        'schedule': crontab(minute='*/5', hour='12-18'),
        'args': ('午间',),
        'options': {
            'queue': 'xxx'
        }
    },
    'evening': {
        'task': 'worker.xxx.get_xxx',
        'schedule': crontab(minute='*/5', hour='19-21'),
        'args': ('晚间',),
        'options': {
            'queue': 'xxx'
        }
    },
}

但是观察日志发现,即使现在是下午 4 点,但是上午的任务依然在跑,这个时候突然想起来,是不是因为版本升级导致,果不然,一查 GitHub 就有人反馈这个问题,目前解决方法很简单,为了恢复可用状态,将 celery 回滚到了 4.0.2 版本。暂时没有发现问题。

看 issue 已经意识到该问题,希望能在 4.2 版本中修复吧,有时间的话我再看看他的源码。


2018-06-13 celery , python , linux , message , queue , bug

使用 lombok 简化 Java 代码

lombok 在编译器编译时通过操作 AST(抽象语法树)改变字节码生成。也就是说他可以改变 Java 语法。lombok 不像 Spring 的依赖注入是运行时的特性,而是编译时的特性。

安装配置

官网地址:https://projectlombok.org/

添加 maven

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
</dependency>

最新的版本号,可以在官网或者 mvnrepository 找到。 如果使用 Intellij IDEA 还需要安装一个插件。

使用

Data 注解

类注解

import lombok.Data;

@Data
public class Thing {
    private Long id;
    private String desc;
}

通过添加注解 @Data 可以给类快速添加 get 和 set 方法,toString 方法等等。 @Data 注解其实是 @ToString@Getter@SetterRequiredArgsConstructor@EqualsAndHashCode 注解的缩写。

其实等效于

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@ToString
@RequiredArgsConstructor
@EqualsAndHashCode
public class Thing {
    @Setter @Getter private Long id;
    @Setter @Getter private String desc;
}

相关的注解:

  • @Getter/@Setter:用在属性上,再也不用自己手写 setter 和 getter 方法了,还可以指定访问范围
  • @ToString:用在类上,可以自动覆写 toString 方法,当然还可以加其他参数,例如 @ToString(exclude=”id”) 排除 id 属性,或者 @ToString(callSuper=true, includeFieldNames=true) 调用父类的 toString 方法,包含所有属性
  • @EqualsAndHashCode:用在类上,自动生成 equals 方法和 hashCode 方法
  • @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用在类上,自动生成无参构造和使用所有参数的构造函数以及把所有 @NonNull 属性作为参数的构造函数,如果指定 staticName = “of”参数,同时还会生成一个返回类对象的静态工厂方法,比使用构造函数方便很多
  • @Data:注解在类上,相当于同时使用了@ToString@EqualsAndHashCode@Getter@Setter@RequiredArgsConstrutor这些注解,对于 POJO 类十分有用
  • @Value:用在类上,是 @Data 的不可变形式,相当于为属性添加 final 声明,只提供 getter 方法,而不提供 setter 方法
  • @NonNull 用在方法参数上,该变量不能为空,否则就抛出异常
  • @Builder:用在类、构造器、方法上,为你提供复杂的 builder APIs,让你可以像如下方式一样调用 Person.builder().name(“Adam Savage”).city(“San Francisco”).job(“Mythbusters”).job(“Unchained Reaction”).build(); 更多说明参考 Builder
  • @SneakyThrows:自动抛受检异常,而无需显式在方法上使用 throws 语句
  • @Synchronized:用在方法上,将方法声明为同步的,并自动加锁,而锁对象是一个私有的属性 $lock 或 $LOCK,而 java 中的 synchronized 关键字锁对象是 this,锁在 this 或者自己的类对象上存在副作用,就是你不能阻止非受控代码去锁 this 或者类对象,这可能会导致竞争条件或者其它线程错误
  • @Getter(lazy=true):可以替代经典的 Double Check Lock 样板代码
  • @Log:根据不同的注解生成不同类型的 log 对象,但是实例名称都是 log,有六种可选实现类

    @CommonsLog Creates log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); @Log Creates log = java.util.logging.Logger.getLogger(LogExample.class.getName()); @Log4j Creates log = org.apache.log4j.Logger.getLogger(LogExample.class); @Log4j2 Creates log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); @Slf4j Creates log = org.slf4j.LoggerFactory.getLogger(LogExample.class); @XSlf4j Creates log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

Slf4j 注解

正如上面所例举,lombok 可以使用的日志框架有很多,拿最常见的 @Slf4j 来举例

@Slf4j
public class A {
    public A() {
        log.info("I'm sectionVO : {}", toString());
    }
}

编译会自动生成

private static final Logger log = LoggerFactory.getLogger(A.class);

Builder 注解

如果在类上使用 @Builder 注解,则会给该类直接生成 builder 模式,然后可以使用 builder() 方法返回的 builder 来构造类。

如果要对 Builder 中的值赋予默认值,有两种方式,比如对于

@Builder
public class Person {
  private String firstname = "John";
  private String lastname = "Doe";
}

第一种方式就是手动编写一个静态内部类

@Builder
public class Person {
  private String firstname;
  private String lastname;
  private String middleName;

  public static class PersonBuilder {
    private String firstname = "John";
    private String lastname = "Doe";
  }
}

或者在 v1.16.16 之后添加的新功能

@Builder
public class Person {
  @Builder.Default private String firstname = "John";
  @Builder.Default private String lastname = "Doe";
  private String middleName;
}

其他的特性可以在这里看到:https://projectlombok.org/features/all


2018-06-11 lombok , java

Kafka 副本备份机制

leader 维护了 ISR(能完全赶得上 leader 的副本集), 每个 Partition 当前的 leader 和 ISR 信息会记录在 ZooKeeper 中。leader 会跟踪与其保持同步的 Replica 列表,该列表称为 ISR。如果一个 follower 宕机,或者落后太多,leader 将把它从 ISR 中移除。只有 leader 才能知道哪些 Replica 能够及时完全赶得上。所有 follower 都会和 leader 通信获取最新的消息。但是 follower 之间并不互相知道彼此的信息。所以由 leader 来管理 ISR 最合适了。leader 还可以决定移除落后太多的 Replicas.

每个 Replica 都在自己的 local log 中存储消息,并在日志中维护了重要的 offset 位置信息。LEO 代表了日志的最新的偏移量,HW 是最近提交消息的偏移量。

每个日志都会定时地同步到磁盘。在 flushed offset 之前的数据一定能保存成功持久化到磁盘上。flush offset 可以在 HW 之前或者之后(因为 follower 只是先写到内存中然后返回 ack 给 leader,hw 增加时, follower 在内存中的消息不一定什么时候写到磁盘上,即可能在 hw 增加前就写到磁盘,或者等 hw 增加后才写到磁盘)。

leader 也会定时地将 HW 广播给所有的 followers. 广播消息可以附加在从 follower 过来的 fetch 请求的结果中。同时,每个副本(不管是 leader 还是 follower) 也会定时地将 HW 持久化到自己的磁盘上。当 follower 向 leader 提交 fetch 请求时,leader 也会告诉所有的 follower 说,我现在的 hw 是多少了。这是一种保护机制。 假设只有 leader 一个人保护了 hw 这个重要的信息,一旦 leader 不幸挂掉了,就没有人知道 hw 现在到底是多少了。所以只要一有 follower 过来获取消息时,leader 就不厌其烦地像个老太婆不断地唠叨说我这一次的 hw 更新到了哪里。每个 follower 也就都会知道 leader 的最新 hw. 这样即使 leader 挂掉了,hw 仍然在其他 follower 上都备份有这个重要信息。几个 follower 在一阵商量后,选举出了新的 leader, 这些人都知道上一个 leader 最新的 hw, 因此 hw 会继续传承下去。

为了简单起见,只有 leader 可以提供读消息的服务。并且最多只到 hw 位置的消息才会暴露给客户端。

Producer 在发布消息到某个 Partition 时会经过如下的步骤:

  • 先通过 Zookeeper 找到该 Partition 的 leader, 然后无论该 Topic 的 Replication Factor 为多少(也即该 Partition 有多少个 Replica),Producer 只将该消息发送到该 Partition 的 leader。
  • leader 会将该消息写入其本地 Log, 每个 follower 都从 leader pull 数据。这种方式上,follower 存储的数据顺序与 leader 保持一致。follower 在收到该消息并写入其 Log 后,向 leader 发送 ACK。
  • 一旦 leader 收到了 ISR 中的所有 Replica 的 ACK,该消息就被认为已经 commit 了,leader 将增加 HW 并且向 Producer 发送 ACK。为了提高性能,每个 follower 在接收到数据后就立马向 leader 发送 ACK,而非等到数据写入 Log 中。

因此,对于已经 commit 的消息,Kafka 只能保证它被存于多个 Replica 的内存中,而不能保证它们被持久化到磁盘中, 也就不能完全保证异常发生后该条消息一定能被 Consumer 消费。但考虑到这种场景非常少见,可以认为这种方式在性能和数据持久化上做了一个比较好的平衡。在将来的版本中,Kafka 会考虑提供更高的持久性。Consumer 读消息也是从 leader 读取,只有被 commit 过的消息(offset 低于 HW 的消息)才会暴露给 Consumer。 Kafka 的复制机制既不是完全的同步复制,也不是单纯的异步复制。事实上,同步复制要求所有能工作的 follower 都复制完,这条消息才会被认为 commit,这种复制方式极大的影响了吞吐率。而异步复制方式下,follower 异步的从 leader 复制数据,数据只要被 leader 写入 log 就被认为已经 commit,这种情况下如果 follower 都复制完都落后于 leader,而如果 leader 突然宕机,则会丢失数据。而 Kafka 的这种使用 ISR 的方式则很好的均衡了确保数据不丢失以及吞吐率。follower 可以批量的从 leader 复制数据,这样极大的提高复制性能(批量写磁盘),极大减少了 follower 与 leader 的差距。

如果 follower 失败了,在超过一定时间后,leader 会将这个失败的 follower (follower 没有发送 fetch 请求)从 ISR 中移除。由于 ISR 保存的是所有全部赶得上 leader 的 follower replicas, 失败的 follower 肯定是赶不上了。虽然 ISR 现在少了一个,但是并不会引起的数据的丢失,ISR 中剩余的 replicas 会继续同步数据(只要 ISR 中有一个 follower, 就不会丢失数据)(注意:这里讨论的是一个 Partition 的 follower 副本,而不是节点,如果是一个节点,它不止存储一个 Partition, 而且不都是 follower)

如果失败的 follower 恢复过来,它首先将自己的日志截断到上次 checkpointed 时刻的 HW. 因为 checkpoint 记录的是所有 Partition 的 hw offset. 当 follower 失败时,checkpoint 中关于这个 Partition 的 HW 就不会再更新了。而这个时候存储的 HW 信息和 follower partition replica 的 offset 并不一定是一致的。比如这个 follower 获取消息比较快, 但是 ISR 中有其他 follower 复制消息比较慢,这样 leader 并不会很快地更新 HW, 这个快的 follower 的 hw 也不会更新 (leader 广播 hw 给 follower) 这种情况下,这个 follower 日志的 offset 是比 hw 要大的。

所以在它恢复之后,要将比 hw 多的部分截掉,然后继续从 leader 拉取消息(跟平时一样). 实际上,ISR 中的每个 follower 日志的 offset 一定是比 hw 大的。因为只有 ISR 中所有 follower 都复制完消息,leader 才会增加 hw。也就是说有可能有些 follower 复制完了,而有些 follower 还没有复制完,那么 hw 是不会增加的,复制完的 follower 的 offset 就比 hw 要大。

一个消费者组可以有多个消费者,Kafka 中的一个 Partition 只会被消费者组中的一个消费者消费,但可以被多个消费组同时消费。

对于多个 partition 和多个 consumer 有以下这样的限制条件:

  • 如果 consumer 比 partition 多,是浪费,因为 kafka 的设计是在一个 partition 上是不允许并发的,所以 consumer 数不要大于 partition 数
  • 如果 consumer 比 partition 少,一个 consumer 会对应于多个 partitions,这里主要合理分配 consumer 数和 partition 数,否则会导致 partition 里面的数据被取的不均匀。最好 partiton 数目是 consumer 数目的整数倍,所以 partition 数目很重要,比如取 24,就很容易设定 consumer 数目
  • 如果 consumer 从多个 partition 读到数据,不保证数据间的顺序性,kafka 只保证在一个 partition 上数据是有序的,但多个 partition,根据你读的顺序会有不同

增减 consumer,broker,partition 会导致 rebalance,所以 rebalance 后 consumer 对应的 partition 会发生变化。High-level 接口中获取不到数据的时候是会 block 住消费者线程的


2018-06-04 kafka , message , broker , replication

Kafka 资料收集整理

学 Kafka 的时候找到了一些非常友好的资料,这里整理下。

教程

不得不说的官方教程,另外我正在翻译官方教程

cloudurable 这个网站提供了非常详细的 Kafka 教程,从入门 Kafka 是什么,到写 Java 代码,到 Kafka 项目各个部分架构 都有着非常详细的介绍。

第三个要推荐的就是一本 Gitbook ,尤其是第二章使用 Unix 管道类比来解释 Kafka 的工作流,非常的生动。

常用 Shell 命令

Kafka 提供了一些命令行工具,用于管理集群的变更。这些工具使用 Java 类实现,Kafka 提供了一些脚本来调用这些 Java 类。不过,它们只提供了一些基本的功能,无怯完成那些复杂的操作 。

创建主题

主题名字可以包含字母、数字、下划线以及英文状态下的破折号和句号。

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 3 --topic my-example-topic

主题名字的开头部分包含两个下划线是合法的,但不建议这么做。具有这种格式的主题一般是集群的内部主题(比如 __consumer_offsets 主题用于保存消费者群组的偏移量)。也不建议在单个集群里使用英文状态下的句号和下划线来命名,因为主题的名字会被用在度量指标上,句号会被替换成下划线 (比如 topic.1 会变成topic_1 )。

描述主题

查看某个 Topic 详情

bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-example-topic

列出主题

bin/kafka-topics.sh --list --zookeeper localhost:2181

删除主题

bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic my-example-topic

增加分区

bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic my-example-topic --partitions 16

Kafka 目前是暂时不支持减少主题分区数量的。

无顺序,一行一个

修改分区

bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic my_topic_name  --partitions 40

Partition 个数只能增加,不能减少。对于采用默认 Partitioner 的 Producer,Message 是按照 Key 的哈希值“规律”分布的(hash(key) % number_of_partitions),如果增加 Partition 个数,会打破现有分布规律。如果业务依赖于此哈希分布,请谨慎操作。

增加或修改 Config

bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic my_topic_name --config x=y

可配置的 config 可以参考官网

删除 Config

bin/kafka-topics.sh --zookeeper localhost:2181 --alter --topic my_topic_name --deleteConfig x

启动生产者发送消息

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-example-topic

启动消费者接受消息

bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning

指定 from-beginning 则从“最老”(最早、最开始)的数据开始读;否则从“最新”的数据开始读(启动后等待新数据的写入并读取)。

Kafka 管理

yahoo 开源了一个 kafka-manager GitHub


2018-05-31 kafka , message

kafka 基础知识笔记

Kakfa 起初是由 LinkedIn 公司开发的一个分布式的消息系统,后成为 Apache 的一部分,它使用 Scala 编写,以可水平扩展和高吞吐率而被广泛使用。目前越来越多的开源分布式处理系统如 Cloudera、Apache Storm、Spark 等都支持与 Kafka 集成。

体系架构

生产者使用 push 向 broker 发送消息,消费者使用 pull 模式拉取并消费消息

  • producer 可能是服务器日志,业务数据
  • broker 消息中间件节点,支持水平扩展,一般 broker 数量越多,集群吞吐率越高
  • consumer
  • zookeeper 集群, Kafka 依赖 zookeeper 管理

存储策略

Kafka 中消息是以 topic 进行分类的,生产者通过 topic 向 Kafka broker 发送消息,消费者通过 topic 读取数据。topic 在物理层面以 partition 为分组,partition 还可以细分为 segment。

假设 Kafka 消息文件存储目录

log.dirs=/tmp/kafka-logs

假设 partition 数量为 4

/bin/kafka-topics.sh –create –zookeeper localhost:2181 –partitions 4 –topic mytopic –replication-factor 4

然后就能在 /tmp/kafka-logs 目录中看到 4 个目录。

在 Kafka 文件存储中,同一个 topic 下有多个不同的 partition,每个 partiton 为一个目录,partition 的名称规则为:topic 名称 + 有序序号,第一个序号从 0 开始计,partition 是实际物理上的概念,而 topic 是逻辑上的概念。

每个 partition(目录)相当于一个巨型文件被平均分配到多个大小相等的 segment(段)数据文件中(每个 segment 文件中消息数量不一定相等)这种特性也方便 old segment 的删除,即方便已被消费的消息的清理,提高磁盘的利用率。每个 partition 只需要支持顺序读写就行,segment 的文件生命周期由服务端配置参数(log.segment.bytes,log.roll.{ms,hours}等若干参数)决定。

segment 文件由两部分组成,分别为.index文件和.log文件,分别表示为 segment 索引文件和数据文件。这两个文件的命令规则为:partition 全局的第一个 segment 从 0 开始,后续每个 segment 文件名为上一个 segment 文件最后一条消息的 offset 值,数值大小为 64 位,20 位数字字符长度,没有数字用 0 填充。.index 索引文件存储大量的元数据,.log 数据文件存储大量的消息,索引文件中的元数据指向对应数据文件中 message 的物理偏移地址。

每条消息都具有固定的物理结构,包括:offset(8 Bytes)、消息体的大小(4 Bytes)、crc32(4 Bytes)、magic(1 Byte)、attributes(1 Byte)、key length(4 Bytes)、key(K Bytes)、payload(N Bytes) 等等字段,

topic vs partition

一类 topic 可以认为一类消息,每个 topic 会被分成多个 partition,每个 partition 使用 append log 文件存储。

在创建 topic 时可以在 $KAFKA_HOME/config/server.properties 中指定 partition 的数量,也可以在 topic 创建之后去修改 partition 的数量。

# The default number of log partitions per topic. More partitions allow greater
# parallelism for consumption, but this will also result in more files across
# the brokers.
num.partitions=4

broker 重要配置

Kafka 有非常多的参数可以控制其工作和运行,大部分情况下默认值就可以,但是要用到生产上,就需要自定义一些参数来适应不同的环境。broker 中有一些非常重要的参数,这里列举一些,关于 broker 所有其他的参数可以参考 Kafka 的官方文档。

broker.id

每个 kafka broker 都需要有一个整型的唯一标识,这个标识通过 broker.id 来设置。默认的情况下,这个数字是 0, 但是它可以设置成任何值。需要注意的是,需要保证集群中这个 id 是唯一的。这个值是可以任意填写的,并且可以在必要的时候从 broker 集群中删除。比较好的做法是使用主机名相关的标识来做为 id, 比如,你的主机名当中有数字相关的信息,如 hosts1.example.com,host2.example.com, 那么这个数字就可以用来作为 broker.id 的值。

port

默认启动 kafka 时,监听的是 TCP 的 9092 端口,端口号可以被任意修改。如果端口号设置为小于 1024, 那么 kafka 需要以 root 身份启动。但是并不推荐以 root 身份启动。

zookeeper.connect

这个参数指定了 Zookeeper 所在的地址,它存储了 broker 的元信息。在前一章节的例子中,Zookeeper 是运行在本机的 2181 端口上,因此这个值被设置成 localhost:2181。这个值可以通过分号设置多个值,每个值的格式都是 hostname:port/path, 其中每个部分的含义如下:

  • hostname 是 zookeeper 服务器的主机名或者 ip 地址
  • port 是服务器监听连接的端口号
  • /path 是 kafka 在 zookeeper 上的根目录。如果缺省,会使用根目录

log.dirs

这个参数用于配置 Kafka 保存数据的位置,Kafka 中所有的消息都会存在这个目录下。可以通过逗号来指定多个目录,kafka 会根据最少被使用的原则选择目录分配新的 parition。注意 kafka 在分配 parition 的时候选择的规则不是按照磁盘的空间大小来定的,而是分配的 parition 的个数多小。

num.recovery.thread.per.data.dir

kafka 可以配置一个线程池,线程池的使用场景如下:

  • 当正常启动的时候,开启每个 parition 的文档块 segment
  • 当失败后重启时,检查 parition 的文档块
  • 当关闭 kafka 的时候,清除关闭文档块

默认,每个目录只有一个线程。最好是设置多个线程数,这样在服务器启动或者关闭的时候,都可以并行的进行操作。尤其是当非正常停机后,重启时,如果有大量的分区数,那么启动 broker 将会花费大量的时间。注意,这个参数是针对每个目录的。比如,num.recovery.threads.per.data.dir 设置为 8, 如果有 3 个 log.dirs 路径,那么一共会有 24 个线程。

auto.create.topics.enable

在下面场景中,按照默认的配置,如果还没有创建 topic,kafka 会在 broker 上自动创建 topic:

  • 当 producer 向一个 topic 中写入消息时
  • 当 cosumer 开始从某个 topic 中读取数据时
  • 当任何的客户端请求某个 topic 的信息时

在很多场景下,这都会引发莫名其妙的问题。尤其是没有什么办法判断某个 topic 是否存在,因为任何请求都会创建该 topic。如果你想严格的控制 topic 的创建,那么可以设置 auto.create.topics.enable 为 false。

num.partitions

这个参数用于配置新创建的 topic 有多少个分区,默认是 1 个。注意 partition 的个数只可以被增加,不能被减少。这就意味着如果想要减少主题的分区数,那么就需要重新创建 topic。

Kafka 通过分区来对 topic 进行扩展,因此需要使用分区的个数来做负载均衡,如果新增了 broker, 那么就会引发重新负载分配。这并不意味着所有的主题的分区数都需要大于 broker 的数量,因为 kafka 是支持多个主题的,其他的主题会使用其余的 broker。需要注意的是,如果消息的吞吐量很高,那么可以通过设置一个比较大的分区数,来分摊压力。

log.retention.ms

用于配置 kafka 中消息保存的时间,也可以使用 log.retention.hours, 默认这个参数是 168 个小时,即一周。另外,还支持 log.retention.minutes 和 log.retention.ms。这三个参数都会控制删除过期数据的时间,推荐还是使用 log.retention.ms。如果多个同时设置,那么会选择最小的那个。

过期时间是通过每个 log 文件的最后修改时间来定的。在正常的集群操作中,这个时间其实就是 log 段文件关闭的时间,它代表了最后一条消息进入这个文件的时间。然而,如果通过管理员工具,在 brokers 之间移动了分区,那么这个时候会被刷新,就不准确了。这就会导致本该过期删除的文件,被继续保留了。

log.retention.bytes

这个参数也是用来配置消息过期的,它会应用到每个分区,比如,你有一个主题,有 8 个分区,并且设置了 log.retention.bytes 为 1G, 那么这个主题总共可以保留 8G 的数据。注意,所有的过期配置都会应用到 patition 粒度,而不是主题粒度。这也意味着,如果增加了主题的分区数,那么主题所能保留的数据也就随之增加。

log.segment.bytes

用来控制 log 段文件的大小,而不是消息的大小。在 kafka 中,所有的消息都会进入 broker, 然后以追加的方式追加到分区当前最新的 segment 段文件中。一旦这个段文件到达了 log.segment.bytes 设置的大小,比如默认的 1G, 这个段文件就会被关闭,然后创建一个新的。一旦这个文件被关闭,就可以理 解成这个文件已经过期了。这个参数设置的越小,那么关闭文件创建文件的操作就会越频繁,这样也会造成大量的磁盘读写的开销。

log.segment.ms

控制段文件关闭的时间,它定义了经过多长时间段文件会被关闭。

message.max.bytes

这个参数用于限制生产者消息的大小,默认是 1000000, 也就是 1M。生产者在发送消息给 broker 的时候,如果出错,会尝试重发;但是如果是因为大小的原因,那生产者是不会重发的。另外,broker 上的消息可以进行压缩,这个参数可以使压缩后的大小,这样能多存储很多消息。需要注意的是,允许发送更大的消息会对性能有很大影响。更大的消息,就意味着 broker 在处理网络连接的时候需要更长的时间,它也会增加磁盘的写操作压力,影响 IO 吞吐量。

Kafka 特点

  • kafka 也被设计为多个消费者去读取任意的单个消息流而不相互影响;同时多个 kafka 消费者也可以选择作为一个组的一部分,来分担一个消息流,确保这整个组,这个消息只被消费一次
  • 基于硬盘的消息保存,消息将按照持久化配置规则存储在硬盘上。这个可以根据每个 topic 进行设置,允许根据不同的消费者的需求不同设置不同消息流的保存时间不同,持久化保存意味着一旦消费者来不及消费或者突然出现流量高峰,而不会有丢失数据的风险。同样也意味着消息可以由 consumer 来负责管理,比如消费消息掉线了一段时间,不需要担心消息会在 producer 上累积或者消 息丢失,consumer 能够从上次停止的地方继续消费
  • 水平扩展能力强,扩展可以在集群正常运行的时候进行,对于整个系统的运作没有影响,集群如果要同时容忍更多的故障的话,可以配置更高的 replication factors
  • 高性能

Kafka 的使用场景

关于该部分,Kafka 官方的入门教程中有非常详细的介绍,包括从最早在 LinkedIn 中为记录用户访问数据设计该系统,到后面日志,消息处理,到流处理等等,Kafka 有着非常广阔的使用场景。

更多关于如何使用命令行启动 Kafka,还有一些基础内容可以参考翻译的 Kafka 中文文档

reference


2018-05-29 kafka , linkedin , message , broker

SSH 端口转发

SSH 全称 Secure Shell,是一种加密的网络传输协议,可以在不安全的网络中为网络服务提供安全的传输环境。

几个 ssh 命令的参数

  • C 压缩数据传输
  • -f 后台登录用户名密码,如果省去该参数 ssh 会一直等待
  • -g 允许打开的端口让远程主机访问
  • -N 不执行shell
  • -T 表示不为该连接分配TTY
  • -p 后接端口

本地端口转发

本地端口转发,就是将发送到本地端口的请求,转发到目标主机端口。

ssh -L 本地网络地址:本地端口:目标主机地址:目标端口 root@<主机地址>
ssh -L localhost:3000:localhost:80 root@<ip>  # 将发送到本地3000端口的请求,转发到远程主机80端口
ssh -L 3000:localhost:80 root@<ip>  # 将发送到本地3000端口的请求,转发到远程主机80端口,省略掉本地地址
ssh -p port -C -f -NT -g -L 3000:localhost:80 root@<ip>

远程端口转发

远程端口转发,就是将发送到远程端口的请求,转发到目标端口,一般用来将公网主机端口请求转发到局域网内机器端口以实现外网访问。

ssh -R 远程地址:远程端口:目标地址:目标端口 root@<主机地址>
ssh -R localhost:3000:localhost:80 root@<ip>  # 将远程主机 80 端口的请求转发到本地3000端口
ssh -p port -C -f -NT -g -R 3000:localhost:80 root@<ip>

动态端口转发

动态端口转发,则是绑定一个本地端口,目标地址,目标端口不固定,目标地址和目标端口由发起的请求决定。只有root才能转发特权,支持 Socks 5 本地。

ssh -D 本地地址:本地端口 root@<主机地址>
ssh -D localhost:5000 root@<ip>
ssh -D 5000 root@<ip>           # 省略 localhost,监听本地5000端口
ssh -p port -C -f -NT -g -D 8080 root@<ip>        # Sock监听本地 8080 端口,任何发给 8080 端口的内容都会被转发到远程主机

reference


2018-05-28 ssh , port , forward , linux

每天学习一个命令:netcat 查看网络信息

nc 全称 netcat,是一个强大的网络工具,可以在网络中对 TCP UDP 读写。

监听本地端口

nc -l -p 80   # 开启本机80端口TCP监听
nc -l -p 80 > /tmp/log

机器上运行该命令,端口80会被认为开饭,可以用来欺骗扫描机

扫描端口

扫描端口

nc -zv host.example.com 22 80 443    # 扫描端口
nc -zv host.example.com 20-30        # 扫描一个范围
nc -zv -w 5 host.example.com 22-443  # -w 表示超时等待5秒

作为简单的Web Server

nc -l 8080 < index.html

客户端请求

curl localhost:8080

reference


2018-05-28 netcat , nc , linux , tcp , udp

如何为视频配上合适的背景音乐

电影电视剧配乐一直都是一门学问,我一直很好奇韩剧或者韩综的配乐,如何能做到用音乐去推进故事,用音乐是暗示结局。但其实本文就是罗列了几个收集音乐的好地方,本不求达到那么专业的水平,只求在日常vlog或者短片中找到合适的BGM。

SoundCloud

SoundCloud 是一个在线音乐分享平台,上面有无数的音乐人作品,但是使用的时候请一定遵照版权。

几个账号

icons8

这是 icons8 推出的免费音乐计划,只有在使用的地方标注来源即可免费使用网站上的任何音乐素材。

YouTube

YouTube 是一个视频分享网站,YouTube 自身的版权做得非常健全,如果你使用了盗版的音乐素材极有可能直接被YouTube Content ID 扫描出来而直接屏蔽,所以谨慎使用版权素材,因此 YouTube 也提供了他的免费音乐库,上面也有非常多的选择。

另外作为数一数二的视频分享网站,YouTube 上面也有无数的音乐人,作曲家自己分享了很多作品,你可以在标明涞源的情况使用他们的音乐,这个就要依靠自己的发觉或者自身的喜好来决定收藏了。

bensound

Bensound 是一个免费的音乐素材网站,有丰富的免费音乐素材

artlist.io

该网站为收费网站, 199 刀 一年,还是很贵的


2018-05-28 music , youtube , soundcloud , sound , bgm , video

搬家整理之术收纳之术

最近要搬家,最初的需求就是快速打包搬走,想要找一些比较好用的搬家神器,搬家神器倒是没找到,不过找到了一些日常收纳整理之术。平时如果注重收纳整理,知道断舍离,其实搬家也远没有那么的痛苦。

可能提到搬家很多人会想到小推车,收纳箱等等用于收纳的物品,当然这些都是日常生活中必不可少的。即使不搬家,在家中使用收纳箱收拾平时不穿的鞋子,衣服等不仅能够防潮,也方便了快速分类整理。下面也不多废话,就直接把我整理过程中觉得比较有用的记录下来。

收纳

想要打包的时候加快速度,那就一定成都上依赖于平时的收纳,我自己平时本没有那么注意收纳,因此在打包时格外的零碎,各种文件,电器,工具分散在房间的各个地方,抽屉,架子,箱子中哪里都是。这可能是我第一要注意的事情,物品分类收纳是平时就需要养成的习惯。说到分类整理,对于我这样常年在外的人来说,收纳箱就必不可少了,虽然可能也有柜子书桌,但是一遇到搬家可能就要挪位置,还不如直接使用收纳箱来整理。

列清单

按照 不常用 - 常用 - 日常用 顺序打包,最后打包日常洗漱用品,杯子、碗筷、枕头、床铺之类

我是这么觉得,如果要出差一周需要打包的东西,比如洗漱用品,换洗衣服,以及必要的电子设备,这些可以放到最后打包,其他剩下的所有东西都可以提前打包好。最后留一个 20 几寸的行李箱装下所有日常装备就行。而其他平时不用的分类整理到收纳箱,或者纸箱中等待运输即可。易碎物品一定要贴上标签,特别注意。

日常用品基本上可以用一个行李箱搞定,剩下的就是平时也会用到的,比如台灯,衣架等等一个礼拜就要用上几次的物品,这类物品可以稍晚一些打包,预留一个比较充裕的纸箱,或者收纳箱,最后在搬家前将这类物品迅速的放到箱中,搬完之后也可以第一时间将这些物品拆出来。

而剩下的不常用的物品,按照物品品类分类打包:

  • 电器,日常大概率使用的电器,比如电磁炉,电饭锅,电水壶,显示器,音响等等可以提前打包好,稍微大一些的电器比如电饭锅等等如果有原包装带泡沫垫是最好的,否则也要做相应的防碰撞措施。
  • 小家电,最好有原包装,没有的话,没用的毛巾衣服就可以派上用场了,用它们或者其他软物报纸等包起来,避免在搬动时碰撞
  • 电源线,所有的家电电源线,能拿下来的,贴好标签装在一个袋子里,不能拿下来的,用胶布贴死在电器上。
  • 被褥,非当季的被褥衣服鞋子等等也可以装入收纳袋,纸箱封存起来,贴上相应的标签方便未来拆箱
  • 衣服,在所有东西里,衣服大概是最好打包的,可以按照存储的地方分类装袋或装箱
  • 鞋子,如果有盒子,可以直接把鞋装在盒子里,用胶布封好,然后用绳子或胶布粘成可以单手拎一次的大小;如果没有盒子,可以用绳子或胶布按双困在一起,放在箱子或袋子里
  • 贵重物品,一般都是要单独携带,以免丢失。数码产品类单反、单反镜头、笔记本电脑等… 证书类:毕业证书、学位证书、各种资格考试证书等等
  • 液体,卫生间和厨房各种洗洁精、洗发水、洗手液等开封的瓶瓶罐罐,扣紧,再用塑料袋封口,加胶带缠一圈,以免散出来
  • 粉状,各种没用完的调料、洗衣粉等开封的袋装物品,都用胶布封好,装箱。在箱子外面贴上”向上”“怕洒”
  • 书籍,等密度高的东西尽量使用小箱子装,或者将书分别放到多个箱底分散重量。放重物的箱子,底部要确实封牢
  • 其他各种不怕摔的零碎可以按照各种方法分类装箱,我习惯于按照平时存放的位置一袋一袋整理,比如抽屉里的东西,按顺序每个抽屉装一袋,拆包时可以就可以直接放回抽屉
  • 食材,一般在搬家前就要尽量减少采购食品原料,如果实在没用完,如米、面、蔬菜等,可以分类装箱,需要的写上“勿压”之类;如果是冰箱里的冷冻食品就只能请家庭成员单独携带了
  • 绿植,如果不太大,可以直接拎过去
  • 编号,打包好了最好编号,可以列个清单大致记一下每个箱子里都有什么,记住箱子和袋子,以免东西丢失而不自知,拆包时找东西也会比较快

对于我,大部分的电器我都留了原始的包装,其中的防碰撞泡沫可以完美的契合物品,除此之外各种零碎物品我都放到抗压的收纳箱中,衣服被褥则放到了收纳袋,然后装到纸箱中。

我个人使用了 50L 的收纳箱,然后有一下规格的收纳袋, 45 * 70 可以放下一个枕头,或者日常春秋外套, 56 * 80 放一些冬季棉衣、羽绒服,80 * 100 放被子等大件。

总结

整理过程中,该丢的一定要丢掉,学会断舍离,然后平时一定要学会分类整理,把规划制定到平时。所以写到这里也就回答了我最初的问题,有没有搬家神器,有,只不过都在平时。


2018-05-26 thought , sort-out , moving , clean-up

电子书

Google+

最近文章

  • 又一个网页文件管理:filebrowser 之前一直使用的是 h5ai,平时也够用,不过 h5ai 是不能上传文件编写文件的,这算是一个问题吧,今天正好看到了 filebrowser,以前叫做 file manager。
  • Linux 下超好用字典 GoldenDict 最近在使用 Linux 版有道的时候发现非常卡,影响正常使用,所以就发现了这个 GoldenDict。以前在 Win 下用过 lingoes 但是无奈只有 Win 版本。
  • Linux 网络配置 昨天升级 Ubuntu ,不知为何将网卡配置覆盖了,导致一时间无法上网,然后看了一些网络配置的文章,感觉自己需要补习一下相关知识,所以有了这篇文章。
  • AES 加密算法 高级加密标准 (AES,Advanced Encryption Standard) 为最常见的对称加密算法。对称加密算法也就是加密和解密用相同的密钥。
  • 使用 alembic 迁移数据库结构 Alembic 是一个处理数据库更改的工具,它利用 SQLAlchemy 来实现形成迁移。 因为 SQLAlchemy 只会在我们使用时根据 metadata create_all 方法来创建缺少的表 ,它不会根据我们对代码的修改而更新数据库表中的列。它也不会自动帮助我们删除表。 Alembic 提供了一种更新 / 删除表,更改列名和添加新约束的方法。因为 Alembic 使用 SQLAlchemy 执行迁移,它们可用于各种后端数据库。