使用 docker 来构建 spark 集群

Description:
上一博客中介绍了如何在 ubuntu 上面部署 docker 系统,以及 docker 的基本命令; 在本篇博客中将会介绍如何利用 docker-hub 上的资源来快速搭建一个 spark 集群


如何使用 dockerfile 来生成 docker 镜像文件

在后续的步骤中有一个镜像文件是无法直接从 docker-hub 的镜像库中直接下载的,但是可以从 github 上面下载到该镜像文件的 dockerfile 文件。 所以在这里介绍一下如何使用 dockerfile 来创建 docker 镜像文件。 如果今后需要的话,将会详细介绍一下如何根据自己的需要来定制编写自己的 dockerfile .

docker build

用来将指定路径的中 dockerfile 生成 docker 镜像文件

 docker build -t="amplab/apache-hadoop-hdfs-precise:1.2.1" . 

 // 上述命令会搜索当前路径,看是否有 dockerfile 文件,如果有,那么执行该 dockerfile 文件,并根据该 dockerfile 文件生成
 // docker 镜像文件。同时又将该镜像文件打上名为 'amplab/apache-hadoop-hdfs-precise:1.2.1' 的标签
 // 由于后面会通过 github 上面提供的 deploy.sh 脚本来构建 spark 系统,所以尽最大可能的保持 hadoop 的镜像文件的名称一致性 
 

从 docker-hub 中下载镜像文件

首先确保正确登录 docker-hub 账号
其实我安装 spark 的主要目的是为了使用 spark 提供的 GraphX 和 mllib 这两个工具,而 mllib 中在 spark-0.9 之后才支持,
所以,在这里我安装的是 spark-1.0.0 版本
在执行 pull 命令之前,我开启了翻墙的软件,这样可以节省时间

sudo docker pull amplab/apache-hadoop-hdfs-precise:1.2.1   
// 这个镜像在 docker-hub 上面找不到,所以需要根据 github 可以获得的 Dockerfile 来构建其镜像文件

$docker pull amplab/dnsmasq-precise:1.0.0
$docker pull amplab/spark-worker:1.0.0
$docker pull amplab/spark-master:1.0.1
$docker pull amplab/spark-shell:1.0.1

$docker images                // 通过该命令查看系统中的镜像文件

REPOSITORY               TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu_14                wget                44552cea1d79        7 hours ago         187.9 MB
ubuntu                   14.04               8693db7e8a00        3 weeks ago         187.9 MB
amplab/spark-shell       1.0.0               c18acb8d81a0        20 months ago       964.3 MB
amplab/spark-worker      1.0.0               6f77966546ee        20 months ago       964.3 MB
amplab/spark-master      1.0.0               a43b969cfeff        20 months ago       964.3 MB
amplab/dnsmasq-precise   latest              d9cdba2ae123        23 months ago       205.8 MB

从 git-hub 上面下载运行 docker 镜像文件的脚本

$wget https://github.com/amplab/docker-scripts/archive/master.zip
$unzip master.zip

启动 spark 集群

将路径切换到包含 /deploy 文件夹的路径下面

./deploy/deploy.sh -i amplab/spark:1.0.0 -w 3

$docker ps                 // 通过该命令来查看系统中正在运行的容器信息

CONTAINER ID        IMAGE                           COMMAND                CREATED              STATUS              PORTS                NAMES
855415af179d        amplab/spark-shell:1.0.0        "/root/spark_shell_f   About a minute ago   Up About a minute   8888/tcp             cocky_meitner           
5bb8f871e474        amplab/spark-shell:1.0.0        "/root/spark_shell_f   2 minutes ago        Up 2 minutes        8888/tcp             modest_brown            
2c2d49565559        amplab/spark-worker:1.0.0       "/root/spark_worker_   5 minutes ago        Up 5 minutes        8888/tcp             silly_ptolemy           
24b16b64e6ad        amplab/spark-worker:1.0.0       "/root/spark_worker_   5 minutes ago        Up 5 minutes        8888/tcp             compassionate_lalande   
25efa7bbeb20        amplab/spark-worker:1.0.0       "/root/spark_worker_   5 minutes ago        Up 5 minutes        8888/tcp             happy_curie             
1ba8d72a6cd2        amplab/spark-master:1.0.0       "/root/spark_master_   6 minutes ago        Up 6 minutes        7077/tcp, 8080/tcp   kickass_jones           
3a8c3a8906df        amplab/dnsmasq-precise:latest   "/root/dnsmasq_files   6 minutes ago        Up 6 minutes 

具体的参数解释可以戳这里
-w 是用来指定 spark 运行之后对应的 worker 进程个数
从上面输入 docker ps 命令之后显示出来的信息可以推知,我们总共创建了: spark-shell , spark-worker , spark-master, dns
这些容器运行,但是没有 hadoop 运行, 于是在熟悉上述流程的我又查了其他的几个spark 集群镜像文件,找了一个合适的 接下来试试这个镜像文件好了…


不过先来将当前系统中的所有运行容器停止

// 同样现将路径切换到 ./deploy 文件夹的路径下面
$ ./deploy/kill_all.sh spark
$ ./deploy/kill_all.sh namespace

将该镜像文件下载到本地

$docker pull sequenceiq/spark:v1.6.0onHadoop2.6.0
成功下载显示信息:
...
95d969caad90: Download complete 
2d727ce74b86: Download complete 
28c9338da9a6: Download complete 
cb7d9861a895: Download complete 
73bb712333d9: Download complete 
a466bc76549f: Download complete 
441cf02fdf7d: Download complete 
056efae329d8: Download complete 
Status: Downloaded newer image for sequenceiq/spark:v1.6.0onHadoop2.6.0

从 docker 镜像文件生成并运行容器

先来查看一下当前系统中所有的镜像文件

$docker images     // 先来查看一下当前系统中所有的镜像文件
REPOSITORY               TAG                   IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu_14                wget                  44552cea1d79        24 hours ago        187.9 MB
ubuntu                   14.04                 8693db7e8a00        3 weeks ago         187.9 MB
sequenceiq/spark         v1.6.0onHadoop2.6.0   056efae329d8        5 weeks ago         2.877 GB
amplab/spark-shell       1.0.0                 c18acb8d81a0        20 months ago       964.3 MB
amplab/spark-worker      1.0.0                 6f77966546ee        20 months ago       964.3 MB
amplab/spark-master      1.0.0                 a43b969cfeff        20 months ago       964.3 MB
amplab/dnsmasq-precise   latest                d9cdba2ae123        23 months ago       205.8 MB

再运行镜像文件生成容器实例

$docker run -it sequenceiq/spark:v1.6.0onHadoop2.6.0  bash 

成功运行显示信息:
Starting sshd:                                             [  OK  ]
Starting namenodes on [1f25c1d3d790]
1f25c1d3d790: starting namenode, logging to /usr/local/hadoop/logs/hadoop-root-namenode-1f25c1d3d790.out
localhost: starting datanode, logging to /usr/local/hadoop/logs/hadoop-root-datanode-1f25c1d3d790.out
Starting secondary namenodes [0.0.0.0]
上述 docker 运行命令作用是是从刚刚下载到本地的 docker 镜像文件中生成 docker 容器(该容器中就包含部署好了的 hadoop 和 spark 软件);
生成容器之后,登录到该容器中,并运行 bash 命令

成功登录显示信息:
bash-4.1# ls     // 先显示一下容器中的基本信息       
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  pam-1.1.1-17.el6.src.rpm  proc  root  rpmbuild  sbin  selinux  srv  sys  tmp  usr  var
bash-4.1# jps     // 然后查看一下容器系统中运行的进程都有什么。 可以看出有 Hadoop 节点和 Spark 等相关进程在运行 
562 NodeManager
353 SecondaryNameNode
109 NameNode
183 DataNode
636 Jps
482 ResourceManager

运行 spark 中的 counter 测试程序

首先需要运行一下 spark-shell , 直接在当前 bash 命令行中输入如下命令 
$spark-shell \
 --master yarn-client \
 --driver-memory 1g \
 --executor-memory 1g \
 --executor-cores 1

如果成功,将会显示如下信息:

16/02/14 02:57:02 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
16/02/14 02:57:03 INFO spark.SecurityManager: Changing view acls to: root
16/02/14 02:57:03 INFO spark.SecurityManager: Changing modify acls to: root
16/02/14 02:57:03 INFO spark.SecurityManager: SecurityManager: authentication disabled; ui acls disabled; users with view permissions: Set(root); users with modify permissions: Set(root)
16/02/14 02:57:04 INFO spark.HttpServer: Starting HTTP Server
16/02/14 02:57:04 INFO server.Server: jetty-8.y.z-SNAPSHOT
16/02/14 02:57:04 INFO server.AbstractConnector: Started SocketConnector@0.0.0.0:58832
16/02/14 02:57:04 INFO util.Utils: Successfully started service 'HTTP class server' on port 58832.
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 1.6.0
      /_/

Using Scala version 2.10.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_51)
Type in expressions to have them evaluated.
... 后面反正还有挺多,最后会看到 scala> 命令行提示输入符

我们来测试一个最简单的计数程序调用好了

scala> sc.parallelize( 1 to 1000).count()
16/02/14 03:01:56 INFO spark.SparkContext: Starting job: count at :28
16/02/14 03:01:56 INFO scheduler.DAGScheduler: Got job 0 (count at :28) with 2 output partitions
16/02/14 03:01:56 INFO scheduler.DAGScheduler: Final stage: ResultStage 0 (count at :28)
16/02/14 03:01:56 INFO scheduler.DAGScheduler: Parents of final stage: List()
16/02/14 03:01:56 INFO scheduler.DAGScheduler: Missing parents: List()
16/02/14 03:01:56 INFO scheduler.DAGScheduler: Submitting ResultStage 0 (ParallelCollectionRDD[0] at parallelize at :28), which has no missing parents
16/02/14 03:01:57 INFO storage.MemoryStore: Block broadcast_0 stored as values in memory (estimated size 1096.0 B, free 1096.0 B)
16/02/14 03:01:57 INFO storage.MemoryStore: Block broadcast_0_piece0 stored as bytes in memory (estimated size 804.0 B, free 1900.0 B)
16/02/14 03:01:57 INFO storage.BlockManagerInfo: Added broadcast_0_piece0 in memory on 172.17.0.10:48410 (size: 804.0 B, free: 517.4 MB)
16/02/14 03:01:57 INFO spark.SparkContext: Created broadcast 0 from broadcast at DAGScheduler.scala:1006
16/02/14 03:01:57 INFO scheduler.DAGScheduler: Submitting 2 missing tasks from ResultStage 0 (ParallelCollectionRDD[0] at parallelize at :28)
16/02/14 03:01:57 INFO cluster.YarnScheduler: Adding task set 0.0 with 2 tasks
16/02/14 03:01:58 INFO scheduler.TaskSetManager: Starting task 0.0 in stage 0.0 (TID 0, 42ba3f37ce84, partition 0,PROCESS_LOCAL, 2078 bytes)
16/02/14 03:01:58 INFO scheduler.TaskSetManager: Starting task 1.0 in stage 0.0 (TID 1, 42ba3f37ce84, partition 1,PROCESS_LOCAL, 2135 bytes)
16/02/14 03:02:04 INFO storage.BlockManagerInfo: Added broadcast_0_piece0 in memory on 42ba3f37ce84:58577 (size: 804.0 B, free: 517.4 MB)
16/02/14 03:02:04 INFO storage.BlockManagerInfo: Added broadcast_0_piece0 in memory on 42ba3f37ce84:36276 (size: 804.0 B, free: 517.4 MB)
16/02/14 03:02:09 INFO scheduler.TaskSetManager: Finished task 0.0 in stage 0.0 (TID 0) in 11580 ms on 42ba3f37ce84 (1/2)
16/02/14 03:02:09 INFO scheduler.TaskSetManager: Finished task 1.0 in stage 0.0 (TID 1) in 11454 ms on 42ba3f37ce84 (2/2)
16/02/14 03:02:09 INFO cluster.YarnScheduler: Removed TaskSet 0.0, whose tasks have all completed, from pool 
16/02/14 03:02:09 INFO scheduler.DAGScheduler: ResultStage 0 (count at :28) finished in 11.657 s
16/02/14 03:02:09 INFO scheduler.DAGScheduler: Job 0 finished: count at :28, took 13.058068 s
res0: Long = 1000

这样一个 spark 集群就搭建好了,如果需要把当前对容器做出的修改同步到原有的镜像文件(推荐重新另存一个新的镜像文件),可以使用上一篇博客中介绍的 docker commit 这个命令

关于收尾工作

当前所处的状态是 scala> 的命令行,输入 exit 便可以退出当前 scala 命令行交互的状态;
再次输入 exit (一次或是多次) 便可以退出当前登录的 spark-hadoop 集群容器,当然容器在你退出之后便会’消亡’,也就是不运行了系统回收它的资源咯,输入 docker ps 便查看不到容器信息;
如果在实际工作中推荐的做法是,在退出容器之前,在另一个远程访问终端内,将该容器的状态信息进行保存(归档或是生成镜像文件,如果乐意也可以将生成的镜像文件提交到 docker-hub 的上面)

总之,博客中很多地方写的很啰嗦啦,因为我喜欢在自己经常犯的错误的地方啰嗦几句,不喜欢的话,来打我啊~
end

在 Ubuntu 上面安装部署 docker

Description: 本篇博客简单记录一下在 ubuntu 上安装部署 docker 的流程,以及 docker 常用的命令

Docker-Hub 管理镜像的方式和 github 管理代码的方式类似,你可在 Docker-Hub 网站上面申请一个账号

便可以 push 自己创建的系统镜像,或是 search,pull 他人的系统镜像到本地了,十分方便。


官网上下载 docker

打开终端,可以通过输入 docker 命令来检查当前系统中是否已经安装有 docker ,如果没有安装系统会显示出

apt-get install docker.io 

的提示语句消息,按照提示输入命令来在 ubuntu 上面安装 docker
如果您想在 windows 上面部署 docker 环境,只需要到官网上面下载 docker-install.exe 文件即可,

该文件提供 windows 系统下面 docker 运行所需要的软件,包括 Virtual-Box ,Git 和 docker-hub 图形界面工具,

不过这里介绍的是基于 ubuntu 请打算在 windows 上面部署的同学绕行。


docker 中的镜像和容器

在动手 pull 一个镜像之前,还要啰嗦一下,那就是对 docker 中的镜像容器 这两个名词的理解。

如果将镜像文件比作是 Java 编程语言中的类(文件)的话,那么容器便是基于 Java 类所实例化(new) 的一个对象实例。

镜像文件是不会被轻易改变的,它由唯一ID号码所标识,该 ID 号码和远程 pull 该镜像文件的镜像库中的文件 ID 号码保持一致。

而通过镜像文件实例化的容器一旦被创建,运行之后便是时刻处于变化状态。当然如果你如果想保存容器的当前状态的话,

也可以通过docker 提供的命令,将容器X(当前的状态)写入镜像文件中,这样再次通过镜像文件生成容器实例的时候,

该被生成的容器实例所处状态便和容器 X 是保持一致的。但是在这里也需要知道容器也是由不同的 ID 号码所唯一标识的,

就如同 Java 编程中每个类对象都由不同的 hashCode来标识一样。


使用 docker

在 docker 安装好了之后,输入 docker 命令之后会有很多参数提示信息,在这里介绍几个十分常用的命令:

docker -h

该命令用来显示 docker 的帮助选项

docker version

用来显示当前 Docker 的版本信息,啰嗦一句, docker 在 1.8 版本之后才能够支持本地向 docker-hub 上面提交 docker 镜像文件。
所以有些时候查看 docker version 还是很有必要的(曾经使用 1.2 的 docker 提交镜像文件一个整下午失败的教训)

docker info

该命令用来显示当前系统中 Docker 的信息,镜像文件和被创建的容器数目等信息

docker login

该命令在你想 docker-hub 上面注册信息之后,可以在命令行通过该命令远程登录,

如同 github 上面注册信息并添加ssh-key 之后通过 ssh 远程登录一样。

Username: kylin27
Password: *******************
Email: kylin27@outlook.com

你的登录信息存放在 .dockercfg 这个文件中

docker logout

注销当前登录的账号

该命令用来在 docker-hub 上面寻找符合描述信息的 docker 镜像文件
比如我想搜索已经安装部署了

  • 被其他用户收藏超过 10 次
  • 列出信息的时候会显示了该镜像完整的描述信息
  • java8
  • 并且能够自动化构建镜像
  • 的docker 镜像的话可以
docker search -s 10 --automated --no-trunc java8

# -s 10  搜查出被其他用户收藏(s short for stars) >= 10 次的 docker 镜像
# --automated 支持自动化构建镜像
# -- no-trunc 在显示被搜索的信息时,需要显示出完整的镜像描述信息
# java8 镜像描述信息

docker pull

将 docker-hub 中的镜像拉去(pull) 到本地镜像库

docker tag

为本地的某个镜像文件打标签,说到打标签就不得不啰嗦几句了,在 docker-hub 中通常将镜像文件按照标签来存放,
根据标签来存放镜像文件可以最大化的减少服务器端冗余的数据。 不过打标签是需要遵循一定的格式的:

docker tag username/image_name

## 其中 username 便是你使用 docker login 命令的时候使用的用户名
## image_name 便是你想为当前镜像文件起的名字

docker push

便是将当前系统中的镜像文件’推送’到docker-hub 上面的命令,
推送之后,你便可以通过网页的 docker-hub 登录自己的账户查看了(就是等同于 github 上面查看自己推送的代码一样)

推送之后,也就意味着这个镜像文件交个 docker-hub 来’托管’, 通过使用 docker search 命令也可以搜索得到的。
docker push 命令推送镜像之前,最好(一定要)使用 docker tag 命令来为它打上标签

docker images

列出本地镜像库中所有的镜像文件,当本地镜像库中镜像文件增多的时候,
推荐使用 docker images [镜像文件起始名称] 命令来查找(筛选)特定的镜像文件

docker ps

这个命令用来显示当前系统中所有运行的容器

docker rmi

这个命令用来删除本地镜像文件

docker rm

删除本地容器对象,镜像文件就像是母鸡,删了就不能生蛋(容器)了,但蛋砸了(容器删了)只要是母鸡(生成该容器的镜像文件)还在,一切都好说。

如果当前容器的状态没有同步到镜像文件中,那… 就当我啥也没说好了.

docker [start| stop | restart]

docker 容器运行系列命令,分别是启动,停止,重启一个容器

docker pause

暂停某个容器中的所有进程,但是该docker容器的进程并不停止 停止docker容器中的进程 != 杀死 docker 容器

docker unpause

将 docker 容器中被暂停的进程重新恢复运行

docker kill

杀死 docker 某个容器进程,杀死 != 移除

docker save

将镜像文件进行归档保存,后接参数 -o (output file) 用来指定归档文件的名称
第一个参数是生成归档的文件名称,

第二个参数是本地镜像文件名称,这里的冒号后面对应是为同一个镜像文件的不同变动所加上的标签 ;

我通常是按照对当前镜像文件作出的改变,或者是镜像文件生成的日期来添加标签(好记…)
同时归档文件还可以使用 .tag.gz ; .tgz, bzip 等等多种压缩文件类型

docker save -o kylin27_java8.tar kylin27/ubuntu:java8

docker load

将归档(docker save 生成的 .tar 文件之后)的docker 镜像文件,重新生成 docker 经常文件(save 的逆向操作)
后接参数 -i (input file)

docker load -i kylin27_java8.tar

docker export

刚刚介绍的拿对是 镜像文件 <=> 归档文件的相互转换, 接下来的这两个命令便是容器 => 归档文件 ; 归档文件 => 镜像的相互转换。
docker export -o 命令是将当前的容器进行归档,

docker export -o kylin27_container_java8.tar 30484q39as30

上述的命令是将 ID 号码为 30484q39as30 的容器归档生成名为 kylin27_container_java8.tar 的文件

docker import

将容器归档文件生成镜像文件

cat ./kylin27_container_java8.jar | sudo docker import - kylin27_ubuntu:java8

上述的命令是用来将数据信息从归档文件 kylin27_java8.jar 中读出来,然后调用 docker import 命令生成名为 kylin27_ubuntu:java8 的镜像文件

docker commit

将当前处于运行状态的容器中所有被变动的状态同步给镜像文件,也就是将这个容器进行模板化,将容器中所有的信息写入(更新)到
镜像文件中(该镜像文件既可以是原来的镜像文件,也可以写入到一个新的镜像文件中)

docker commit [容器ID] [要被更新的镜像文件名称|新创建的镜像文件名称]

docker inspect

在刚才介绍的命令中,比较一般的登录到运行容器中的方法是 docker run 后接登录之后执行的程序,

在上述命令中介绍的是一登录容器边启动它的命令控制台的交互程序,还有一种方式是先使用 run 方法来登录然后修改容器的 IP 地址,

在通过 ssh 的方式来登录。 不过官方推荐的方式是,使用 docker inspect + 容器 ID 号码来提取正处于运行状态的容器 PID 号码

(对的就是容器的进程号), 然后通过 nsenter 这个程序来直接切入到容器进程中; 我们接下来就介绍这种方法:

nsenter 在 Ubuntu-14.4 和它之前的版本均不支持,如果您的 Ubuntu 版本要小于等于 14.4 那么通过下面的命令来安装(更新)

cd /tmp
curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz | tar -zxf-cd util-linux-2.24./configure --without-ncursesmake nsentercp nsenter /usr/local/bin

不得不说,有人曾经很详细的了解 docker 用来存放镜像和容器的底层文件,发现 docker 底层是通过使用 json-schema 格式的文件来存放镜像和容器
所处的状态的,而当前所介绍的这个 docker inspect 命令便是抽取 docker 容器对应的 json-schema 格式的文件内容,而后面的通过参数来指定
需要获取的是底层文件中的哪种属性信息字段
输入上述命令之后,便可以获取当前运行容器的进程 PID 号码

$docker inspect --format param  容器 ID 号
上述的param 使用冒号中括号中括号.State.PID 中括号中括号冒号 来替代,我的博客格式显示有点问题,没办法直接表示 输入上述命令之后,便可以获取当前运行容器的进程 PID 号码
$nsenter --target $PID --mount --uts --ipc --net --pid 

上面的 $PID 使用输入 docker inspect 命令之后显示出来的数据结果来替代


docker 命令使用案例

接下来通过案例的方式来系统学习一下 ubuntu 下面 docker 的使用方法,在案例中,我们要

  • 下载 ubuntu 的镜像文件

    $docker pull ubuntu:14.04
    output message :
    14.04: Pulling from ubuntu
    f15ce52fc004: Downloading 24.85 MB/65.68 MB
    f15ce52fc004: Downloading  25.4 MB/65.68 MB
    f15ce52fc004: Downloading 56.75 MB/65.68 MB
    8693db7e8a00: Download complete
    
  • 显示当前系统中所有 docker 镜像文件

    $docker images 
    output message :
    REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    ubuntu              14.04               8693db7e8a00        3 weeks ago         187.9 MB
    
  • 通过镜像文件生成容器并登录

    $docker run -i -t ubuntu14.04/bin/bash
    output message :
    root@b4bfcdde7b31:/#    // 这就进入了运行的 ubuntu:14.04 的容器中了 ; 退出的话直接在命令行中输入 exit 即可
    
  • 登录容器之后,安装一个 wget 软件

    root@b4bfcdde7b31:/# apt-get install wget 
    
  • 待到软件安装成功之后,将容器当前状态更新到新创建的镜像文件中

  • 不过首先应该获取到该容器的 ID 号码,打开一个新的 ssh 远程连接/终端窗口输入查看当前系统中所有容器和其ID号码
    $docker ps 
    output message :
    docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
    b4bfcdde7b31        ubuntu:14.04        "/bin/bash"         4 minutes ago       Up 4 minutes                            desperate_poitras   
    // b4bfcdde7b31  这个便是容器的 ID 号码,用于在系统中唯一标识容器
    
  • 生成该容器的归档文件

    $docker export -o kylin27_container_wget.tar.gz b4bfcdde7b31 
    # 等待之后,便会在当前路径下面找到名为 kylin27_container_wget.tar.gz 的归档文件
    
  • 生成该容器的镜像文件


    $docker commit b4bfcdde7b31 ubuntu_14:wget
    // 冒号后面的是为镜像文件创建的标签,而冒号前面是该镜像文件对应镜像库的名称

  • 为该镜像文件打标签
    在将镜像文件提交到 docker-hub 上面的时候需要注意两点 1. 确保成功 login 2. 确保 docker 镜像文件所打的标签符合 docker-hub 格式
    必定如果你用过 github 的话(没用过也不会看到这篇搭建在 github 上面的博客)应该熟悉 github 上面的 repository 的命名和路径规则
    通常是用户名+资源库名称,道理放到 docker-hub 上面也是一样的。

    docker tag ubuntu_14:wget kylin27/ubuntu14:wget
    

    再次输入命令 docker images 会看到如下信息

    REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
    ubuntu_14           wget                44552cea1d79        5 minutes ago       187.9 MB
    kylin27/ubuntu14    wget                44552cea1d79        5 minutes ago       187.9 MB
    ubuntu              14.04               8693db7e8a00        3 weeks ago         187.9 MB
    

    看到ubuntu_14 和 kylin27/ubuntu14 这两个镜像文件虽然镜像库名称不同,但是 IMAGE ID 却是相同的,这就是方便区分标识,但却最大程度上减少冗余文件的存储的思想所在。

  • 为该镜像创建文档文件

    $docker save -o kylin27.tar ubuntu_14:wget
    

    随后便可以在当前目录下面看到名为 kylin27.tar 的归档文件

  • 将该镜像文件推送到 docker-hub 上面 在推送镜像文件之前,需要先把系统中生成该镜像文件的容器给停止(血与泪的教训)

    $docker stop b4bfcdde7b31
    $docker push kylin27/ubuntu14:wget
    output message:
    The push refers to a repository [kylin27/ubuntu] (len: 1)
    44552cea1d79: Image already exists 
    8693db7e8a00: Image successfully pushed 
    a4c5be5b6e59: Image successfully pushed 
    c4fae638e7ce: Image successfully pushed 
    f15ce52fc004: Image successfully pushed 
    Digest: sha256:09dd6e67f2c8ec9a0a9e21f4e34f415083dcd29a96e156b5d36fbdd4295f5c2f
    

成功推送信息之后,到自己的 docker-hub 上便可以看到咯~
在页面右边的显示信息便是该镜像文件 pull 的路径,如果你对这个镜像文件感兴趣的话便可以通过该信息将镜像 pull 到本地

  • 再来介绍一个 docker 中实用的批量删除 none 镜像文件的命令
    在频繁创建删除 docker 镜像文件之后便会生成很多名为 none 的镜像文件,在这里可以通过下面的命令将所有无用的 none 镜像文件删除。
    $docker ps -a | grep "Exited" | awk '{print $1 }'|xargs docker stop
    $docker ps -a | grep "Exited" | awk '{print $1 }'|xargs docker rm
    $docker images|grep none|awk '{print $3 }'|xargs docker rmi
    

end

google guava's BiMap

Description:

google guava 中提供的一种非常有用的数据结构,双向 Map。是的,你没有听错,就是相比于普通仅能够提供 key 到 value 映射的 Map ,双向Map既能够提供 key 到 value 的映射,有能够提供 value 到 key 的映射。
这在某些特定场合是非常有用的,那么接下来简单介绍一下 BiMap 的使用方法。


BiMap 所支持的方法

V forcePut(K key, V value)

  • 方法介绍: 该方法用来强制执行 BiMap 所继承的 Map put() 方法. 也就是说如果 map 中有
    “a-key”->”a-value” 的话,在执行 biMap.forcePut(“a-key”,”a-value2”) “a-value2” 会强制取代 “a-value” 的位置

BiMap inverse

  • 方法介绍: 该方法会将当前的 转换成

V put(K key , V value)

  • 方法介绍: 该方法用来向数据结构中放入 key-value 键值对

    void putAll(Map<? extends K, ? extends V> map )

  • 该方法用来将 Map 类型的一次性全部放入到当前 BiMap 中,不过值得注意的是 K1, V1 必须要是 K,V 类型或使其子类

    Set values()

  • 该方法用以集合的方式来获得 BiMap 的值集合

  • 下面通过一个例子来介绍一下如何使用 BiMap,通过一个 Seq 类型的字符串 List 生成 key-value 对字典,
  • 然后将字典进行反转操作,将原来的 k-v 编程 v-k 映射map
object MatchPatternTester extends App{

    // 将传入的 Seqp[String] 通过 zipWithIndex.toMap 的方式
    // 转换成 Map 类型,然后通过调用 ImmutableBiMap 中的 putAll 方法
    // 将其传入到 ImmutableBiMap 中,然后调用它的 build 方法生成 BiMap 

    def getMapFromSeq(dict: Seq[String]): Unit ={
        val kTv = ImmutableBiMap.builder[String,Int]()
              .putAll(dict.zipWithIndex.toMap[String,Int])
              .build()

    // 获取 BiMap 的 key 集合
    val values = kTv.keySet()

    // 通过获取的 key 集合遍历 BiMap 中的每个 key-value 元素对
    values.foreach( (x: String ) => println("key " + x +" value "+ kTv(x)))

    // 调用 BiMap 的 inverse 方法来将 BiMap 转换成 BiMap    
    val inverseMap = kTv.inverse()

    // 获取反转之后的 key 集合
    val values2 = inverseMap.keySet()
    // 在获取 key 集合之后遍历反转之后的 BiMap 
    values2.foreach((k: Int ) => println("key " +k + " value "+ inverseMap(k)))

  }

  // 创建传入参数(Seq[String])
  val listInput = List[String]("aimer","aimer","kylin","kokia","rurutia","kylin")

  getMapFromSeq(listInput)

}

scala编程相关知识1

Description:
这篇博客中记录一下 scala 编程语言中的知识点


case class 在 scala 中的使用方法

case class 和 java 中所定义的 class 二者类似

  • 在创建 case class 对象实例的时候,可以不用在实例前面加上 new 关键字
case class apple(name:String)
class banana(color:String)

val app = apple("i am a apple — —b")
val ban = new banana("i am yellow — —||")
  • case class 创建的类对象实例调用 .toString 方法时显示出来的字体更漂亮
    case class 实例会将 ‘case class 名称(里面是成员变量名称)’ 显示出来
    class 的 .toString 方法会显示类似 java 语言直接打 Object 象信息的数据
    如果你想通过打印的方式来快速获取类对象中的成员数据信息的话,推荐使用 case class

  • case class 默认实现了 equals 和 hashCode 两个方法,所以 case class 实例化的对象可直接调用这两个方法

  • case class 实现了(extends) Serializable ,可以对 case class 对象执行序列化操作

  • 在声明 case class 的时候,传入的构造参数均是 public 的可以直接通过 实例名称.构造函数参数名称
    来访问
case class Document(docId: String, body: String="", label: Set[String] = Set.empty, integerLabel: Int)
其中 body: String="" 这种赋值方式是默认赋值,如果调用者没有为该参数指定参数数值的话,就会使用默认的赋值。
如果调用者为该参数指定特定数值的话,该指定的特定数值便会替代该指定默认参数数值
但是如果最后一个参数为必须传入数值的参数类型的话,中间的参数数值是必须要给定的

1. val doc1 = Document("id1",2)
   println(doc1.toString) // 将会出错
2. val doc2 = Document("id2","doc2",Set("abc","eft")
   println(doc1.label)  // output Set(abc,eft)   
  • case class 对象实例支持模式匹配,这是 scala 为何要提出 case class 这一类型的原因
abstract  class methodX
case class test1(n: Int) extends methodX
case class test2(a: Int, b: Int) extends methodX

object MatchPatternTester extends App{

  def matchTester(term: methodX ){
    term match{
      case test1(n) =>
        println("this is test1" +n)
      case test2(a,b) =>
        println("this is test2 a , b"+a +" " +b)
    }
  }

 val x1 = test1(1027)

 val x1Result =matchTester(x1)

 val x2 = test2(10,72)
 val x2Result = matchTester(x2)

}

搜索引擎知识点整理

Description: 这篇博客简单介绍一下搜索引擎中的基础知识和原理。

由于在开发 github 上的 StackExchangeRecommenderSystem 时候会用到 lucene,
所以在这里先来复习一下搜索引擎中的基础知识和算法。


索引的基础知识

索引是在程序员的世界是常常被提起的名词,例如,在数据库中,在编程的时候为高级复杂的数据结构以及
对网页所创建索引,来提高程序的查询效率。
索引在日常生活中也很常见:字典目录中某个词语后面标识了出现该词的页码。
下面我们来一起学习一下,关于索引常见的词汇:

  • 文档(Document): 索引所标定的’目标对象’: 是数据库索引,那么文档便是数据表中的某条记录;
    如果是对网页创建的’倒排索引’(索引的一种类型),那么文档便是网页的页面; 这个’目标对象’
    还可以是某种具体的文件,例如 word,PDF文档
  • 文档集合 (Document Collection): 就是上述’文档’构成的集合
  • 文档编号(Document ID): 在搜索引擎内部为每个文档赋予的唯一标识,可以在文档集合中唯一标识一个文档
  • 单词编号(Word ID): 用来唯一标识某个单词
  • 倒排索引(Inverted Index): 普通的索引是为某个文档创建索引出在该文档中某个单词,
    而倒排索引则是以单词为主题,通过单词来索引出出现该单词出现的文件; 倒排索引由两部分组成
    {单词词典,和出现该单词的文档集合}
  • 单词词典(Lexicon): 搜索引擎通中的索引单位是单词,单词词典是在所有文档集合中出现过的单词所构成的集合;
    而在单词词典中的每条索引项中记录的信息有{单词信息,单词所指向出现该单词的文章集合-倒排列表}
  • 倒排列表(PostingList): 倒排列表指的是,出现过某个单词的所有文档所构成的列表和该单词在该文档中出现的
    位置信息; 倒排列表中的每条记录我们可以将其称作是倒排项(Posting).
  • 倒排文件(Inverted File): 倒排文件是存储倒排索引的物理文件。我们都已经知道了倒排索引由两部分组成,
    分别是,单词词典和倒排列表. 在此基础上,存放倒排列表的文件就叫做倒排文件。


    倒排索引小例子

    比如那今天的知乎日报新闻来举例

    文章编号            文章标题
    1                    如何自己制定健身训练计划?
    2                    女生怎么健身锻炼好身材?
    3                    减肥对外貌的改变有多大?
    4                    健身教练有哪些内幕?
    5                   女生如何锻炼减肥既简单有健康?
    
    单词 ID         单词        倒排列表
      1.            自己          {1}
      2.            女生          {2,5}
      3.            减肥            {3,5}
      4.            健身          {1,2}
      5.            教练          {4}
      6.            简单          {5}
      7.            训练          {1}
      8.            外貌          {3}
      9.            改变          {3}
      10.            哪些          {4}    
      11.         内幕          {5}
      12.            锻造          {2,5}
      13.            身材          {2}    
      14.            训练          {1}
      15.            计划          {1}
     

当然,我们也为了让索引列表更细致的记录索引文件的信息来添加上某个词语出现的频率信息,TF 便是用来描述词语在某个文章中出现的次数的. 例如 ‘女生’ 这个词语在文章 2,5 均出现过,并且出现 1 次
便可以记成 女生 {2;1,5;1} 这样子。更加细致的记录方式是,将这个单词出现的位置也记录下来:

例如 ‘女生’这个单词在文章2,5中出现的位置分别是第 1 个位置,就写成如下的格式
女生 { (2;1;<1>),(5;1;<1>) }


单词词典 (Lexicon)

单词词典存放了在文档集合中出现过的所有单词的相关信息的同时也记录了该单词集合中的每个单词
所映射到的倒排列表在倒排文件中的位置信息。

可以试想一下,如果需要创建索引的文档集合十分的巨大,那么随之提出出来的单词词典中所存放的单词数量
也是十分庞大的。在数据结构课程中我们已经学习过,如果要快速的定位某个单词(键值)的话可以借助于
哈希链表或是树形词典这类的数据结构。

哈希加链表 - 单词词典

哈希加链表是处理哈希表冲突的一种解决方法,哈希表有随之配套的 hash 散列函数,会为每个输入的
key 键生成’唯一’标定该key 对应 value 的标识,但是如果 hash 散列函数选取的不当的话,便会造成
不同的 key 生成了重复的标识; 这就是所谓的’冲突’的发生,处理’冲突’有着不同的方法,
加链表就是在发生冲突的 key 的后面开辟一块空间存放后来的 key ; 待到查找的时候,通过 hash 散列函数
找到该 hash 生成数值指定的链表头,沿着链表继续寻找就可以了。

树形结构 - 单词词典

前缀树和后缀树都是可以做单词词典很好的数据结构


倒排列表

在前面我们提到过了,倒排列表中的基本构成单元倒排索引项中包含字段有:
{单词出现的文档的唯一编号,单词出现频率,<单词在文档中的位置1,位置2,位置3…>}
但是在实际的应用中为了尽量节省内存,通常会使用文档编号的差值来取代文章的唯一编号。


建立索引

建立索引有着不同的方法,同时索引也分为动态索引和静态索引,其中静态索引是生成索引之后在修改文档集合中的
文档之后,不能自动的更新之前索引文件 ; 而动态索引则是生成索引之后,修改文档集合中的文件内容之后
会随之自动的更新文档文件。

静态索引

两遍文档遍历法

两遍文档遍历法在创建索引的时候需要对文档集合执行两遍扫描,在创建索引的时候仅需要内存即可,整个过程无需磁盘参与。

  • 首次扫描:
    算出为该文档集合创建的索引所需的内存容量大小:搜集文档集合包含文档个数 N ,文档集合中不重复单词总数 M, 每个单词在多少个文档中出现 DF. 将所有单词的 DF 数值进行加和便是建立最终索引所需要的内存大小。
  • 第二次扫描:

    首次扫描已经确定了文档集合中的每个单词的 DF信息,在第二次扫描的时候,为每个单词的 DF 信息
    

    分配内存空间,并使用指针将单词集合与单词的 DF 信息将连接即可,也就是简历每个单词的倒排列表信息;
    但是通过上面的陈述可知,在单词倒排列表中的倒排索引项仅仅包含单词 DF信息还是不够的;对文档 ID 或是文档 ID 偏移量的计算以及单词在文档中出现位移和出现次数等这些信息都可以在第二次扫描的时候来完成。
    等到两遍扫描结束之后,将内存中创建的倒排列表和单词词典信息写入到磁盘中就完成了索引的创建了。

  • 对两遍文档遍历法的评估: 因为全程使用的是内存无需磁盘的参与,所以连通文档集合和生成的倒排列表等数据信息均需要存放到内存中,这需要内存足够大,或者是仅仅适用于创建文档集合规模较小的索引。同时,需要对文档进行两次扫描,比较耗时在速度上并不占优势。

    排序法

    排序法为文档集合创建索引是为了弥补两遍遍历法建立索引过程中,对内存消耗大这一缺点提出的。两边遍历索引生成方法使用内存的大小是不固定的,如果文档集合大内存开辟就会大一些,相反便会小一些。而排序法无论文档集合如何,其所需要的内存大小均是固定分配的。开辟的内存空间主要用来存放词典信息和索引的中间结果。
    每当中间结果将内存吃空的时候,便会统一将内存中的中间数据写入到磁盘中。遵循上述倒排索引中所介绍的两部分的结构-单词词典,倒排列表(倒排列表的组成单元是倒排索引项),在整个的排序法创建文件集合索引的过程中,单词词典始终是作为常驻内存的数据结构的。

  • 读取文档,对文档进行编号,每个文档为其创建唯一的标识 ID 号码
  • 解析文档,每当在文档中遇到新单词,

    { 
    查看该单词在单词词典中是否有记录 
    1. 有记录,获取该单词在词典中的 ID 号码
    2. 单词词典中没有记录该单词,为该单词创建全词典唯一的 ID 号码,然后将其收录到单词词典中
    }
    
  • 在对当前文档完成了读取和解析之后,便能够为当前文档中出现的每个单词均创建包含着如下信息

 (单词ID,文档ID,单词频率)

上述三元组便可以作为倒排索引列表中的索引项了,将该三元组索引项集合追加到用来存放中间处理结果的大小固定的内存缓冲区中。然后便可以开始处理下一个文档了。

  • 在内存缓冲区被占满之前需要将缓冲区中的数据写入到磁盘临时文件中去,不过在写入之前需要对内存中的三元组序列执行排序操作; 首先按照单词 ID 进行非递减,然后按照文档 ID 进行非递减排序。由于内存中存放的中间结果并不一定全都是同一个文件生成的中间信息,所以会有单词ID 相同但是文档ID不同的情况,这种情况按照文档ID非递减的规则进行排序。PS: 在上述的全部过程中,单词词典是常驻内存的
  • 每次执行向磁盘中写入中间缓存文件操作均是写入一个新的缓存文件,而不是在之前的缓存文件中执行追加。在对所文档集合中的每个文档均读取解析之后,剩下的工作便是合并临时写入磁盘的所有的中间文件了。由于写入磁盘之前在内存中执行了先按单词ID然后文章ID进行排序的操作,所以在合并的时候,只要首先将所有中间文件中单词ID相同的三元组合并为一个数组(由三元组元素构成的有序序列),而所有的数组组合起来便是该文件的倒排列表了。接下来只要将倒排列表中的内容写入到文件中即可。而这个文件便是所谓的索引文件了。
  • 对排序法的评估: 在排序法创建文档集合索引的过程中,单词词典作为常驻内存数据结构并不会被写入到磁盘的中间临时文件中且大小也随着解析文档个数的增多而变大,同时排序法中分配的内存大小是固定的,所以当单词词典大小变大之后,每次用来缓存三元组的个数也会随之减少,如果单词词典继续增大会无法缓存解析文件而生成的三元组,这样便会频繁的执行写入操作,从而导致程序整体性能的下降。

归并法

归并法是为了弥补排序法中,单词词典常驻内存耗空分配的固定内存这一缺陷而提出的。归并法的特点是在每次执行三元组数据信息写入的同时也会将单词词典信息写入到中间临时文件中。这样便可以保证为程序分配固定大小的内存会全部用于后续索引的创建。

  • 归并法的执行过程和排序法大部分相同,不同之处之一是在写入中间缓存文件中是将 {单词词典,三元组集合} 写入到中间缓存文件中
    之二是在文档集合中的全部文档完成解析之后,将所有生成的临时文件进行合并的时候,每个临时文件中存放的是最终倒排列表的一部分;
    而最后的合并操作便是将所有的部分倒排列表合并成一个完成的倒排列表。
    

分布式索引

分布式索引和数据库中的’分片’ 技术有些类似,数据库的’分片’ 技术是通过将一张大数据库表中的信息按照表中的某个属性字段
中的不同值/或是范围分割成许多个来自于该大数据表的’子表’; 常用作’分片’的属性有地域和时间 ;
而分布式索引技术则是将文档集合按照文档或者是单词来对索引进行划分。

按照文档来划分索引

将大的文档集合划分成分布于不同机器上的文档子集,为每个文档自己创建各自的索引。
在执行查询的时候,会在每个机器上面执行查询,并把来自于每个子集的查询结果进行合并生成最终查询结果

按照单词来划分索引

在创建按照单词划分的倒排索引的时候,划分发生在合并中间缓存文件生成最终索引文件的时候。
每当将来自于所有中间缓存文件中的同一单词ID的三元组合并之后,将该三元组集合+单词发送到某台主机上面。
便完成了按照该单词对索引进行’分片’的操作。
在实际的应用场景中,按照文档来进行索引的划分这种方法比较常使用,而按照单词的划分仅仅在特定场合下使用。
因为按照单词来划分索引的可扩展性、均衡负载和容错性都不是很好。

查询处理

一次一个单词的查询处理

将用户查询语句执行清洗(我通常把去掉语句中无用符号和去掉停用词的操作叫做’清洗’…好记),
然后执行分词操作将语句分成多个词语组成集合,获取每个词语的倒排列表,然后计算每个词语与倒排列表中的
每一个文件的相似度,计算相似度之后,如果有文档重复的(两个不同词语倒排索引到同一个文档),那么将重复的文档
得分进行累加;最后将得出得分最高的 K 个文档(通过优先队列进行存放)作为查询结果进行返回即可

一次一个文档的查询处理

跳跃指针的查询处理

短语查询

2 Build Blog on Windows by Node.js and Hexo

Description: This blog introduces how to build your own blog on windows OS. with the help of Node.js and Hexo

Install

Git

Download Git and register an account in GitHub ,

Make sure SSH is installed on your windows OS. Create DSA or RSA keys by running commands below

 
$ cd ~/.ssh * 
$ ssh-keygen -t rsa -C youremail@email.com  
 

Add the generated key rsa*.pub content to your GitHub

SSH keys New SSH key

Paste your ssh rsa.pub key to it

Setup your cmd/git hub bash ,tab

1
$ ssh -T git@github.com

Your will receive a message like this “Hi , welcome to git hub blabla “.

But now ,remember you have not got your token string yet which means you couldn’t ‘deploy’ your hexo blog to github.

Go to the GitHub Setting Page and press the Personal access tokens then Generate new token and confirm your password again .
Write in the token name (Token description) in order your can distinguish this token to other tokens , select scopes (just like setting functions of the token) , and the last step is press the green ‘Generate token’ button. Like this you’ll get your ‘Personal access tokens’

Open your git bash again , and tab in commands

 
$ git config --global user.name "your name" 
$ git config --global user.email "your email"
$ git config --global usr.token "paste the personal access tokens your just got here"
 

Node.js

Download Node.js for Windows

Install Node.js and add both Node.js and npm’s path to environment’s PATH

Create two folders with the name of ‘node_cache’ and ‘node_global’under node.js’s folder

Open git bash under current folder(by clicking right mouse -> git bash), and tab

 
$ npm config ls
$ npm config set cache 'the absolute path you created folder node_cache'
$ npm config set prefix 'the absolute path you created folder node_global'  
 

Hexo

Open your git bash again,change your current to one folder your want your hexo installed in, tab in

1
$ npm install hexo

Local Blog Server

After hexo installed , do not forget add the hexo(the executable binary file)’s location into env’s PATH too

Make a new folder which you want to store blog’s file and change current path to it

Tab in

 
$ hexo init         # this command like git init , initialize the blog workspace and generate files
$ npm install    # wait for a while
$ hexo g         # hexo g is the command which short for hexo generate
$ hexo s         # deploy your hexo template on localhost's server(0.0.0.0:4000)

Deploy Blog to Git(like this blog)

Create a new respo. on your github with the name ‘your github account name’.github.io

Deploy Blog to Git ‘s step is a little different from deploying it on local server

Open git bash on hexo blog’s path and tab in the commands below

 
$ hexo init 
$ npm install
$ npm install hexo-deployer-git --save # download git corresponding plugins

Open your _config.yml file , update the #Deployment’s part content like this

 
# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
  type: git
  repository: git@github.com:'your github account name'.github.io.git  #--> do not forget replace it
  branch: master

After update _config.yml file , tab in commands

1
$ hexo d    # short command for hexo deploy

Open your browser, and tab in https://'your github account name’.github.io you will see the blog your just create

If your want to add new blogs just add a new .md file (mark down edit style) under your blog’s source/_post/ folder

You can change your blog’s name by changing _config.yml’s ‘title’ content

Good luck !

How to use log4j in your project ?

Description: This blog introduces two log4j log file patterns we often use in our java project


Log4j Pattern Note

Pattern1

You can use this log pattern when you want log info be written into multi-log files instead of a single large file.

#file name : log4j.properties
log4j.rootLogger = appendername

log4j.appender.appendername=org.apache.log4j.RollingFileAppender    
# which file appender pattern your choose 

log4j.appender.appendername.File=log/rlog.log                        
# name of the log file in which the program going to write 

log4j.appender.appendername.MaxFileSize = 100KB                        
# threshold of the log file , if current log file size >= 100 KB 
# log4j will create a new log file to write in

log4j.appender.appendername.MaxBackupIndex = 2                        
# limit the maximum number of log file log4j will create
# here we set max log file number equals to 2 , if log4j create 2 files
# next time , if the rlog.log.2's size is >= 100KB ,
# log4j will clean the rlog.log.1's content and continue writing new log info into rlog.log.1

log4j.appender.appendername.layout = org.apache.log4j.PatternLayout
log4j.appender.appendername.layout.ConversionPattern = %p %t %c - %m %n  # output log info pattern                                                                

Pattern2

You can use this log pattern if you would like your log info output to both console and file

This pattern also supports separating error log info into a single error log file

# file name : log4j.properties
log4j.rootLogger = debug,FILE,CONSOLE,ERRORFILE

# output log info to console
log4j.appender.CONSOLE = org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target = System.out
log4j.appender.CONSOLE.layout = org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern = [%p] %t %c %l - %m %n

# %p log info level : {DEBUG,INFO,WARN,ERROR,FATAL}
# %t name of the thread that generates log info
# %c log info is generated by which class 
# %l which class , which thread , on which line 's event produce the log info
# %m output code's info

# output log info to log files
log4j.appender.FILE = org.apache.log4j.DailyRollingFileAppender
log4j.appender.FILE.File = logs/rlog.log
log4j.appender.FILE.Threshold = DEBUG  
# this is used to limit the log level , 
#if the log's level is higher than DEBUG ( like INFO, ERROR) output log info

log4j.appender.FILE.MaxFileSize=50KB
log4j.appender.FILE.MaxBackupIndex = 2
log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern = [%p] %t %c %l - %m %n

# output error (log level >= ERROR) into a single file
log4j.appender.ERRORFILE = org.apache.log4j.DailyRollingFileAppender
log4j.appender.ERRORFILE = logs/error.log
log4j.appender.ERRORFILE.Threshold = ERROR
log4j.appender.ERRORFILE.layout = org.apache.log4j.PatternLayout
log4j.appender.ERRORFILE.layout.ConversionPattern =  [%p] %t %c %l - %m %n

Example

Here is a simple example to use log4j to output log info


// log4j.properties is under 'resources' folder

public class TestLog4j{
    public static void main (String [] args ){
        URL url = new URL("/log4j.properties") ;
        PropertyConfigurator.configure(url.getPath()) ;
        Logger logger = Logger.getLogger(TestLog4j.class) ;

        logger.debug("this is a debug message") ;
        logger.error("error info will output to two files") ;
    }

}

end

谢慧


联系方式

  • 手机:15142046233

  • Email:kylin27@outlook.com

  • QQ : 48060478


教育背景

  • 2009.9-2013.6 | 辽宁大学 | 计算机科学与技术(软件方向) | 本科

  • 2014.9-2017.6 | 辽宁大学 | 计算机软件与理论 | 研究生(保送)


项目经验

基于 zookeeper Curator 实现的小型 P2P网络系统 2015.05 - 2015.11

  • 使用 zookeeper 的客户端 Curator 来快速获取 zookeeper 上数据结点变动的信息,从应用加深了对 zookeeper 所基于的 paxos 算法的理解
  • 使用 Netty 5.0 作为网络数据传输的底层工具,基于 Netty 编写自定义通讯协议,对 Netty 通讯原理有了进一步的认识与学习
  • 编写用于Linux系统和Windows系统的shell,bat脚本用作最后程序的启动脚本
  • 通过将 DHT(分布式哈希表)中的 Chord 算法作为子模块添加到系统中,来大大提高系统中查找资源的效率。将逐个结点查询的资源查找算法使用 Chord查找算法替代,将时间复杂度从 O(N) 改进为 O(logN)。

  • 项目链接地址: 基于 zookeeper Curator 实现的小型 P2P网络系统


大数据算法分析平台 2013.09 -2014.04

  • 在项目初期负责部署开发环境,使用 python 编写安装部署脚本在实验室集群上安装部署 Hadoop 和 Hive, HBase 等 Hadoop 相关子项目。并为开发组的组员编写安装部署说明手册,同时在虚拟机上执行 Hadoop 多借点分布式集群安装仿真实验。
  • 在项目中期负责数据的批量导入输出和数据清洗工作。期间尝试使用 Hive-API, Sqoop 最终使用 Pentahoo 的开源ELT来实现数据在数据库与Hive中的传递。
  • 在项目中后期出于提高文件上传效率的需求,主要负责修改 Hadoop 底层用于传送数据的java 源码。通过使用多线程技术和增添重组 HDFS 底层文件块模块来实现数据块并发推送上传。
  • 在项目接近尾声的时候,应导师的要求系统研究 Hadoop 中的RPC框架 avro 及其通讯机制。
    并对当时主流的 RPC 框架如进行调研实验工作。包括该 RPC 所支持开发语言,数据类型,序列化反序列化性能消耗等等做出详细参数对比,归纳整理后编写性能对比报告。

实习经历 2015.9 - 2015.12

Mycat 开源项目担任项目经理助理

  • 负责开源志愿者信息的录入与汇总,并发布开发模块任务登记和任务领取信息。记录每名志愿者领取开发任务之后的详细开发进度。

  • 向开源志愿者们广播项目开发进度,同时整理与派发项目设计文档。测试、并合并志愿者提交到 GitHub 上面的模块代码,同时使用 GitHub 追踪记录每名开发者的提交信息。

  • 参与编写整理 Mycat 的用户手册,README,ChangeLog 和 Release Note 等技术文档。


开源项目和技术文章

开源项目

  • 基于朴素贝叶斯邮件垃圾分类器 Spark 实现 : 该项目使用的是 Spark mllib 中的朴素贝叶斯算法来对垃圾邮件和常规邮件进行分类处理。 通过 Spark mllib 提供的 Java API 来实现算法的调用。 处理流程分为 数据抽取,数据清洗,数据分析,数据分类和数据输出这五个步骤。

技术文章


技能清单

以下均为我熟练使用的技能

  • 开发语言:Java > C/C++ > Python > Scala

  • 大数据相关: Spark,Hadoop,Hive

  • 数据库相关:MySQL/PgSQL/Redis/Kafka

  • 版本管理、文档和自动化部署工具:Svn/Git/Hexo

  • 单元测试: gtest(google 开源 C++ 测试框架)

  • 云和开放平台: 阿里云(ECS)/微博开放平台/七牛存储

  • 虚拟化平台: Docker,Virtual Box,VMware


四六级成绩

  • 四级 522
  • 六级 485

获奖记录

  • 连续 2 年获得信息学院一等奖学金
  • 信息学院本科生毕业论文一等奖

科研方向

  • 基于大数据的图计算,图可编辑距离计算

学校活动

  • 学校网络中心成员,负责网络中心日常维护与管理
  • 学校科技部成员,主要负责管理举办每月的科技知识讲座活动

致谢

感谢您花时间阅读我的简历,期待能有机会和您共事。