404频道

学习笔记

Image Title

今天下午新办的工资银行卡办下来了,原先工资卡用的招商银行的金卡,现在新办了个农村信用社的普通卡,有种从城市的大马路走到了胡同里的城中村的感觉。于是大家高高兴兴的蹦蹦跳跳的屁颠屁颠的去领来的新的工资卡。为什么大家会高兴呢?这也是本文的因子,因为大家都已经有两个月没发工资了,新卡到说明工资在不久的将来也会到,大家当然高兴啦。

可是深入想一下,大家真的应该高兴吗?工资本来就是公司单方面拖欠大家的,给大家造成的损失员工宽宏大量没有跟公司计较也就罢了。工资关系到民生,直接影响了员工每个人的生活。要是搁到法国估计一天不发工资,员工早就集体罢工了吧。换卡势必会造成大家的麻烦,原来的银行卡可能就要销户,新的银行是否足够方便。我们却是心宽体胖,公司的错既往不咎,只要看到发工资的希望我照样努力为公司奉献。

这就好比是在中国假期几天高速路不收费,有车一族就喜出望外,“在中国真幸福,可以赚到国家便宜了”。殊不知,世界已建成14万公里收费公路,10万公里在我国。不知道了解这个之后,有车一族还笑得出来吗?我估计连哭的心态都有了。

那大家应该是什么心态去领工资卡呢?平常心就好。至少不至于表现出屁颠屁颠的心态吧。

中国两千多年的封建统治对人民的思想影响是深远的,人们心中早就建立起了人分三六九等的等级观念。而且这种等级观念影响是深远的,悄无声息的深入到我们所谓的“社会主义国家”内部的各个角落。电视里整天的宫廷剧,格格与皇阿玛齐飞,太监共丫鬟一色,这些可是典型的民权不公,民生不平。我们可是就靠这些文化垃圾养大的,心里怎么会没有了奴隶的思想呢?百姓心中的怕官、傍官的思想及考官的行为直接复制到了现在不曾改变,奴隶的心态也就不会改变。

好在现代文明来了,互联网时代来了。西方的文明渐渐深入,我们有的救。打倒xx,打倒xx,期待来一场轰轰烈烈的革新吧。

如果说奴隶的心态为“给你点阳光你就灿烂,给你点洪水你就泛滥”,期待我们的心态变成“没有阳光也要比比灿烂,没有洪水也要试图泛滥”。

题图:《被释放的姜戈》电影海报

Image Title

这是来自一个保守主义者的geeker的傻X的吐槽。

最近股市暴跌,我却在思考为何有股票这个东西。我从未入市且对股市一点兴趣都没有甚至是反感。我所认识的股市游戏规则就是价位低的时候大股东入市,价位高的时候大股东抛售,大股东一吐一吞钱到手了,股民被掏空了。很多股民却用侥幸心理来入市,有赔有赚,出市的时候裤子都赔进去了。

搞不懂那么多经济学专家、博士、硕士、学士,却搞不定一个经济学。若是真搞不懂,索性就不要去研究,反正学了也白学,浪费这个资源误人歧途干啥。一个砖家说熊,另一个砖家说牛,相互对掐有意思吗?

为什么搞股市这个东西,这是嫌经济学不够复杂吗?股市难道能推动人类社会的进步?股市是企业融资的一种手段,而能上市的公司往往是相对不太缺钱的,而最缺钱的是小型创业公司。这也就早就了很多公司把上市套现作为了一个目标,这不明摆着投机取巧,一夜暴富。传销是集合了底层的力量资金而构成了金字塔,企业通过上市手段获取到了股民的资本来运作而发展自身,只不过金字塔只有两层罢了。

假如没有股市,很多上班族就会坐在办公桌上安心工作,而不用时时刻刻关心着像过山车一样的死难看的折线,也不用设个老板键提心吊胆的担心自己的boss悄悄走到自己的面前。难道安安心心全身心投入工作不是更好?

假如没有股市,或许就少了一个行业,一部分人就可以全身心投入到其他行业,带动其他行业的发展,推动历史进步,岂不快哉!

假如没有股市,就不会有人赚发了之后,别人也总想着不劳而获,间接助长了人们投机取巧的气焰。

假如没有股市,就不会因此而发家,当然这是少数。郭美美的母亲也不会用4万块钱赚到100万,也不会有郭美美的今天,也不会有红十字会的今天,当然这只是个笑话。没有股市郭照样可以炫富,因为人家本来就不是靠的股市。没有股市红十字会照样会没落,因为他们的本质就是那样。

假如没有股市,就不会有人因为股市而跳楼的新闻,就不会有人将养老钱都搭进去了,就不会…

请不要职责我,因为我是一个傻X,永远不要和傻子讲道理,否则你也会变成一个傻X!

最近有同事想离职了,上午在跟领导一番谈话之后又有些犹豫了。大概领导会说些公司状况及个人发展之类的话语来说服同事不要离职,用大腿想想都知道的事。同事肯定会说自己的几个抱怨,领导肯定会一一还击,到最后领导领导一摊手“看没什么顾虑的吧,年轻人就是冲动,你再想想吧,先回去好好干活吧”。

我在这里想说的事不要管别人怎么想,做自己,坚持自己的想法。

一千个人眼里有一千个郭美美。有人说郭美美的自拍照真漂亮,有人说郭美美卸妆之后直接毁三观,有人说是郭美美毁掉了红十字会,有人说没有郭美美红十字会一样玩完,有人说郭美美炫富就是为了出名,有人说郭美美是偶像,有人说郭美美是呕像,有人说郭美美为咱们的游戏代言应该很棒,有人说…

同一件事情在不同人眼里就会有不同的看法。当然我的观点,在别人看来也许是那么的反感或不屑一顾。

做技术的天天跟机器打交道,往往语言表达能力较弱,不善辩,更别提忽悠别人了。经过领导只言片语的轰炸,自己已经是茫然不知所措,自己最初的坚持在渐渐退去,别人的想法正在填充你的大脑,恭喜你被洗脑了。但是这种洗脑往往是暂时性的,往往当是生效,事后理性分析一下觉得“不对啊,我当时怎么了”。所以搞传销的一培训就是在一个鸟拉屎的地方培训上几个月来彻底洗脑。

我还好,至少我会坚持自己的观点。记得我上次离职时,领导找我谈话,领导叽里呱啦说了很多,我明知道不对头,但却愣是无力反驳,那时候心里的就想说“求求你,别说了”。

离职对同事自身而言是好事。同事工作两年,在公司工作一年多了,由于公司业绩差,工资非但没有涨过,最近甚至都拖欠了一个月的工资了,而且如果待在公司未来一年内都不涨工资的可能性极大。换个工作薪水可以提高,更可以接触到新鲜的技术,对自己无论在物质还是在技术上都是有利无害。我可以想到的唯一坏处就是舍不得公司,舍不得同事,舍不得那个靠窗的小办公桌。可是这些算个屁,公司的发展跟普通员工毛关系都没有,公司好坏员工拿到的都是稀薄的工资,何况公司已经发不下工资来了,是公司待你不厚道。感情在涉及到金钱后往往变得一文不值。

就公司而言是坏事。公司恨不得不发你工资光给公司干活。领导之所以留你是因为你对公司的业务熟悉,公司如果再重新招人没个一个月的时间很难上手。假如换成了在公司里天天闲的蛋疼的人提离职,公司可能就巴不得立马卷铺盖走人。领导留人只能说明领导对你的肯定,其他的说明不了什么。公司的前途未卜,领导凭什么忽悠员工留下来?

这是一个相互炒鱿鱼的时代。员工跟公司之间是对等的关系,公司对员工不满意可以解除合同,员工对公司不满意照样可以将公司炒掉换个新鲜的工作。

做自己,坚持自己的观点,适当聆听别人的建议,才会赢!

本文是翻译的solrcloud的官方英文文档,本文仅将文中重点翻译,原文地址点这里。英文水平不咋地,翻译篇文章也算练练手。

Image Title

SolrCloud

SolrCloud是Solr的分布式集群。可以通过集群来搭建一个高可用性,容错性的Solr服务。当想搭建一个大规模,容错性,分布式索引,查询性能好的Solr服务时可以采用SolrCloud。

关于SolrCores和Collections的一点小知识

在单机运行时,单独的索引叫做SolrCore。如果想要创建多个索引,可以创建多个SolrCore。利用SolrCloud,一个索引可以存放在不同的Solr服务上。意味着一个单独的索引可以由不同的机器上的SolrCore组成。不同机器上的SolrCore组成了逻辑上的索引,这些SolrCore叫做collection。组成collection的SolrCore包括了数据索引和备份。

例子A: 简单两个shard集群

Image Title
这个例子简单创建了包含两个solr服务的集群,一个collection的数据分布到两个不同的shard上。
因为在这个例子中我们需要两个服务器,这里仅简单的复制example的数据作为第二个服务器,复制example目录之前需要确保里面没有索引数据。

1
2
rm -r example/solr/collection1/data/*
cp -r example example2

下面的命令会启动一个solr服务并启动一个新的solr集群。

1
2
cd example
java -Dbootstrap_confdir=./solr/collection1/conf -Dcollection.configName=myconf -DzkRun -DnumShards=2 -jar start.jar
  • -DzkRun 参数会在solr服务中启动一个内置的zookeeper服务。
  • -Dbootstrap_confdir=./solr/collection1/conf 因为在zookeeper中没有solr配置信息,这一参数会将本地的./solr/conf目录下的配置信息上传到zookeeper中作为myconf配置参数。myconf是在下面的collection.configName参数中指定的。
  • -Dcollection.configName=myconf 为新的collection设置配置名称。如果不加这个参数配置默认名称为configuration1
  • -DnumShards=2 划分索引到逻辑分区的个数。

浏览http://localhost(本地主机):8983/solr/#/~cloud可以看到集群的状态。

通过目录树可以看到配置文件已经上传到了/configs/myconf/目录下,一个叫collection1的collection已经创建,在collection1下是shard的列表,这些shard组成了完整的collection。

接下来准备启动第二个服务器,因为没有明确的设置shard的id,该服务会自动分配到shard2。

启动第二个服务,并将其指向集群。

1
2
cd example2
java -Djetty.port=7574 -DzkHost=localhost:9983 -jar start.jar
  • -Djetty.port=7574来指定Jetty的端口号。
  • -DzkHost=localhost:9983用来指定Zookeeper集群。在本例中,在第一个Solr服务中运行了一个单独的Zookeeper服务。默认情况下,Zookeeper的端口号为Solr服务的端口号加上1000,即9983。

通过访问http://localhost(本地主机):8983/solr/#/~cloud,在collection1中就可以看到shard1和shard2。

Image Title

下面对一些文档建立索引。

1
2
3
4
5
6
7
cd exampledocs

java -Durl=http://localhost:8983/solr/collection1/update -jar post.jar ipod_video.xml

java -Durl=http://localhost:8983/solr/collection1/update -jar post.jar monitor.xml

java -Durl=http://localhost:8983/solr/collection1/update -jar post.jar mem.xml

无论是向集群中的任何一台服务器请求都会得到全部的collection:http://localhost:8983/solr/collection1/select?q=*:*

假如想更改配置,可以在关闭所有服务之后删除solr/zoo_data目录下的所有内容。

实际测试插入速度要比单个服务慢。

例子B:简单的两个shard重复的shard集群

Image Title
本例子会通过复制shard1和shard2来创建上一个例子。额外的shard备份可以有高可用性和容错性,简单提升索引的查询能力。

首先,在运行先前的例子中我们已经有了两个shard和一些索引文档。然后简单的复制这两个服务:

1
2
cp -r example exampleB
cp -r example2 example2B

然后,在不同的端口上启动两个新的服务:

1
2
cd exampleB
java -Djetty.port=8900 -DzkHost=localhost:9983 -jar start.jar
1
2
cd example2B
java -Djetty.port=7500 -DzkHost=localhost:9983 -jar start.jar

重新浏览网址http://localhost(本地主机):8983/solr/#/~cloud,检查四个solr节点是否已经都启动。
Image Title
因为我们已经告诉Solr我们需要两个逻辑上的shard,启动后的实例3和4会自动的成为原来shard的备份。

向集群中的任意一个服务发起查询:http://localhost:7500/solr/collection1/select?q=*:*。多次发起这个查询并查看solr服务的日志。可以观察到Solr通过备份对请求做了平衡,通过不同的服务来处理请求。

为了证明高可用性,在除了运行Zookeeper的服务上按下CTRL-C。(在例子C中将会讨论Zookeeper的冗余)当服务终止后,发送另外一个查询请求到其他服务,仍然能够看到所有的结果。

在没一个shard至少还有一个服务时,SolrCloud仍然可以提供服务。可以通过关闭每一个实例来查看结果。假如关闭了一个shard的所有的服务,到其他服务的请求就会收到503错误。为了能够返回其他可用的shard中的文档,可以在请求中增加参数:shards.tolerant=true

SolrCloud用leaders和overseer来作为具体的实现。一些节点或备份将会扮演特殊的角色。不需要担心杀死了leader或overseer,假如杀死了其中的一个,集群会自动选择一个新的leader或overseer,并自动接管工作。任何的Solr实例都可以成为这种角色。

例子C:两个shard集群,shard带备份和zookeeper集群

Image Title

在例子B中问题是虽然有足够的Solr服务器可以避免集群挂掉,但是仅有一个zookeeper服务来维持集群的状态。假如zookeeper服务挂掉了,分布式的查询还是可以工作的,因为solr服务记录了zookeeper最后一次报告的状态。问题是没有新的服务器或客户端能发现集群的状态,集群的状态也不会改变。

运行多个zookeeper服务可以保证zookeeper服务具有高可用性。每一个zookeeper服务需要知道集群中的其他服务,大部分服务需要提供服务。例如,一个含有三个zookeeper服务的集群允许其中一个失败剩余的两个仍然可以提供服务。五个zookeeper服务的集群可以允许一次失败两个。

从产品角度考虑,推荐使用单独的zookeeper服务而不是solr服务中集成的zookeeper服务。你可以从这里读取到更多的zookeeper集群。在这个简单的例子中,我们仅简单的使用了集成的zookeeper。

首先,停止四个服务,并清空zookeeper中的数据作为一个新的开始。

1
rm -r example*/solr/zoo_data

我们仍然将服务分别运行在8983,7574,8900,7500端口。默认是在端口号+1000的端口上启动一个zookeeper服务,第一次运行的时候在另外三台服务器上zookeeper的地址分别为:localhost:9983,localhost:8574,localhost:9900。

为了方便通过第一个服务上传solr的配置到zookeeper集群中。在第二个zookeeper服务启动之前程序会阻塞。这是因为zookeeper在工作的时候需要其他服务。

1
2
3
cd example

java -Dbootstrap_confdir=./solr/collection1/conf -Dcollection.configName=myconf -DzkRun -DzkHost=localhost:9983,localhost:8574,localhost:9900 -DnumShards=2 -jar start.jar
1
2
cd example2
java -Djetty.port=7574 -DzkRun -DzkHost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar
1
2
cd exampleB
java -Djetty.port=8900 -DzkRun -DzkHost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar
1
2
cd example2B
java -Djetty.port=7500 -DzkHost=localhost:9983,localhost:8574,localhost:9900 -jar start.jar

现在我们运行了三个内置的zookeeper服务,如果一个服务挂掉之后其他一切正常。为了证明,在exampleB上按下CTRL+C杀掉服务,然后浏览http://localhost:8983/solr/#/~cloud来核实zookeeper服务仍然可以工作。

需要注意的是,当运行在多个机器上,需要在每一台机器上设置-DzkRun=hostname:port属性。

ZooKeeper

多个zookeeper服务同时运行来避免错误和高可用性叫做ensemble。从产品角度,推荐运行外部的zookeeper ensemble来代替solr集成的zookeeper。浏览zookeeper官方网站下载和运行一个zookeeper ensemble。可以参考Getting StartedZooKeeper Admin。非常简单就可以运行。可以坚持使用solr来运行zookeeper集群,但是必须知道zookeeper集群不是非常容易动态改变的。除非solr增加对zookeeper更好的支持,重新开始是最好的改变方式。zookeeper和solr是两个不同的进程是最好的方式。

当solr运行内置的zookeeper服务时,默认会使用solr服务的端口号加上1000作为zookeeper的客户端端口号。另外,默认会增加一个zookeeper的客户端端口号和两个zookeeper的选举端口号。所以在第一个例子中,solr运行在8983端口,内置的zookeeper服务运行在9983端口作为客户端端口,9984和9985作为服务端端口。

当增加了更多zookeeper节点可以提高读性能,但是会稍微降低写性能。当集群状态稳定的时候,Solr用的Zookeeper非常少。下面有一些优化zookeeper的建议:

  1. 最好的情况是zookeeper有一个专用的机器。zookeeper是一个准时的服务,专用的机器可以确保及时响应。当然专用的机器不是必须的。
  2. 当把事务日志和snap-shots放到不同的磁盘上可以提高性能。
  3. 假如zookeeper和solr运行在同一台机器上,利用不同的磁盘可以提高性能。

参考文档

https://wiki.apache.org/solr/SolrCloud

前几天写了篇《在Linux上搭建solr环境》的博文,是基于solr3.6.2的安装。本文仅记录在tomcat7.0.41上搭建solr4.3.1搭建过程中需要注意的地方,其他地方可以参考上一篇博文。

配置完成之后发现http://192.168.20.38:8090/solr无法访问,但是http://192.168.20.38:8090/却可以访问,通过查看tomcat的日志文件localhost.2013-07-03.log,发现里面有如下错误提示。

1
2
严重: Exception starting filter SolrRequestFilter
org.apache.solr.common.SolrException: Could not find necessary SLF4j logging jars. If using Jetty, the SLF4j logging jars need to go in the jetty lib/ext directory. For other containers, the corresponding directory should be used. For more information, see: http://wiki.apache.org/solr/SolrLogging

解决办法:将/solr-4.3.1/example/lib/ext目录下的所有jar文件复制到/apache-tomcat-7.0.41/lib目录下,然后重启tomcat即可。

相关下载

用到的文件

本文以《在Linux上搭建solr环境》为基础,假设已经在192.168.20.6和192.168.20.38上搭建了单机版solr环境。

主服务器配置

找到solr的环境目录下的conf文件夹下的solrconfig.xml文件,我的是在/hadoop/solr/conf/solrconfig.xml目录下,打开后找到如下行

1
<requestHandler name="/replication" class="solr.ReplicationHandler" >

默认是被注释的,将其修改为

1
2
3
4
5
6
7
<requestHandler name="/replication" class="solr.ReplicationHandler" >
<lst name="master">
<str name="replicateAfter">commit</str>
<str name="replicateAfter">startup</str>
<str name="confFiles">schema.xml,stopwords.txt</str>
</lst>
</requestHandler>

replicateAfter表示solr会在什么情况下复制,可选项包括:commit、startup、optimize,这里保持默认。
confFiles表示要分发的配置文件。

从服务器配置

在从服务器上,将/hadoop/solr/conf/solrconfig.xml文件相应的修改为

1
2
3
4
5
6
<requestHandler name="/replication" class="solr.ReplicationHandler" >
<lst name="slave">
<str name="masterUrl">http://192.168.20.6:8080/solr/replication</str>
<str name="pollInterval">00:00:60</str>
</lst>
</requestHandler>

masterUrl为服务器的url地址。
pollInterval为从服务器的同步时间间隔。

本文采用Linux操作系统在hadoop用户下安装,solr采用3.x中的最新版本3.6.2,tomcat采用6.0.37版本,安装包可以从本文下方链接下载。
这里有两种安装方式,一种方式为利用solr自带的jetty来启动solr,默认端口为8983。另外一种方式为将solr集成到tomcat中。其中第一种方式较为简单,推荐新手采用。

独立启动

  1. 将sorl的安装包解压到用户的根目录下,解压后文件夹为apache-solr-3.6.2。
  2. 进入到example目录下,执行java -jar start.jar命令,solr服务启动,端口为8983。
  3. 通过http://IP地址:8983/solr/来访问solr的web页面,进入admin页面后可以通过输入字符串来查找索引。查找索引默认显示的格式为xml格式,可以通过在url的后面加上参数wt=json来显示json格式的结果。

利用tomcat

安装tomcat

1. 将apache-tomcat-6.0.37.tar.gz解压到hadoop的跟目录下。
2. 修改hadoop用户的环境变量,执行vi ~/.bash_profile命令,添加如下:

1
2
3
export CATALINA_HOME=/home/hadoop/apache-tomcat-6.0.37
export CLASSPATH=.:$JAVA_HOME/lib:$CATALINA_HOME/lib
export PATH=$PATH:$CATALINA_HOME/bin

3. 执行source ~/.bash_profile 使修改的环境变量生效。
4. 执行tomcat的bin目录下的startup.bat脚本来启动tomcat。
5. 通过netstat -anp | grep 8080命令查看tomcat是否启动。

安装solr

1. 将solr的dist/apache-solr-3.6.2.war文件复制到tomcat的webapps目录下,并将文件命名为solr.war。执行cp ~/apache-solr-3.6.2/dist/apache-solr-3.6.2.war ~/apache-tomcat-6.0.37/webapps/solr.war命令。WAR是一个完整的web应用程序,包括了Solr的jar文件和所有运行Solr所依赖的Jar文件,Jsp和很多的配置文件与资源文件。

2. 修改~/apache-tomcat-6.0.37/conf/server.xml文件相应行的内容如下:

1
2
3
4
<Connector port="8080" protocol="HTTP/1.1" 
connectionTimeout="20000"
URIEncoding="UTF-8"
redirectPort="8443" />

增加URIEncoding="UTF-8"来支持中文。这是因为solr基于xml,json,javabin,php,python等多种格式传输请求和返回结果。

3.复制~/apache-solr-3.6.2/example/solr目录到/home/hadoop/solr位置。该位置为solr的应用环境目录。

4. 修改/home/hadoop/solr/conf/solrconfig.xml文件中的dataDir一行内容为:

1
<dataDir>${solr.data.dir:/home/hadoop/solr/data}</dataDir>

目的是为了指定存放索引数据的路径。

5. 在~/apache-tomcat-6.0.37/conf/Catalina/localhost目录下新建文件solr.xml。增加内容如下:

1
2
3
<Context docBase="/home/hadoop/apache-tomcat-6.0.37/webapps/solr.war" debug="0" crossContext="true" >
<Environment name="solr/home" type="java.lang.String" value="/home/hadoop/solr" override="true" />
</Context>

其中docBase为tomcat的webapps下的solr.war完整路径。Environment的value属性的值为存放solr索引的文件夹,即第三步中复制的文件夹。
需要注意的是:Catalina目录在首次启动tomcat时创建,因此在此步骤前需要启动过tomcat。

6. 在tomcat的bin目录下通过startup.sh启动tomcat。

7. 通过http://IP地址:8080/solr/来访问solr的web页面。

相关命令

放入数据到solr中

在apache-solr-3.6.2/example/exampledocs目录下,执行java -jar post.jar 要存放的文件名。这里自己新建一个文件test.xml放入到solr中,文件内容如下:

1
2
3
4
5
6
<add>
<doc>
<field name="id">company</field>
<field name="text">kaitone</field>
</doc>
</add>

执行java -jar post.jar test.xml将数据放入solr中。

删除数据

新建文本文件test_delete.xml,内容如下

1
2
3
<delete>
<id>company</id>
</delete>

执行java -jar post.jar test_delete.xml将数据从solr中删除。
另外还可以通过命令行的方式来删除,命令为java -Ddate=args -jar post.jar '<delete><id>company</id></delete>'

在Eclipse中搭建环境操作Solr api

1. 新建一个java工程
2. 在工程中引入如下包:

  • commons-httpclient-3.1.jar
  • commons-codec-1.6.jar
  • apache-solr-solrj-3.6.2.jar
  • slf4j-api-1.6.1.jar
  • slf4j-log4j12-1.6.1.jar
  • commons-logging-1.1.3.jar
  • log4j-1.2.12.jar
  • httpclient-4.2.5.jar
  • httpcore-4.2.4.jar
  • httpmime-4.2.5.jar

其中commons-httpclient-3.1.jarcommons-codec-1.6.jarapache-solr-solrj-3.6.2.jarslf4j-api-1.6.1.jar可以从solr的目录apache-solr-3.6.2中的dist目录下找到。

slf4j-log4j12-1.6.1.jar可以从slf4j的压缩包中slf4j-1.6.1.tar.gz找到。

commons-logging-1.1.3.jar可以从slf4j的压缩包中commons-logging-1.1.3-bin.zip找到。

log4j-1.2.12.jar可以从log4j的压缩包中logging-log4j-1.2.12.tar.gz找到。

httpclient-4.2.5.jarhttpcore-4.2.4.jarhttpmime-4.2.5.jarhttpcomponents-client-4.2.5-bin.tar.gz文件中。

具体的API编程可以参考Solr开发文档

在linux上编译并执行程序

1. 将工程中用到的jar包复制到Linux机器上,这里复制到/home/hadoop/test_solr/lib目录下。

2. 将测试程序的源码放到Linux机器上,这里复制到/home/hadoop/test_solr目录下。其中源码包括三个文件:SolrTest.java、SolrClient.java、Index.java。该三个文件将会包含在下面相关下载中的Eclipse工程中。

3. 在/home/hadoop/test_solr目录下执行

1
javac -cp lib/apache-solr-solrj-3.6.2.jar:lib/commons-httpclient-3.1.jar:lib/log4j-1.2.12.jar:lib/commons-codec-1.6.jar:lib/commons-logging-1.1.3.jar:lib/slf4j-api-1.6.1.jar:lib/httpclient-4.2.5.jar:lib/httpcore-4.2.4.jar:lib/httpmime-4.2.5.jar:. SolrTest.java

其中-cp等同于-classpath参数,指定编译SolrTest.java文件需要的ClassPath路径,不要忘记路径后面的.表示当前路径,否则找不到当前目录下的其他java文件。
命令执行后会在/home/hadoop/test_solr目录下生成Index.class、SolrClient.class、SolrTest.class三个class文件。

4. 在/home/hadoop/test_solr目录下执行

1
java -cp lib/apache-solr-solrj-3.6.2.jar:lib/commons-httpclient-3.1.jar:lib/log4j-1.2.12.jar:lib/commons-codec-1.6.jar:lib/commons-logging-1.1.3.jar:lib/slf4j-api-1.6.1.jar:lib/httpclient-4.2.5.jar:lib/httpcore-4.2.4.jar:lib/:httpmime-4.2.5.jar:. SolrTest

来运行程序。

在Linux上打包并执行

1. 在上面步骤基础上,为了方便执行,可以将class文件打成jar包来执行,这样在使用java命令执行的时候就不用指定classpath路径了,只需要在jar包的MANIFEST.MF文件中指定classpath。

2. 在/home/hadoop/test_solr下新建一个文件,文件名可以随便,这里取名为MANIFEST.MF,与生成的jar包中的文件名一致,文件内容为

1
2
3
4
5
6
7
Manifest-Version: 1.0
Created-By: 1.6.0_10 (Sun Microsystems Inc.)
Main-Class: SolrTest
Class-Path: /home/hadoop/test_solr/lib/apache-solr-solrj-3.6.2.jar /home/hadoop/test_solr/lib/commons-httpclient-3.1.jar /home/hadoop/test_solr/lib/log4j-1.2.12.jar /home/hadoop/test_solr/lib/commons-codec-1.6.jar /home/hadoop/test_solr/lib/commons-logging-1.1.3.jar /home/hadoop/test_solr/lib/slf4j-api-1.6.1.jar
/home/hadoop/test_solr/lib/httpclient-4.2.5.jar
/home/hadoop/test_solr/lib/httpcore-4.2.4.jar
/home/hadoop/test_solr/lib/httpmime-4.2.5.jar

其中Main-Class指定main函数所在的类。
Class-Path指定用到的jar所在的路径。其中Class-Path的各个jar文件之间通过空格分隔而不是通过:分隔。

3. 将class文件打包成jar文件。执行

1
jar -cfm solrtest.jar MANIFEST.MF Index.class SolrClient.class SolrTest.class 

会在此目录下生成solrtest.jar文件。jar命令会根据指定的MANIFEST.MF文件来产生jar包中的META-INF/MANIFEST.MF文件。两个文件内容并不完全一致,jar命令会根据格式对内容进行调整。

4. 运行jar文件。通过java -jar solrtest.jar来执行。

相关下载

本文中用到的安装包

参考文档

简单的Solr安装配置
官方安装教程
Solr初体验系列讲的非常详细,适合初学者
Solr开发文档

通过Eclipse编写java程序久了,发现已经不会用命令来编译java程序了。今天在windows下搭建了一个solr环境,想放到linux下去跑一下,在windows上打成jar包后放在linux下不能运行,是时候回顾一下java的编译命令了。而且网上的资料比较零散,没有特别系统的资料。

本文在linux测试,同windows下的命令行工具差别不大。

编译并执行单个文件

1. 在目录下~/test_java/com/kuring下新建HelloWorld.java的文件,文件内容为

1
2
3
4
5
6
7
package com.kuring;

public class HellowWorld {
public static void main(String[] args) {
System.out.println("hello world");
}
}

2. 在目录~/test_java下执行javac com/kuring/HelloWorld.java命令来编译文件。此时会在HelloWorld.java文件所在的目录下生成HelloWorld.class的二进制文件。

3. 在目录~/test_java下执行java com.kuring.HelloWorld来执行HelloWorld.class。屏幕会输出hello world,说明文件执行成功。
也可以在任意路径下指定classpath路径来执行,命令为java -classpath ~/test_java com.kuring.HelloWorld,其中classpath指定了类的搜索路径。

编译并执行多个文件

1. 在目录下~/test_java/com/kuring下新建HelloWorld2.java和Main.java的文件,HelloWorld2.java文件内容为

1
2
3
4
5
6
7
package com.kuring;

public class HellowWorld2 {
public void print() {
System.out.println("hello world too");
}
}

Main.java的文件内容为

1
2
3
4
5
6
7
8
package com.kuring;

public class Main {
public static void main(String[] args) {
HelloWorld2 hello = new HelloWorld2();
hello.print();
}
}

2. 在目录~/test_java下执行javac com/kuring/Main.java命令来编译文件。此时会在Main.java文件所在的目录下生成Main.class和HelloWorld2.class两个文件,可以看出javac有自动推导编译的功能。

3. 在目录~/test_java下执行java com.kuring.Main。屏幕会输出hello world too,说明文件执行成功。

打包

将上述例子中的程序打成jar包,可以在~/test_java目录下通过执行命令jar cvf my.jar com来生成jar文件。其中my.jar为要生成的jar文件的名字。
通过java -classpath my.jar com.kuring.Main来执行jar文件。
上述命令需要指定要执行的类名Main,如果想通过java -jar my.jar命令即可执行程序需要在jar包的META-INF/MANIFEST.MF文件中增加一行

1
Main-Class: SolrTest

来执行含有main函数的类。然后通过jar -cfm my.jar MANIFEST.MF路径 要打包的目录或文件来重新生成jar包。这样就可以通过java -jar my.jar来执行jar包了。

关于如何创建并执行引用了其他jar包的jar包,可以参考我的另外一篇博客《在Linux上搭建solr环境》,这里不再赘述。

常用jar命令

功能 命令
用一个单独的文件创建一个 JAR 文件 jar cf jar-file input-file...
用一个目录创建一个 JAR 文件 jar cf jar-file dir-name
创建一个未压缩的 JAR 文件 jar cf0 jar-file dir-name
更新一个 JAR 文件 jar uf jar-file input-file...
查看一个 JAR 文件的内容 jar tf jar-file
提取一个 JAR 文件的内容 jar xf jar-file
从一个 JAR 文件中提取特定的文件 jar xf jar-file archived-file...
运行一个打包为可执行 JAR 文件的应用程序 java -jar app.jar

参考文档

JAR 文件揭密
Java程序的编译、执行和打包

本文选择安装的hadoop版本为网上资料较多的0.20.2,对于不懂的新技术要持保守态度。遇到问题解决问题的痛苦远比体会用不着功能的新版本的快感来的更猛烈。

安装环境

本文选择了三台机器来搭建hadoop集群,1个Master和2个Slave。本文中的master主机即namenode所在的机器,slave即datanode所在的机器。节点的机器名和IP地址如下

机器名 IP地址 用途 运行模块
server206 192.168.20.6 Master NameNode、JobTracker、SecondaryNameNode
ap1 192.168.20.36 Slave DataNode、TaskTracker
ap2 192.168.20.38 Slave DataNode、TaskTracker

安装Java

  1. 检查本机是否已安装Java
    在命令行中输入java -version判断是否已经安装。如果已经安装检查Java的版本,某些操作系统在安装的时候会安装Jdk,但可能版本会太低。如果版本过低,需要将旧的版本删除。在Redhat操作系统中可以通过rpm命令来删除系统自带的Jdk。
  2. 安装java
    本文选择jdk1.6安装,将解压出的文件夹jdk1.6.0_10复制到/usr/java目录下。
  3. 设置java的环境变量
    添加系统环境变量,修改/etc/profile文件,在文件末尾添加如下内容:

    export JAVA_HOME=/usr/java/jdk1.6.0_10
    export CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
    export PATH=$JAVA_HOME/bin:$PATH
    export JRE_HOME=$JAVA_HOME/jre

    修改完profile文件后要执行source /etc/profile命令才能使刚才的修改在该命令行环境下生效。
  4. 检查java是否安装成功
    在命令行中输入java -version、javac命令来查看是否安装成功及安装版本。

配置hosts文件

本步骤必须操作,需要root用户来操作,修改完成之后立即生效。在三台机器的/etc/hosts文件末尾添加如下内容:

192.168.20.6 server206
192.168.20.36 ap1
192.168.20.38 ap2

修改完成之后可以通过ping 主机名的方式来测试hosts文件是否正确。

新建hadoop用户

在三台机器上分别新建hadoop用户,该用户的目录为/home/hadoop。利用useradd命令来添加用户,利用passwd命令给用户添加密码。

配置SSH免登录

该步骤非必须,推荐配置,否则在Master上执行start-all.sh命令来启动hadoop集群的时候需要手动输入ssh密码,非常麻烦。
原理:用户将自己的公钥储存在远程主机上。登录的时候,远程主机会向用户发送一段随机字符串,用户用自己的私钥加密后,再发回来。远程主机用事先储存的公钥进行解密,如果成功,就证明用户是可信的,直接允许登录shell,不再要求输入密码。
在本例中,需要实现的是192.168.20.6上的hadoop用户可以无密码登录自己、192.168.20.36和192.168.20.38的hadoop用户。需要将192.168.20.6上的ssh公钥复制到192.168.20.36和192.168.20.38机器上。

  1. 在192.168.20.6上执行ssh-keygen –t rsa命令来生成ssh密钥对。会在/home/hadoop/.ssh目录下生成id_rsa.pub和id_rsa两个文件,其中id_rsa.pub为公钥文件,id_rsa为私钥文件。
  2. 在192.168.20.6上执行cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys命令将公钥添加到授权的key里。
  3. 权限设置。在192.168.20.6上执行chmod 600 ~/.ssh/authorized_keys来修改authorized_keys文件的权限,执行chmod 700 ~/.ssh命令将.ssh文件夹的权限设置为700。如果权限不对无密码登录就配置不成功,而且没有错误提示,这一步特别注意。
  4. 在本机上测试是否设置无密码登录成功。在192.168.20.6上执行ssh -p 本机SSH服务端口号 localhost,如果不需要输入密码则登录成功。
  5. 利用scp命令将192.168.20.6上的公钥文件id_rsa.pub追加到192.168.20.36和192.168.20.38机器上的~/.ssh/authorized_keys文件中。scp命令的格式如下:
    1
    scp -P ssh端口号 ~/.ssh/id_rsa.pub hadoop@192.168.20.36:~/id_rsa.pub
    在192.168.20.36和192.168.20.38机器上分别执行cat ~/id_rsa.pub >> ~/.ssh/authorized_keys命令将192.168.20.6机器上的公钥添加到authorized_keys文件的尾部。
  6. 配置无密码登录完成,在Master机器上执行ssh -P 本机SSH服务端口号 要连接的服务器IP地址命令进行测试。

搭建单机版hadoop

在192.168.20.6上首先搭建单机版hadoop进行测试。

  1. 将hadoop-0.20.2.tar.gz文件解压到hadoop用户的目录下。
  2. 配置hadoop的环境变量。修改/etc/profile文件,在文件的下面加入如下:

    HADOOP_HOME=/home/hadoop/hadoop-0.20.2
    export HADOOP_HOME
    export HADOOP=$HADOOP_HOME/bin
    export PATH=$HADOOP:$PATH

修改完成之后执行source /etc/profile使修改的环境变量生效。
3. 配置hadoop用到的java环境变量
修改conf/hadoop-env.sh文件,添加export JAVA_HOME=/usr/java/jdk1.6.0_10
4. 修改conf/core-site.xml的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://localhost:9000</value>
</property>

<property>
<name>dfs.replication</name>
<value>1</value>
</property>

<property>
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/hadoop-0.20.2/tmp</value>
</property>
</configuration>
  1. 修改/conf/mapred-site.xml的内容如下:
    1
    2
    3
    4
    5
    6
    <configuration>
    <property>
    <name>mapred.job.tracker</name>
    <value>localhost:9001</value>
    </property>
    </configuration>
  2. 至此单击版搭建完毕。可以通过hadoop自带的wordcount程序测试是否运行正常。下面为运行wordcount例子的步骤。
  3. 在hadoop目录下新建input文件夹。
  4. 将conf目录下的内容拷贝到input文件夹下,执行cp conf/* input
  5. 通过start-all.sh脚本来启动单机版hadoop。
  6. 执行wordcount程序:hadoop jar hadoop-0.20.2-examples.jar wordcount input output
  7. 通过stop-all.sh脚本来停止单机版hadoop。

搭建分布式hadoop

在上述基础之上,在192.168.20.6上执行如下操作。
1. 修改/home/hadoop/hadoop-0.20.2/conf目录下的core-site.xml文件。

1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://192.168.20.6:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/home/hadoop/hadoop-0.20.2/tmp</value>
</property>
</configuration>

如没有配置hadoop.tmp.dir参数,此时系统默认的临时目录为:/tmp/hadoo-hadoop。而这个目录在每次重启后都会被干掉,必须重新执行format才行,否则会出错。
2. 修改/home/hadoop/hadoop-0.20.2/conf目录下的hdfs-site.xml文件。

1
2
3
4
5
6
7
8
9
10
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
<property>
<name>dfs.support.append</name>
<value>true</value>
</property>
</configuration>

3. 修改/home/hadoop/hadoop-0.20.2/conf目录下的mapred-site.xml文件。

1
2
3
4
5
6
<configuration>
<property>
<name>mapred.job.tracker</name>
<value>http://192.168.20.6:9001</value>
</property>
</configuration>

4. 修改/home/hadoop/hadoop-0.20.2/conf目录下的masters文件。
将Master机器的IP地址或主机名添加进文件,如192.168.20.6。
5. 修改/home/hadoop/hadoop-0.20.2/conf目录下的slaves文件。Master主机特有
在其中将slave节点的Ip地址或主机名添加进文件中,本例中加入

1
2
192.168.20.36
192.168.20.38

6. hadoop主机的master主机已经配置完毕,利用scp命令将hadoop-0.20.2目录复制到两台slave机器的hadoop目录下。命令为:scp -r /home/hadoop hadoop@服务器IP:/home/hadoop/。注意slaves文件在master和slave机器上是不同的。

常用命令

参考文档

下载链接

http://pan.baidu.com/share/link?shareid=1235031445&uk=3506813023 提取码:v8ok

本文选择四台机器作为集群环境,hadoop采用0.20.2,HBase采用0.90.2,zookeeper采用独立安装的3.3.2稳定版。本文所采用的数据均为简单的测试数据,如果插入的数据量大可能会对结果产生影响。集群环境部署情况如下:

机器名 IP地址 用途 Hadoop模块 HBase模块 ZooKeeper模块
server206 192.168.20.6 Master NameNode、JobTracker、SecondaryNameNode HMaster QuorumPeerMain
ap1 192.168.20.36 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain
ap2 192.168.20.38 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain
ap2 192.168.20.8 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain

单线程插入100万行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;


public class InsertRowThreadTest {

private static Configuration conf = null;

private static String tableName = "blog";

static {
Configuration conf1 = new Configuration();
conf1.set("hbase.zookeeper.quorum", "server206,ap1,ap2");
conf1.set("hbase.zookeeper.property.clientPort", "2181");
conf = HBaseConfiguration.create(conf1);
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 列族
String[] familys = {"article", "author"};
// 创建表
try {
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
System.out.println("表已经存在,首先删除表");
admin.disableTable(tableName);
admin.deleteTable(tableName);
}

HTableDescriptor tableDesc = new HTableDescriptor(tableName);
for(int i=0; i<familys.length; i++){
HColumnDescriptor columnDescriptor = new HColumnDescriptor(familys[i]);
tableDesc.addFamily(columnDescriptor);
}
admin.createTable(tableDesc);
System.out.println("创建表成功");
} catch (Exception e) {
e.printStackTrace();
}

// 向表中插入数据
long time1 = System.currentTimeMillis();
System.out.println("开始向表中插入数据,当前时间为:" + time1);

for (int i=0; i<1; i++) {
InsertThread thread = new InsertThread(i * 1000000, 1000000, "thread" + i, time1);
thread.start();
}
}

public static class InsertThread extends Thread {

private int beginSite;

private int insertCount;

private String name;

private long beginTime;

public InsertThread(int beginSite, int insertCount, String name, long beginTime) {
this.beginSite = beginSite;
this.insertCount = insertCount;
this.name = name;
this.beginTime = beginTime;
}

@Override
public void run() {
HTable table = null;
try {
table = new HTable(conf, Bytes.toBytes(tableName));
table.setAutoFlush(false);
table.setWriteBufferSize(1 * 1024 * 1024);
} catch (IOException e1) {
e1.printStackTrace();
}

System.out.println("线程" + name + "从" + beginSite + "开始插入");

List<Put> putList = new ArrayList<Put>();
for (int i=beginSite; i<beginSite + insertCount; i++) {
Put put = new Put(Bytes.toBytes("" + i));
put.add(Bytes.toBytes("article"), Bytes.toBytes("tag"), Bytes.toBytes("hadoop"));
putList.add(put);
if (putList.size() > 10000) {
try {
table.put(putList);
table.flushCommits();
} catch (IOException e) {
e.printStackTrace();
}
putList.clear();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
table.put(putList);
table.flushCommits();
table.close();
} catch (IOException e) {
System.out.println("线程" + name + "失败");
e.printStackTrace();
}

long currentTime = System.currentTimeMillis();
System.out.println("线程" + name + "结束,用时" + (currentTime - beginTime));
}
}
}

测试5次的结果分布图如下:
Image Title
其中Y轴单位为毫秒。平均速度在1秒插入3万行记录。

10个线程每个线程插入10万行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;


public class InsertRowThreadTest {

private static Configuration conf = null;

private static String tableName = "blog";

static {
Configuration conf1 = new Configuration();
conf1.set("hbase.zookeeper.quorum", "server206,ap1,ap2");
conf1.set("hbase.zookeeper.property.clientPort", "2181");
conf = HBaseConfiguration.create(conf1);
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 列族
String[] familys = {"article", "author"};
// 创建表
try {
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
System.out.println("表已经存在,首先删除表");
admin.disableTable(tableName);
admin.deleteTable(tableName);
}

HTableDescriptor tableDesc = new HTableDescriptor(tableName);
for(int i=0; i<familys.length; i++){
HColumnDescriptor columnDescriptor = new HColumnDescriptor(familys[i]);
tableDesc.addFamily(columnDescriptor);
}
admin.createTable(tableDesc);
System.out.println("创建表成功");
} catch (Exception e) {
e.printStackTrace();
}

// 向表中插入数据
long time1 = System.currentTimeMillis();
System.out.println("开始向表中插入数据,当前时间为:" + time1);

for (int i=0; i<10; i++) {
InsertThread thread = new InsertThread(i * 100000, 100000, "thread" + i, time1);
thread.start();
}
}

public static class InsertThread extends Thread {

private int beginSite;

private int insertCount;

private String name;

private long beginTime;

public InsertThread(int beginSite, int insertCount, String name, long beginTime) {
this.beginSite = beginSite;
this.insertCount = insertCount;
this.name = name;
this.beginTime = beginTime;
}

@Override
public void run() {
HTable table = null;
try {
table = new HTable(conf, Bytes.toBytes(tableName));
table.setAutoFlush(false);
table.setWriteBufferSize(1 * 1024 * 1024);
} catch (IOException e1) {
e1.printStackTrace();
}

System.out.println("线程" + name + "从" + beginSite + "开始插入");

List<Put> putList = new ArrayList<Put>();
for (int i=beginSite; i<beginSite + insertCount; i++) {
Put put = new Put(Bytes.toBytes("" + i));
put.add(Bytes.toBytes("article"), Bytes.toBytes("tag"), Bytes.toBytes("hadoop"));
putList.add(put);
if (putList.size() > 10000) {
try {
table.put(putList);
table.flushCommits();
} catch (IOException e) {
e.printStackTrace();
}
putList.clear();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
table.put(putList);
table.flushCommits();
table.close();
} catch (IOException e) {
System.out.println("线程" + name + "失败");
e.printStackTrace();
}

long currentTime = System.currentTimeMillis();
System.out.println("线程" + name + "结束,用时" + (currentTime - beginTime));
}
}
}

耗时分布图为:
Image Title
结果比单线程插入有提升。

20个线程每个线程插入5万行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;


public class InsertRowThreadTest {

private static Configuration conf = null;

private static String tableName = "blog";

static {
Configuration conf1 = new Configuration();
conf1.set("hbase.zookeeper.quorum", "server206,ap1,ap2");
conf1.set("hbase.zookeeper.property.clientPort", "2181");
conf = HBaseConfiguration.create(conf1);
}

/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
// 列族
String[] familys = {"article", "author"};
// 创建表
try {
HBaseAdmin admin = new HBaseAdmin(conf);
if (admin.tableExists(tableName)) {
System.out.println("表已经存在,首先删除表");
admin.disableTable(tableName);
admin.deleteTable(tableName);
}

HTableDescriptor tableDesc = new HTableDescriptor(tableName);
for(int i=0; i<familys.length; i++){
HColumnDescriptor columnDescriptor = new HColumnDescriptor(familys[i]);
tableDesc.addFamily(columnDescriptor);
}
admin.createTable(tableDesc);
System.out.println("创建表成功");
} catch (Exception e) {
e.printStackTrace();
}

// 向表中插入数据
long time1 = System.currentTimeMillis();
System.out.println("开始向表中插入数据,当前时间为:" + time1);

for (int i=0; i<20; i++) {
InsertThread thread = new InsertThread(i * 50000, 50000, "thread" + i, time1);
thread.start();
}
}

public static class InsertThread extends Thread {

private int beginSite;

private int insertCount;

private String name;

private long beginTime;

public InsertThread(int beginSite, int insertCount, String name, long beginTime) {
this.beginSite = beginSite;
this.insertCount = insertCount;
this.name = name;
this.beginTime = beginTime;
}

@Override
public void run() {
HTable table = null;
try {
table = new HTable(conf, Bytes.toBytes(tableName));
table.setAutoFlush(false);
table.setWriteBufferSize(1 * 1024 * 1024);
} catch (IOException e1) {
e1.printStackTrace();
}

System.out.println("线程" + name + "从" + beginSite + "开始插入");

List<Put> putList = new ArrayList<Put>();
for (int i=beginSite; i<beginSite + insertCount; i++) {
Put put = new Put(Bytes.toBytes("" + i));
put.add(Bytes.toBytes("article"), Bytes.toBytes("tag"), Bytes.toBytes("hadoop"));
putList.add(put);
if (putList.size() > 10000) {
try {
table.put(putList);
table.flushCommits();
} catch (IOException e) {
e.printStackTrace();
}
putList.clear();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
try {
table.put(putList);
table.flushCommits();
table.close();
} catch (IOException e) {
System.out.println("线程" + name + "失败");
e.printStackTrace();
}

long currentTime = System.currentTimeMillis();
System.out.println("线程" + name + "结束,用时" + (currentTime - beginTime));
}
}
}

结果如下:
Image Title
执行结果跟10个线程效果差不多。

10个线程每个线程插入100万行

代码跟前面例子雷同,为节约篇幅未列出。
执行结果如下:
Image Title

20个线程每个线程插入50万行

执行结果如下:
Image Title

总结

  • 多线程比单线程的插入效率有所提高,开10个线程与开20个线程的插入行效率差不多。
  • 插入效率存在不稳定情况,通过折线图可以看出。

相关文章

在Linux上搭建Hadoop集群环境
在Linux上搭建HBase集群环境

0%