记录一些三言两语能扯清楚的技术总结

JDK

jdk 1.8的元空间

基本概念

  • 类的元数据,包含类的层级信息,方法数据和方法信息,运行时常量池,已确定的符号引用和虚方法表

在jdk 1.8之前

  • 在过去(自定义类加载器未被‘滥用’时期),类几乎是静态的,因此被看成“永久的”
  • 永久带大小由‑XX:MaxPermSize指定,一旦超过即OOM
  • 永久代的垃圾回收和老年代的垃圾回收是绑定的
  • 永久代中的元数据可能会随着每一次Full GC发生而进行移动
  • 以上两点导致永久带难以调优

jdk 1.8

  • jdk 1.8 去掉永久带,引入了元空间(Metaspace)
  • 简化Full GC以及对以后的并发隔离类元数据等方面进行优化
  • 不会再出现永久带OOM,但是不代表可以忽视自定义类加载器的内存泄露问题

避免重复的信息检索,纪录一些搜了又搜的命令/工具用法

docker

使用daocloud提供的加速器

鉴于国内恶劣的网络环境,使用商业vpn代理时,docker镜像的拉取速度仍然捉急
所以在docker安装完毕的同时,建议配置daocloud提供的加速器1.0(2.0版本只供daocloud云服务的主机里面使用)
步骤很简单,注册,进入加速器页面,点选1.0,根据提示配置即可,网址如下
https://dashboard.daocloud.io/mirror

mac下查看/修改docker的配置文件

docker-machine ssh default
cat(or vim) /var/lib/boot2docker/profile
exit

Intellij IDEA

右键左树发现不能创建Java的类、package等

需标记源码目录:

  1. 选中左侧项目树视图任意一module,按F4(或者 File–Project Structure)
  2. 最左边选Modules, 中间选择对应project(module), 右侧选Path页签
  3. 选源码目录,单击上方的Mark as: Sources(蓝色的)
    另:代码目录标蓝之后,会自动加入classpath;一般java项目标src,maven结构的项目往往标记src/main/java的java目录

注释处理

有些类库比如LomBok, 需要开启Intellij IDEA的注释处理才能顺滑使用,否则编译、构建时会出问题。
Mac下特别注意,需同时开启这两个位置:

  1. Intellij Idea -> Preferences -> Compiler -> Annotation Processors
  2. File -> Other Settings -> Default Settings -> Compiler -> Annotation Processors

Linux

查看 jdk/gradle/mysql 等等等的安装位置

可以使用 which 命令,查看可执行文件的位置

1
2
3
4
5
6
which java
# 结果可能是符号链接,如 /usr/bin/java

ls -l `which java`
# 输出 /usr/bin/java -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java
# 当然如果还指向符号链接,递归执行下去即可

本文翻译自Quora一则提问, 原文链接: https://www.quora.com/What-is-the-actual-role-of-ZooKeeper-in-Kafka

要理解Kafka内部是如何使用ZooKeeper,我们首先要了解ZooKeeper。下面我将尝试直观的解释ZooKeeper如何工作。我将尽我所能,避免使用技术用语,并保持简单。

1. At a High level

首先,ZooKeeper肯定是一个客户端可以连接到的服务。它提供了一个树状结构(ZooKeeper文档称之为分层命名空间),以便客户端访问。那么,我们为什么需要这棵树?当然是为了存储数据,因此这棵树被称作”data tree”. 在这棵树上,你可以做整套CRUD操作。它采用标准UNIX文件路径表示法。例如,我们使用 /A/B/C 表示C这个zNode节点的路径,其中B是C的父节点,而同理A是B的父节点。
下面是一个例子:

这看起来很像一个UNIX文件系统对吧?下面是若干定义

  1. 每个节点称为zNode
  2. 每个zNode在树中由PATH标示
  3. zNode有两种类型 –persistent(持久节点)ephemeral(临时节点)
  4. 每个zNode里可以存储值数据,zNode可以有子节点
  5. zNodes一旦创建不可以重命名
  6. 可以在zNode上添加/删除WATCH

有人说ZooKeeper的API过于基础,应当基于它做一些Recipes封装。可以看一下由Netflix编写的Curator框架(目前已贡献给Apache),Curator替我们解决了很多问题。

另外,WATCH是一个很有趣的组件,你可以为某个zNode添加WATCH,当某些事件发生的时候会得到通知。比如它可以订阅某个路径上数据的变更。

注: Recipe 词典的意思是食谱,配方,美食菜谱,烹饪法, 延伸用法:某项计划或步骤来取得预先给定的结果。 在计算机领域没有合适的汉语对应,如果把ZooKeeper看成菜的话,recipe就相当于菜谱, 比如麻婆豆腐, 宫保鸡丁。个人认为Recipes在这里指Leader选举、分布式锁、分布式事物等ZooKeeper常见用途的封装。

2. One level deeper

让我们看一下另一个例子,它的目的是基于键值对格式将若干配置信息保存起来,并在整个集群中可用。这些键值对应该持久化于磁盘,并且应该保证高可用、冗余和容错。而ZooKeeper正是为此而生。

如果想尝试一番,现在可以将ZooKeeper安装到本地,进入其bin目录,运行如下命令:

zkCli.sh –server 127.0.0.1:2181

你可以看到一些提示信息,最后如下:

[zk: 127.0.0.1:2181(CONNECTED) 1]

继续尝试如下命令:

一旦zNode被创建,你可以使用GET或SET操作,将其当做分布式KV存储或hashmap来使用。
你也可以安装netflix exhibitor,它是一个基于UI的ZooKeeper管理面板,可以使用它可视化观察ZooKeeper中的数据。

3. Now how Kafka uses ZooKeeper?

Kafka于v0.8版本使用ZooKeeper保存各种配置数据,并以分布式方式在整个集群中访问。观察如下两个简单用例中,Kafka如何将数组组织于ZooKeeper

  1. Topics under a broker -/brokers/topics/[topic]
  2. Next Offset for a Consumer/Topic/Partition combination - /consumers/[groupId]/offsets/[topic]/[partitionId]

本文以adxp-agent节点为例,演示如何基于rhcs创建高可用的服务。假设读者已经搭建好rhcs环境。

配置高可用集群:

打开luci登录界面, 点击create, 创建集群

在下面的五个选项中,“Download packages”表示在线下载并自动安装RHCS软件包,而“Use locally installed packages”表示用本地安装包进行安装。大多数情况下,RHCS组件包在集群搭建时已经手动安装完成,所以这里选择本地安装。

剩下两个复选框分别是启用共享存储支持(Enable Shared Storage Support)、节点加入集群时重启系统(Reboot nodes before joining cluster)这些创建cluster的设置,可选可不选,这里不做任何选择。
新建集群界面

点击create cluster按钮完成集群配置。若创建成功,下图可以浏览集群各节点状态
集群状态

进入Fence Devices页签创建 fence 设备,此处为了演示随意建一个。实际生产环境中,必须使用 fence 硬件设备。(这里根据客户的具体情况决定)
添加Fence Device

以上属于rhcs平台搭建环节,故简述。

Failover Domain是配置集群的失败转移域,通过失败转移域可以将服务和资源的切换限制在指定的节点间。
比如可以指定a001, a002, a003三台机器作为mysql的失败转移域。部署后若某台节点上的mysql出现不可用,就会在这几个节点之间切换。下面具体说明:

下图操作将创建adxp服务的失败转移域,其参数如下:
? Name:创建的失败转移域名称,起一个易记的名字即可。
? Prioritized:是否在Failover domain 中启用域成员优先级设置,这里选择启用。
? Restricted:表示是否在失败转移域成员中启用服务故障切换限制。
? No failback:表示在这个域中使用故障切回功能,也就是说,主节点故障时,备用节点会自动接管主节点服务和资源,当主节点恢复正常时,集群的服务和资源会从备用节点自动切换到主节点。(这里保持默认:不回切)

然后,在Member复选框中,选择加入此域的节点,这里选择的是n62和n61节点,然后在“priority”处设置优先级。越小优先级越高。
所有设置完成,点击Submit按钮。
添加Failover Domain

Resources是集群的核心,主要包含服务脚本、IP地址、文件系统等

  • 对于adxp-monitor, 只需配置一项Resource即可,即monitor自身
  • 对于adxp-agent节点,需要添加agent运行过程中所需的所有服务(共三项,分别为h2数据库,aetl-server与adxp-agent应用)

下图演示创建adxp-agent节点所需的三项Resource。首先是添加aetl资源的配置界面,其参数如下:
? Name:Resource名称
? Full Path to Script File:指向对应脚本的绝对路径,脚本已附带在产品包之中。(各个节点要将产品包部署到同一位置)
添加Resource

除aetl之外,继续向集群中添加adxp-agent所需的h2与agent资源,如下图:
添加Resource

最后也是最终的一步,添加服务组:
新建服务组,引入刚才新建的三个资源(agent, etl, h2)在一个服务组内,可通过添加一个resource(包含两个child resource)的方式。

主要配置项如下:
? Service Name:服务组名称,起一个易记的名字即可。
? Failover Domain:失败转移域,服务与资源将在此域中的节点切换。
? Recovery Policy: 恢复策略,主要有重启(原节点restart尝试)与重分配(切换节点)
添加Service Group

所有服务添加完成后,如果应用程序设置正确,服务将自动启动。在Service Groups中可以看到服务组的启动状态,正常情况下,会显示当前服务运行所在节点。
Service Group状态

高可用性验证测试:

  1. 部署完成后在一个节点A
  2. 登录A节点机器,通过脚本对某服务运行stop命令或者直接kill pid
  3. 观察luci是否能重新拉起应用(restart或relocate到另一机器上)
  4. 另外留意:同一个服务组内,一个应用/服务的挂掉会触发组内所有应用/服务的Recovery行为

这篇文章整理了若干Intellij IDEA 的常用快捷键与相关的简要说明,基于 windows 下Intellij IDEA 14 中default键位进行测试。

导航
Intellij IDEA 备注
快速打开类 Ctrl + N 可以打开类时直接跳转到某行,如String:123
快速打开指定文件或目录 Ctrl + Shift + N 同上,支持简单通配符、驼峰式命名、包名前缀
快速打开指定符号(symbol) Ctrl + Shift + Alt + N 同上,均属于Go to a xxx系列
结构菜单 Ctrl + F12 如.java文件的成员变量与方法
显示最近编辑文件 Ctrl + E -
切换指定的工具窗口 Alt + 数字 记住下面这几个常用的:
Alt + 1 Project窗口
Alt + 6 TODO窗口
Alt + F12 Terminal窗口
跳转至关联的单元测试 Ctrl + Shift + T 如果在使用这一功能的类没有相关联的测试,IntelliJ可以帮你生成一个
文件内导航
Intellij IDEA 备注
最后编辑位置 Ctrl + Shift + Backspace -
高亮显示选中文字 Ctrl + Shift + F7 F3向下;Shift+F3向上
书签操作
Intellij IDEA 备注
增删书签 F11 在当前行添加或删除书签
增删具名书签 Ctrl + F11 在当前行添加或删除书签,可用一位数字或字母命名
书签跳转 Shift + F11 在列表中选择或使用具名书签的编号快速切换
跳转至前/后一个书签 - 需在KeyMap中自定义,建议设置
基本操作
Intellij IDEA 备注
快速切回编辑区 ESC -
快速切换方案 Ctrl + ~ -
还原默认布局 Shift + F12 -
隐藏/恢复所有窗口 Ctrl + Shift + F12 往往与ESC配合使用,快速切回编辑区再隐藏其他窗口
查找/替换
Intellij IDEA 备注
高亮显示选中文字 Ctrl + N 可以打开类时直接跳转到某行
替换文本 Ctrl + R -
全局替换 Ctrl + Shift + R -
查找文本 Ctrl + F -
全局查找 Ctrl + Shift + F -
编辑
Intellij IDEA 备注
显示文档 Ctrl + Q -
显示参数 Ctrl + P -
复制行 Ctrl + D -
格式化代码 Ctrl + Alt + L 可以优化导入包
多行编辑 Alt + Shift + Mouse1 像Sublime Text一样的多行编辑功能
编译/调试
Intellij IDEA 备注
Run last Shift + F10 -
Run… Alt + Shift + F10 在菜单中选择
Debug last Shift + F9 -
Debug… Alt + Shift + F9 在菜单中选择
step over F8 -
step into F7 -
Resume F9 -
step out Shift + F8 -

Kettle于2.4.1-M1版本引入错误处理特性。如下图所示,支持错误处理的组件可以向后拖拽出一条红色的线条。当出现错误数据时不会抛出异常终止step,而是将出错行沿着红线向后发送。

错误处理连线

右击组件选择Define error handling…(或者单击红色错误连线上的叉图标),打开错误处理设置界面:

错误处理配置界面

主要配置项:

  • nrErros: (本轮)发生错误行数,绝大多数组件为1
  • errorDescription: 错误堆栈信息
  • errorField: 错误列名;本例中为空因为在表输出组件实现中,jdbc发生错误时没有办法通过JDBC Driver获取具体的出错数据库列名。其他组件是否有值取决于具体的后台实现。
  • errorCode: 错误码,由组件约定;比如表输出组件的TOP001表示常规数据库错误

Note: 这四个列会被后续组件获取到

组件开发:支持错误处理

  1. 在组件Meta实现类中覆盖基类中的supportsErrorHandling()方法,返回true
  2. 后台逻辑实现

通过一个boolean变量标记组件是否启用错误处理模式(后面连着红线),可在构造方法中初始化

1
boolean sendToErrorRow = getStepMeta().isDoingErrorHandling();

在行处理逻辑附近,catch块中进行putError()的调用

1
2
3
4
5
6
7
8
9
if(sendToErrorRow) {
putError( getInputRowMeta(), r, 1, e.toString(), null, "ExchangeFileInput001" );
} else {
logError( BaseMessages.getString(PKG, "ExchangeFile.ErrorInStepRunning", e.getMessage()) );
setErrors( 1 );
stopAll();
setOutputDone(); // signal end to receiver(s)
return false;
}

参考文档

Matt的博客
Step error handling codes

H2的几种运行方式:

  1. 作为内存数据库使用
    • jdbc url配置:jdbc:h2:mem:
  2. 作为持久化数据库
    • jdbc url配置:jdbc:h2:file:
  3. 以Server方式启动
    • 默认方式(此时数据库端口默认为9092); java -cp h2.jar org.h2.tools.Server
    • 指定数据库端口:java -cp h2.jar org.h2.tools.Server -tcp -tcpPort 6006
    • jdbc url配置:jdbc:h2:tcp://:/mem:
      jdbc:h2:tcp://:/file:

综述:前两种连接为嵌入式模式,只有单进程能访问;第三种可以多进程访问

H2在Kettle/AETL中的数据库连接配置:

1.作为嵌入式数据库使用(内存或磁盘)

数据库名称mem:file:开头,例如 file:E:/workspace3/adxp//db/agentDb
主机名称端口号必须留空

2.作为C/S模式使用

主机名称填ip(一般填localhost),端口号填写端口(AETL启动使用默认端口9092);
数据库名称填写数据库的名称比如logdb

Note:

  • 原AETL使用h2的1.2.131版本,当前升级至最新稳定版1.3.176
  • 自1.4.177版本起,H2的数据库文件默认使用新的MVStore格式,形如 xxx.mv.db; 之前版本的数据库文件为xxx.h2.db;如若禁用,须在jdbc url后面添加 ;MV_STORE=FALSE;MVCC=FALSE

问题

首先确认一下本文要解决的问题:ADXP的文件传输是将文件分为若干小块进行块传输。在传输过程中,可能因为网络问题而发生文件块的丢失、乱序或者错误。当传输完成时,发送端与接收端会对整个文件做一次MD5比对,若MD5不同,说明接收过程中发生了错误。

问题在于这时文件位于网络中两台不同的机器上,无法确认具体是哪些快出现了问题。因此需要使用一个类似rsync的算法,在两边文件见不到面时确认哪些块不同。

校验流程

  • 分块 首先把接收端接收到的文件平均分成若干个小块,比如每块2048个字节(最后一块会小于等于2048字节),然后对每块计算MD5
  • 传输 接收端会将上述MD5列表传送到文件发送端。当发送端在拿到列表时,也会以同样的过程生成这个列表
  • 查找 发送端获得列表后,会把数据存到一个以MD5为key的哈希表中,以便获得O(1)时间复杂度的查找性能
  • 比对 此时比较源列表与目标列表中MD5的差集,便可以知道所有需要重传的文件块。这里用HashSet简单示意如下:

    1
    2
    3
    4
    Set<String> s1 = Sets.newHashSet("md1", "md2", "md3", "md4", "md5");
    Set<String> s2 = Sets.newHashSet("md1", "md2", "md4", "m5d"); // 模拟传输中丢失块3,块5有内容错误
    s1.removeAll(s2);
    System.out.println(Arrays.toString(s1.toArray())); // out: "md3" "md5"

Note: 样例代码有所简化,实际中是将MD5与位置属性(比如文件块首尾位置)结合为一个模型对象,实现equals()和hashcode(); Set中存储这个对象即可

  • 缓存
    在上述伪代码中,调用s1.removeAll(s2)时会修改掉s1本身。考虑到可能的重复校验多次情况,每次都要重新生成源文件的MD5列表会引起额外开销,可以引入缓存进行优化(Guava cache或Ehcache等)。这时需要在比对过程中不修改源端列表s1的内容。这样s1便可以被缓存至传输流程结束。
    1
    2
    3
    4
    5
    Set<String> s1 = Sets.newHashSet("md1", "md2", "md3", "md4", "md5");
    Set<String> s2 = Sets.newHashSet("md1", "md2", "md4", "m5d"); // lost 3 and dismatch 5
    Set<String> result = Sets.difference(s1, s2); // Guava的API返回一个视图,而不是修改集合s1本身
    System.out.println(Arrays.toString(result.toArray())); // out: "md3" "md5"
    System.out.println(Arrays.toString(s1.toArray())); // out: "md1", "md2", "md3", "md4", "md5"

其他相关

在文件传送过程中,我们需要一个用以做标记的数据结构:
它应该对下面操作做到尽可能的高效:

  • 接收到一组文件块,快速做出标记,表示其已收到
  • 快速统计出当前接收到的文件块总数
  • 快速定位到哪个(哪些个)文件块尚未收到

简单思路是,若源端文件被分成比如1000个块,目标端建立一个长度为1000的boolean数据用于标记,接收到文件块时,根据其位置信息快速定位到相应的数组下标,并置为true。这样可以分别以O(1), O(n), O(n)的时间复杂度满足上述三个需求。

以boolean数组为基础,可以进一步的简化:如果能以一串二进制01来标记对应的文件块是否收到,这样可以将内存占用降低到极致。在JDK中正巧有类似的实现java.util.BitSet,不过略微不同的是,BitSet内部实际上以length为 位数>>6 的long数组来存储。另外,用于统计true总数的API cardinality() 由于高效率的位运算实现,遍历次数可以降低为朴素循环遍历的1/64

Reverse digits of an integer.

Example1: x = 123, return 321

Example2: x = -123, return -321


递归处理,代码可以写的很优雅

1
2
3
4
5
6
7
8
9
10
public int reverse(int x) {
boolean neg = x < 0;
if (neg) x = -x;
int result = 0;
while (x != 0) {
result = result * 10 + x % 10;
x /= 10;
}
return (neg ? -1 : 1) * result;
}

Implement atoi to convert a string to an integer.

Hint: Carefully consider all possible input cases. If you want a challenge, please do not see below and ask yourself what are the possible input cases.

Notes: It is intended for this problem to be specified vaguely (ie, no given input specs). You are responsible to gather all the input requirements up front.


很基础且常见的面试题,更多的是考察能否把所有情况考虑周全,注意到以下几点即可AC

  1. 这个字符串是否为空。

  2. 这个字符串是否有非法字符(非0-9之间的字符)。

  3. 这个数是正数或者是负数的情况(第一个字符是否为+,-)。

  4. 是否存在溢出的情况(这个比较难考虑到)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int atoi(String str) {
if (str == null || str.length() == 0) return 0;
boolean hasSymbol = false;
boolean negative = false;
char[] c = str.trim().toCharArray();
if (c[0] == '+' || c[0] == '-') {
hasSymbol = true;
if (c[0] == '-') negative = true;
}

long result = 0;
for (int i = hasSymbol ? 1 : 0; i < c.length; i++) {
if (c[i] < '0' || c[i] > '9') {
break;
}
result = result * 10 + c[i] - ('9' - 9);
}
if (result > 2147483647) {
return negative ? -2147483648 : 2147483647;
} else {
return (int) (negative ? -1 * result : result);
}
}