分类存档: 纯技术

Hadoop集群问题解决记录

下面是我最近一段时间修复公司Hadoop集群(CDH-5.5.1)的一些问题的记录。目前所遇到的挑战都还不算太大,主要是CDH对开源组件的侵入性不强,开源的bugfix代码都能正常合并进来。每次遇到具体的问题,只要善于使用google,就会发现,大部分我们所面对的问题,很早就有人遇到过了,开源社区一般都有相应的解决方案,我们要做的就是,精确的定位问题,找到它的官方bug记录,然后就是合并bug fix代码,自己打包,替换集群里面jar包,然后重启集群,问题基本都能够顺利解决。

1. Hue的mysql从5.5升级到5.7以后,Hue界面执行hql的时候不支持任何中文。解决方案是将Hue的数据库以及所有表的字符集改成utf8。

ALTER DATABASE hue CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE “${all tables in hue}” CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;

2. 使用Sqoop1将Mysql数据抽取到Hive的时候,中文注释变成乱码的解决方案:下载cdh-hive源代码,合并https://issues.apache.org/jira/browse/HIVE-11837 的bugfix代码。参考:https://www.iteblog.com/archives/1687.html  代码下载:https://github.com/cloudera/hive/releases?after=cdh5.4.10-release

修改DDLTask.java:2084行

// outStream.writeBytes(createTab_stmt.render());
 outStream.write(createTab_stmt.render().getBytes("UTF-8"));

1899行

// outStream.writeBytes(createTab_stmt.toString());
 outStream.write(createTab_stmt.toString().getBytes("UTF-8"));

修改完以后,单独打包hive-exec.jar

mvn clean package -DskipTests -Dmaven.javadoc.skip=true -Phadoop-2

3.通过sqoop1将Hive数据导入Mysql的时候,如果启用“–direct”模式,数据中如果有中文会出现乱码。解决方案是合并https://issues.apache.org/jira/browse/SQOOP-2639的bugfix代码。代码下载:https://github.com/cloudera/sqoop/releases

另外,如果在安装Hive的元数据库Mysql的时候各种字符集不是utf8,需要将数据库、所有表的字符集改成utf8,collation也需要修改utf8系列。

代码修改位置:org.apache.sqoop.mapreduce.MySQLExportMapper.java 322行

// this.mysqlCharSet = MySQLUtils.MYSQL_DEFAULT_CHARSET;
 this.mysqlCharSet = "utf-8";

需要注意的是,sqoop1是一个Ant工程,根目录下由一个cloudera-pom.xml,在执行ant package的时候里面会引用该文件来打包。在mac上执行的时候需要设置JAVA_HOME, 必须是Java 1.7,不能用1.8, Ant必须是1.9系列,不能是1.10系列。Ant的安装命令是brew install ant@1.9.

“ant package”执行的时候应该会失败,只要不是在“jar”这个target步骤失败就行了,在build目录下会生成sqoop-1.4.6-cdh5.5.1.jar。将这个jar包替换整个集群上所有机器上的/opt/cloudera/parcels/CDH-5.5.1-1.cdh5.5.1.p0.11/jars/sqoop-1.4.6-cdh5.5.1.jar。

4. “FSImage may get corrupted after deleting snapshot”。HDFS 2.6的一个bug,Apache官方记录HDFS-9406。症状是,NameNode无法启动成功,报错信息如下:

2017-01-27 23:29:04,832 ERROR org.apache.hadoop.hdfs.server.namenode.NameNode: Failed to start namenode.
java.lang.NullPointerException
at org.apache.hadoop.hdfs.server.namenode.INodeDirectory.addChild(INodeDirectory.java:531)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode$Loader.addToParent(FSImageFormatPBINode.java:252)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode$Loader.loadINodeDirectorySection(FSImageFormatPBINode.java:202)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.loadInternal(FSImageFormatProtobuf.java:261)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.load(FSImageFormatProtobuf.java:180)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormat$LoaderDelegator.load(FSImageFormat.java:226)

查看HDFS的源代码,发现是在加载FSImage文件的时候报空指针错误。修改“FSImageFormatPBINode.java:202”,增加空指针判断:

for (long id : e.getChildrenList()) {
  INode child = dir.getInode(id);
 addToParent(p, child); //line 202
}

改成如下:

for (long id : e.getChildrenList()) {
  INode child = dir.getInode(id);
 if (child != null) {
    addToParent(p, child);
 }
}

重新打包HDFS的jar包,替换以后重启NameNode,发现新的报错信息:

2017-01-28 19:51:43,441 ERROR org.apache.hadoop.hdfs.server.namenode.FSImage: Failed to load image from FSImageFile(file=/srv/cloudera/meta01/dfs/nn/current/fsimage_0000000000510693370, cpktTxId=0000000000510693370)
java.io.IOException: Cannot find an INode associated with the INode 000000_0 in created list while loading FSImage.
at org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.loadCreated(SnapshotFSImageFormat.java:158)
at org.apache.hadoop.hdfs.server.namenode.snapshot.FSImageFormatPBSnapshot$Loader.loadCreatedList(FSImageFormatPBSnapshot.java:241)
at org.apache.hadoop.hdfs.server.namenode.snapshot.FSImageFormatPBSnapshot$Loader.loadDirectoryDiffList(FSImageFormatPBSnapshot.java:333)
at org.apache.hadoop.hdfs.server.namenode.snapshot.FSImageFormatPBSnapshot$Loader.loadSnapshotDiffSection(FSImageFormatPBSnapshot.java:188)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.loadInternal(FSImageFormatProtobuf.java:270)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.load(FSImageFormatProtobuf.java:180)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormat$LoaderDelegator.load(FSImageFormat.java:226)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:929)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:913)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImageFile(FSImage.java:732)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:668)
at org.apache.hadoop.hdfs.server.namenode.FSImage.recoverTransitionRead(FSImage.java:281)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.loadFSImage(FSNamesystem.java:1061)

从上面红色字体的地方,能分析出在加载fsimage文件的时候,有一个加载snapshot diff的过程,就是在这个过程中发现真实文件信息和元数据不一致才导致的空指针错误的。既然是由加载snapshot的diff的过程出错的,联想到官方的bug信息,可以断定的确是因为某次删除snapshot的时候导致元数据没有及时更新导致的问题。先注释掉FSImageFormatProtobuf.java:270这一行,跳过这一步:

case SNAPSHOT_DIFF:
  snapshotLoader.loadSnapshotDiffSection(in); //line 270
 break;

改成:

 case SNAPSHOT_DIFF:
// snapshotLoader.loadSnapshotDiffSection(in);
 break;

重新打包部署,重启namenode,发现其能正常启动。HDFS service back on line!

尝试过将HDFS-9406的官方bugfix的代码合并到我们的部署版本(cdh-5.5.1)中,发现非常有难度,因为官方的修复代码并不是基于hdfs 2.6.0更改的。与这个bug相关的HDFS-9696代码,是和保存snapshot相关的代码,可以很方便的合并。

后续操作步骤:

  1. 基于修改过的hdfs jar包,启动HDFS namenode的HA模式;
  2. 删除HDFS下的所有snapshot。这么做的目的是想确保fsimage文件中不再有snapshot-diff信息;
  3. 将其中的一个NameNode的hdfs jar包替换成官方发行的,重启,发现没有任何问题;
  4. 将另外一个NameNode的hdfs jar包还原,重启,没有发现问题;
  5. 多次测试创建快照、删除快照、重启NameNode的过程,均没有发现任何问题。

Mysql运维记事

在创业公司做事儿,意味着随时都会遇到挑战。我虽然不是一个Mysql DBA,但还是得承担公司各个产品线、不同环境下的Mysql运维工作,好在现在数据量都不大,目前还能应付得过来。

在开始具体的运维工作之前,有两个基本原则先要阐述一下:

  1. 对于没有专门DBA人才的创业团队,能用云数据库(RDS)的,就尽量用云数据库。在计算机科学领域,数据库技术绝对算得上一个比较独立、深邃的学科,大型云厂商都有专业的、成规模的DBA团队,甚至是专门的数据库研发团队,他们打造的云数据库产品相对来说更加稳定,安全。比如说像AWS的RDS这种。直接用他们的产品,什么高可用、安全、备份问题基本是不用怎么操心。虽说人家是按使用时间来收费的,但也不是贵的离谱,相对于高级技术人员(DBA的工资一般都很高)的人力成本、服务器成本、托管费用来说,其实还算是便宜的。
  2. 如果受限于大环境(比如说云数据库一般版本比较低),必须要自己维护的数据库实例,一定要使用最新的5.7版。5.7版现在已经是GA版本了,相信我,这是Mysql历史以来最优秀的一个版本 — 性能、可用性、稳定性都达到了一个历史最高点。我们的应用有一个特殊的需求是中文分词+全文检索,这在5.7之前想直接通过数据库来做,是比较痛苦的。目前我们在5.7上面做的单表百万记录的中文全文检索,方便又快捷。

下面就简单记录一下,最近一段时间里面我在运维公司Mysql 5.7一主一从实例的过程中积累的经验。由于我司所有的服务都是运行在AWS云主机上,所有很多解决方案都是从云主机的角度来出发的。

  • AWS的Amazon Linux也是非常适合安装和运行Mysql实例的。以前我的第一反应是Mysql一定要在CentOS这种操作系统上运行,方便运维。事实证明,最新的Amazon Linux已经非常优秀的了,各种辅助设施也很完备,直接通过yum来安装和更新Mysql非常便捷。当然在通过yum安装mysql之前先要通过“yum localinstall”命令来安装最新的mysql yum源。我记得我使用的是“mysql57-community-release-el6-7.noarch.rpm”。具体的安装方法详见Mysql官网。有一个小插曲,在我装完mysql5.7.9并运行一个月以后,有一天不小心运行了一下“yum update”,结果就把mysql升级到了5.7.10。这个最新版的系统表和5.7.9有些不兼容,直接导致主从复制挂了,需要通过“mysq_upgrade”命令来在线修复这个问题。这个在数据库启动日志里面会得到提示。
  • 如何手动跳过基于GTID的主从复制过程中从库上执行失败的事务。我们的Mysql 5.7主从结构,从库比主库增加了更多的索引。这里需要说明一点就是,我们之所以做主从,是因为Mysql的Fulltext索引是非常消耗CPU和IO的,为了不影响主库的基本功能,我们选择只在从库建立全文检索,主库只负责数据更新。早期的时候由于没有限制死从库的数据库账户只具备读的权限,有开发人员会不小心在从库上做数据的更新,这样会导致主从两个库出现大量的数据不一致,主从复制线程在遇到在从库上执行失败的事务的时候hang住。Hang住的时候,通过“show slave status\G;”查看“Last_SQL_Error”执行哪个事务的时候出问题的。一般报错信息如下:

继续阅读 »

记一次由nproc引起的Tomcat OutOfMemory故障

最近有一个应用新上生产环境,我对系统人员的部署建议是每台4C8G的虚拟机部署2个Tomcat实例,总共部署20个节点。结果他们觉得这样虚拟机个数太多管理不方便,就将虚拟机的配置改成8C16G,每个虚拟机部署5个Tomcat实例。表面上来看问题不大,无非就是把多个鸡蛋放在同一个篮子了而已,在没有出现大问题之前我也找不到好的理由来反驳他们。

应用上线运行了几天,刚开始几天没什么问题。大约过了一个礼拜左右,每天就会出现其中一台虚拟机上5个Tomcat实例同时挂掉。查看错误日志,每个Tomcat都是因为”OutofMemory: cannot create a thread”而停止响应,从监控来看,每个Tomcat在当时的总线程数都是210左右。进过分析dump日志,发现这个并不是因为JVM的堆内存不足引起,也不是因为操作系统的内存不足引起的。我猜测是因为文件描述符的限制了。经查看,原来系统室提供的虚拟机是根据总行的要求,将ulimit的nofile设置成了4096,将fs.file-max也设置成了4096.遂让他们把这两个参数逐个调大到65535。原本以为问题就此解决了,不想第二天当高流量来了的时候,还是会出现同样的症状。当时就没思路了,然后重新撸了一遍所有的参数,Tomcat的Connector最大线程数(maxActive)设置都是500,文件描述符远远足够,只是知道是操作系统限制了Tomcat来创建更多的线程,但不知道到底是什么参数来限制的。

通过进一步分析JVM的dump文件,有一个参数引起了我的注意,nproc=1024,1024/5=205, 这个数量级刚好跟我们的监控数据(Tomcat crash的时候)接近。难道是因为这个参数的限制?但在我的知识体系里面,nproc一直都是用来限制进程数的,通过“man ulimit”来查看得到解释是“The maximum number of processes available to a single user”。而现在是线程数(The maximum number of Thread)被限制了,process和thread是两个完全不同级别的限制啊,到底是怎么回事呢?

这个时候,还是google解救了我。我怀着这个猜测在google上直接搜索了“Linux nproc”,没想到立即就找到IBM官方的一篇文章,“Insufficient ulimit -u (NPROC) Value Contributes to Native OutOfMemory”, 里面有一个句很关键的解释让我茅塞顿开:

“The nproc limit on Linux counts the number of threads within all processes that can exist for a given user.”

原来如此,看来问题的症结就在这里了,nproc表面上来看限制的是进程总数,其实是连线程总数一起限制的。再进一步研究发现,早期的Linux是没有“线程(Thread)”这个概念的,直到Linux 2.6内核出来引入NPTL – Native POSIX Thread Library的方案,才有了大家通普遍理解的线程的实现。但是和Windows平台不同的是,Linux里面的线程其实是通过fork函数来fork一个子进程的方式来实现的,并且是通过关闭COW – Copy on Write特性来实现线程间独立但是又共享父进程内存的效果。总而言之,在Linux世界里,线程其实就是进程,进程是Linux内核调度的实体,nproc是为了限制内核”可调度实体”的总数的,所以就一并将线程也限制进去了。这与Windows世界“线程是任务的调度实体”哲学大部相同。

在调整nproc参数的时候,还有一个小细节需要注意。在/etc/security/limits.d/下面有一个90-nproc.conf文件,里面配置了系统默认nproc值:” * soft nproc 1024″,这个文件里面的配置会覆盖/etc/security/limits.conf里面相同的配置项。正因为如此,当我们的系统人员在limits.conf末尾加上”* soft nproc 102400″以后,意图粗暴的将所有用户的nproc值调大,但是重启Tomcat以后,但是在/proc/<tomcat_pid>/limits文件中看到的nproc限制还是1024. 后来把”*”号改成tomcat用户以后才真正生效。

将所有虚拟机的这个参数调整好以后,Tomcat的OutofMemory的问题就彻底消失了。

在Windows 7下用Ruby+rtesseract来识别简单的验证码

公司内部有一个比较难用的考勤网站,每天上下班都必须登陆这个网站输入员编、密码以及一组非常简单的动态验证码来做上下班考勤,遇到高峰期(临上班或者临下班)这个网站卡的厉害,我们每天这么被折磨着,实在是不爽。更为严重的问题是,我还经常忘记这茬事儿,这个可是事关工资的,逼得我要想点办法解决这个问题。于是我想用ruby写个脚本,每天定时上班之前运行一次,下班以后运行一次,从此打卡这事儿就省心了。

开始折腾了,准备工作如下:

  1. 安装Ruby和DevKit,这个过程比较简单,不多述。关于DevKit的配置请参考 https://github.com/oneclick/rubyinstaller/wiki/Development-Kit
  2. 安装ImageMagick,这里需要注意的是不能去官方下载最新的windows安装文件(官网只发布最新的安装包),ruby的rmagick没有做到和最新的兼容。我是去这里下载的,版本是ImageMagick-6.8.0-0-Q8-windows-dll.exe: http://ftp.icm.edu.pl/packages/unix/graphics/ImageMagick/binaries/。装完以后再命令行运行“convert -v验证是否安装成功。装的时候记得勾选“Install development headers and libraries for C and C++ ”这个选项,默认安装路径是在C:\Program Files下面,最好不要装到这里去,接下来要用到ImageMagick的全路径,有空格会麻烦。
  3. 安装rmagick,具体命令如下:gem install rmagick –platform=ruby — –with-opt-lib=%path_to_imagemagick%/lib –with-opt-include=%path_to_imagemagick%/include
  4. 安装rtesseract。直接用命令“gem install rtesseract”安装即可。

安装过程一般不太顺利,我在公司的电脑上百般折腾都没成功,回到家用自己的电脑也是折腾了半天才成功的。下面这些文档都很有帮助。

  1. HowTo install rmagick gem on Windows
  2. How to install ImageMagick + RMagick on Windows 8
  3. Stack Overflow:installing rmagick gem on windows
  4. GitHub RMagick Issue

安装完成以后就可以开始写段代码尝试解析验证码了,非常简单,对于下面这种简单的图,只有三行代码就搞定。1

代码:

require ‘rtesseract’
img = RTesseract.new(“C:/Users/wpeng/Desktop/1.jpg”)
puts img.to_s.sub(/\s+$/, “”)  #去掉空格,输出 “254369”

接下来要做的就是通过open-uri来访问网页,然后借助于nokogiri来解析html代码,最后通过上面的代码来识别验证码。具体过程就不详细写了。

解决Tomcat的shutdown.sh无法正常关闭tomcat的问题

这两天在给生产环境的服务器配置monit(一个Linux下非常实用轻型的监控工具),发觉monit不能通过界面正常的停止tomcat服务。究其原因是因为我的tomcat的服务脚本(/etc/init.d/tomcat1)里面停止tomcat是通过pkill命令强行杀掉的,脚本执行以后的返回结果是非零(好像137),而monit是通过判断返回值是否为零来决定脚本是否执行成功。没办法,只好将脚本改成通过tomcat自带的shutdown.sh来关闭服务了。这时候问题就出来了,由于应用程序里面集成了ehcache,Spring容器里面的cacheManager线程在ServletContext关闭的时候没有被destroy掉,导致tomcat服务停止的时候报以下错误,jvm进程没有退出:

SEVERE: The web application [/xyz] appears to have started a thread named [net.sf.ehcache.CacheManager@787db430] but has failed to stop it. This is very likely to create a memory leak.

这是由于在关闭Tomcat的时候,除了主线程以外尚有持续运行的线程没有退出。具体来讲就是每一个cacheManager对应的TerracottaClient会启动一个Runable的RejoinWorker,除非我们手动destroy这个cacheManager,不然这个worker线程不会正常退出。ehcache官方已经给出解决方案,就是在web.xml里面增加一个shutdownListener,在servletContext销毁之前自动关闭这个线程。具体写法如下:

<listener>
<listener-class>net.sf.ehcache.constructs.web.ShutdownListener</listener-class>
</listener>

在涉及到多线程编程的时候,新手通常喜欢通过while(true)来使线程循环做某件事,而又忘记了将线程设置成daemon模式。这是一个不好的编程习惯,这样的线程可控性很差,除非通过kill命令来杀掉jvm主进程,不然这些线程不会随着主线程的退出而退出。一旦应用里面有这样的线程存在,在通过shutdown.sh来关闭tomcat的时候会报类似下面这样的错误:
SEVERE: The web application [/*] appears to have started a thread named [Thread-4] but has failed to stop it. This is very likely to create a memory leak.

那么该如何避免这类情况的发生呢?答案是可以借助于JVM的shutdownHook来做手脚,让这些线程在创建之初就加入到Java Runtime的shutdownHook中,在JVM退出之前,shutdownHook里面的线程都会被一一的关掉。具体做法可以参考下面这篇文章:

Tomcat’s Graceful Shutdown with Daemons and Shutdown Hooks

Restlet:怪异的HTTP Status = 200 但是 Content-Length=0

上周五遇到一个很怪异的问题,实习生在做网站性能测试的时候,通过JMeter往服务器发送大量并发请求的时候,不管是post和get,有部分请求的返回的结果status code是200, 但是content-length却是0。非并发请求(顺序请求)的时候一切都正常。问题的分析思路以及解决方案如下所述。

  1. 通过抓包分析,断定这个现象跟客户端JMeter没有任何关系,完全是由服务端引起的。
  2. 通过分析Nginx的日志,断定Nginx每次都做了正常的request/response转发,断定问题是由Tomcat里面的应用程序引起的。
  3. 最先被怀疑的是Restlet框架有类似于“maxConnection”这样的参数,应用程序没有配置但默认值偏小而引起的。通过阅读源代码发现在org.restlet.engine.connector.ConnectionHelper中果然存在这些配置,但是默认值都还合理,初始连接池大小就是100,请见下图,这个原因也被排除了。ConnectionHelper
  4. 回想这个网站的架构,对外的接口是通过Spring+Restlet搭建起来的。具体的功能接口是基于Restlet的ServerResource来写的,Spring提供的扩展SpringBeanRouter负责将HttpRequest根据url pattern匹配到不同的ServerResource类,每一次请求过来,由Spring 容器负责初始化一个具体对象来handle这个request。仔细看看这些ServerResource的配置,发觉每个bean的配置并没有设置scope。这个时候就大致知道问题的原因了,Spring对bean的管理,如果不显式的配置scope=”prototype”,则默认是singleton模式的。这样会导致Spring容器对每个ServerResource的类总是缓存一个instance,那样每次request到达的时候,都是由这同一个instance来服务。这样就会导致并发请求的时候,总是只有少数几次请求总是成功,其他的请求会“伪成功”。   <bean name=”/noti/to/{toId}/unread” id=”countUnRead” autowire=”byName”  class=”net.*.*.restlet.CountUnReadNotiResource” scope=”prototype”/>
  5. 加上scope设置以后,应用程序在单个tomcat里面顺利抗住500个并发没有任何问题。事实上在org.restlet.ext.spring.SpringBeanRouter的javadoc上已经实例需要配置这个scope属性,只是当时在demo阶段没有仔细留意。

PS:选择Restlet当作REST API的开发框架挺好的,它与具体应用程序之间的关系就像Nginx和tomcat之间的关系一样,restlet能非常方便的做接口封装、分发,在后端有多个应用程序的环境下用起来非常方便。只是目前有一些已知的坑需要注意一下,比如不能用它自带的ClientResource来发起client http request(最好用Apache commons下面的HttpClient来做),因为它不能做到在每次请求结束以后顺利的关闭连接、释放资源,容易引起操作系统“最大打开文件数”的问题。

通过VisualVM和VisualGC来监控Tomcat的运行情况

在对应用程序做性能测试之前需要配置对tomcat的监控,下面的步骤详细介绍了如何通过visualVM和VisualGC来监控tomcat的运行情况。

关于VisualVM的介绍,请分别前往http://visualvm.java.net/查看,VisualGC是VisualVM的一个插件,用来可视化查看JVM内存实时回收情况。具体信息请前往http://www.oracle.com/technetwork/java/visualgc-136680.html查看官方介绍。

本次实验是在本地(Windows 7)上开启VisualVM,远程监控测试服务器(192.168.1.11)上面的tomcat的运行情况,tomcat版本是7.0.42, JDK的版本是1.7.0_45.

服务端准备工作:

  1. 开启tomcat的JMX支持。在tomcat的启动参数里面增加:
    CATALINA_OPTS=”-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9901 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.11″
    如果在${TOMCAT_HOME}下面有setenv.sh, 那么将上面的参数添加到JAVA_OPTS,然后export JAVA_OPTS也可以。
    9901是客户端的接入端口(JMX/RMI port for Platform MBeans),初次之外JMX会在背后启动一个随机的JMX/RMI sever端口,这样一来如果服务器启用了防火墙,那么这个随机端口就不好配置策略了。好在tomcat提供了第二种配置方案,那就是在启动参数中去掉端口配置,然后通过在server.xml通过Listener的方式来配置固定的端口。在“server”节点添加如下配置:
    <Listener className=”org.apache.catalina.mbeans.JmxRemoteLifecycleListener” rmiRegistryPortPlatform=”9901″ rmiServerPortPlatform=”9902″ />
    这里的rmiRegistryPortPlatform=”9901″就是对应了之前的-Dcom.sun.management.jmxremote.port=9901,9902就是那个随机端口,现在固定下来了,防火墙上面可以很方便的配置通过策略。关于这个Listener的说明请参考这里。本次实验就是采取这种方式。
  2. 根据tomcat具体的版本号去http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.42/bin/extras/下载catalina-jmx-remote.jar,并把它放到$TOMCAT_HOME/lib下面,然后重启tomcat。
  3. 启动jstat deamon程序。Java Virtual Machine Statistics Monitoring Tool(jstat)是为VisualGC服务的。开启步骤如下:
    1. 新建jstatd.policy文件,例如/home/sdb/jstatd.policy,内容如下:
      grant codebase “file:${java.home}/../lib/tools.jar” {
      permission java.security.AllPermission;
      };  //注意:“${java.home}/../lib/tools.jar”必须这么写,这里的${java.home}不是系统这是的环境变量$JAVA_HOME
    2. 执行“sudo /usr/local/java/bin/jstatd -J-Djava.security.policy=/home/sdb/jstatd.policy -J-Djava.rmi.server.hostname=192.168.1.11 &” 启动jstat。注意,jstat必须以不低于tomcat的执行用户的权限来运行,一般是root,或者和tomcat一样。java.rmi.server.hostname就是JMX配置中的值,这个不能少。最后的“&”是为了让jstat在后台运行。jstat默认开启的端口是1099,如果想自定义端口,可以在命令后面(&之前)续上“-p ****”,例如“-p 9903”
  4. 防火墙配置对9901,9902,1099(9903,如果自定义过的话)端口放行,过程略去不表。服务器端的准备工作已经结束。

客户端VisualVM的准备工作

  1. 启动VisualVM,通过Tools–>Plugins–>Avaliable Plugins找到VisualGC 插件并安装。 继续阅读 »

MySQL基于GTID的主从复制的实现方案

上周在公司内网发布的,现在迁移到这里来。

假设有两台服务器,主服务器叫master1(10.4.8.31), 服务器叫slave1(10.4.4.14)

操作步骤:

  1.  数据库安装具体过程略去不表。两台服务器最好都从源码安装,版本保持一致,此次试验的版本是5.6.14,安装的时候编译命令如下:
    sudo cmake \
    -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
    -DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \
    -DDEFAULT_CHARSET=utf8 \
    -DDEFAULT_COLLATION=utf8_general_ci \
    -DWITH_MYISAM_STORAGE_ENGINE=1 \
    -DWITH_INNOBASE_STORAGE_ENGINE=1 \
    -DWITH_ARCHIVE_STORAGE_ENGINE=1 \
    -DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
    -DWITH_MEMORY_STORAGE_ENGINE=1 \
    -DWITH_READLINE=1 \
    -DENABLED_LOCAL_INFILE=1 \
    -DMYSQL_DATADIR=/var/mysql/data \
    -DMYSQL_USER=mysql \
    -DMYSQL_TCP_PORT=3306 \
    -DMYSQL_USER=mysql
  2. 在master1的配置文件/usr/local/mysql/my.cnf中添加以下内容:binlog-format=ROW
    log-slave-updates=true
    gtid-mode=on
    enforce-gtid-consistency=true
    master-info-repository=TABLE
    relay-log-info-repository=TABLE
    sync-master-info=1
    slave-parallel-workers=2
    binlog-checksum=CRC32
    master-verify-checksum=1
    slave-sql-verify-checksum=1
    binlog-rows-query-log-events=1
    server-id=2
    report-port=3306
    port=3306
    log-bin=10-4-8-31-bin.log
    report-host=10.4.8.31
    innodb_flush_log_at_trx_commit=1
    sync_binlog=1
  3. 在slave1的配置文件中添加以下内容,请注意和master1种的区别:
    binlog-format=ROW
    log-slave-updates=true
    gtid-mode=on
    enforce-gtid-consistency=true
    master-info-repository=TABLE
    relay-log-info-repository=TABLE
    sync-master-info=1
    slave-parallel-workers=2
    binlog-checksum=CRC32
    master-verify-checksum=1
    slave-sql-verify-checksum=1
    binlog-rows-query-log-events=1
    server-id=10
    report-port=3306
    port=3306
    log-bin=10-4-4-14-bin.log
    report-host=10.4.4.14
    innodb_flush_log_at_trx_commit=1
    sync_binlog=1
  4. 两台server的mysql都重启:sudo service mysql restart
  5. 登陆master1服务器,执行以下命令: 继续阅读 »

通过LVM来做Mysql的热备份

以前发布在公司内网的,现在慢慢的搬到这里来。

背景知识:关于LVM以及LVM snapshot的介绍文章:http://vbird.dic.ksu.edu.tw/linux_basic/0420quota.php#lvm

下面是具体的步骤

第一步 将现有的mysql数据迁移到LVM中

su root
service mysql stop
lvcreate -L 30G -n mysql-data xpos-vg
mkfs -t ext4 /dev/xpos-vg/mysql-data
mkdir /tmp/mysql
mount /dev/xpos-vg/mysql-data /tmp/mysql
cd /var/mysql/data
tar cf – . | tar xf – -C /tmp/mysql     #将现有的数据全部拷贝到lv中
rm -rf *
umount /tmp/mysql
mount /dev/xpos-vg/mysql-data /var/mysql/data
sudo service mysql start

现在mysql就已经允许在LVM中了, lv=/dev/xpos-vg/mysql-data.

 

第二部 通过LVM snapshot备份mysql

mysql -uroot -p -e ‘flush tables with read lock;’     //防止创建快照过程中有数据写入
lvcreate -n mysql-snap -L 2G -s /dev/xpos-vg/mysql-data     //为/dev/xpos-vg/mysql-data创建snapshot
mysql -uroot -p -e ‘unlock tables;’    //解除锁
mount /dev/xpos-vg/mysql-snap /tmp/mysql/
mkdir -p /data/backup/mysql/full-bak-`date +%F`
cd /tmp/mysql/
tar cf – . | tar xf – -C /data/backup/mysql/full-bak-`date +%F`     //将snapshot中的数据备份到其他地方
cd /
umount /tmp/mysql
lvremove /dev/xpos-vg/mysql-snap    //删除snapshot

这样mysql的数据就快速备份到了/data/backup/mysql/full-bak-`date +%F`文件夹中了。

如何防止网站关键数据被人恶意采集

昨天晚上花了几个小时用Jsoup写了一个网站采集器,帮一个高中同学采集了一个工业信息门户上的芯片待售信息。网站上显示的数据多达60w+条, 我写的程序跑了7个小时只采集了一半,算是可以交差了。

这已经是我第二次写这种采集器了。之前在做波菜网的时候,写过一个更复杂的。当时网站上线以后苦于没有原生态的内容,我就写了一个采集器从“百度身边”采集各大城市的餐馆和菜品信息,并通过google地图的接口获取餐馆的地理位置,然后转存到我们自己的网站上。

我必须承认,做这些事儿都是不光彩的,属于不劳而获,弄不好还得背上法律责任。但是在国内这个互联网的环境下,这种小规模的数据采集想上升到法律层面去禁止,还是不切实际的。那么从一个网站的开发或者运营的角度,如何来防范别人的恶意采集呢?如何做到在防范的同时又不影响搜索引擎的蜘蛛的采集工作呢?

从我个人的采集经验来看,我觉得这种防御可以从两个方面着手。

  1. 服务器端的配置。至少可以通过防火墙来屏蔽某些ip的高频率访问,或者在web服务器设置规则来禁止关键页面被某些ip高频请求。也可以先通过防火墙设置预警机制,一旦发现异常立即通知web服务器采取屏蔽措施。不管是专业的采集器还是像我的这种纯代码级别的采集,最基本的原理就是模拟用户浏览网站行为,发送http请求到网站的server,然后解析返回的结果。出于效率起见这种采集行为都有几个共同的特点。一是发出请求的ip地址比较固定,二是请求的频率比较稳定,三是访问的页面也比较固定。防火墙的设置可以基于前面两个特征,web服务器的过滤规则可以基于后面两个特征。当然这些措施只是从物理层面的防御,放不了真正的高手。我在我的采集器里面实现的多线程采集,就会把采集频率用随机时间来控制,也就是发送采集请求的频率是随机的,有可能是3秒钟一次,有可能是2秒钟一次。另外如果我是黑客,我可以发动多个ip的肉机来发送请求。总之一句话,服务器上的防御,防得了君子,防不了高级的小人,但是有总比没有好。
  2. 代码级别的防御。采集器可以模拟浏览器大部分的行为,但是肯定有模仿不到的地方。比如说带参数的ajax请求。从我的经验来看,你至少可以在通过以下三种方式来优化你的代码,以防止恶意采集。 继续阅读 »

依据进程关键字来监控进程的消耗

出于对服务器性能的监控,我需要在指定时间内反复查看具体的进程对系统资源的消耗。我用perl脚本实现了这个功能。

例如,针对tomcat的进程,每隔5秒钟print一次资源消耗详细情况,循环print 120次。将结果输出到一个文件。
用法:./ps.pl 5 120 tomcat > /tmp/ps.out
继续阅读 »

网站用户自动城市定位的免费解决方案

服务器通过HttpRequest拿到访问者的IP,然后通过下面几个方式来定位所在城市(以下方式兼容所有浏览器):

  1. Server端用java+纯真版QQ IP数据库。优点:实现原理简单,网上用例丰富。IP数据库更新方便。命中率高。缺点:涉及大量的java IO操作,对并发访问的支持比较差。
  2. 发送HttpRequest将ip传给第三方接口(http://api.liqwei.com/location/),等待返回城市结果。缺点:第三方接口的性能,安全性和稳定性未知,不可控。优点:实现简单,不用担心数据不够新。命中率高。
  3. 使用GeoLite City。Apache加载二进制数据库文件到内存中,利用apache的url过滤功能对指定页面(有地图的页面)的request做ip定位。缺点:命中率低,对服务器的内存有要求。数据库文件30M左右,每个apahce进程的内存消耗都会徒增30m左右。官方数据命中率只有79%。优点:对并发支持的比较好,能自由定制。

波菜网一开始是采用方案2主用+方案1备用的方式,发现网站定位比较慢,网络高峰期第三方接口不稳定导致网站定位失败。目前采用的是方案3. 目前运行状态良好。

在CentOS 上面 Enable ImageMagick + JMagick

终于把Server上的ImageMagick + JMagick配置好了。配置过程不表,总的来说过程还是蛮折腾的。下面说说注意事项:

  1. Jmagick官网最新的release版本是6.4.0. 我是从它的文档里面找到SVN地址checkout了最新的版本6.5.7. 网上能找到的教程都会要求JMagick的版本和ImageMagick版本必须一致,否则会出现莫名异常(这个规则我没有验证,默认遵守),所以我也选择了ImageMagick 6.5.7
  2. ImageMagick安装的时候最好指定安装目录,./configure –prefix=/usr/local/ImageMagick,这样在安装JMagick的时候指定–with-magick-home=/usr/local/ImageMagick就方便了。之前在安装6.4.0版本的时候没有这么指定prefix,安装完以后在使用JMagick的时候遇到了”Unable to retrieve handle”异常。 继续阅读 »

Monit is enabled

生产环境下的JDK7+Tomcat 7遇到了一个很诡异的问题,Tomcat会间歇性的挂掉,没有任何异常的log,每次收到监控宝发送的503 Service Temporarily Unavailable邮件都会感到心急火燎。今天花了几个小时在server上装了monit,这个工具可以通过配置可以监控几个关键process,一旦出现问题就会自动重启被监控的进程,甚是方便,省心。

下面是我给monit设置的monitor配置文件: 继续阅读 »

做好网站的反馈

最近这两天我在寻思着给产品做一个反馈系统。反馈系统是一个能让网站运维人员和visitor顺利交流最直接的途径。其设计的初期应该考虑以下三个问题:

  1. 用户是否需要登录才能反馈。
  2. 反馈的内容是否需要分类。
  3. 界面必须包含哪些元素。

对于第一个问题,我的考虑是visitor不需要登录也可以反馈,但是鼓励Ta在填写反馈意见的同时留一个可联系email,方便我们答复Ta的反馈。网站建设初期,用户遇到的问题会比较多,很多功能不需要用户登录就能使用。不需要登录是降低了反馈的门槛。

反馈的内容是需要分类的。至少需要分成“问题”和“建议”两大类。“问题”是指用户在使用网站的过程中受挫了,需要把情况反映给网站的运营人员。“建议”是指用户在使用过程中有好的idea冒出来,希望网站能改进的。不管是那一种,界面上都必须对他们的反馈表示感谢,同事运营人员一定要即使处理他们的反馈并给予回复。

至于界面,看到很多网站的反馈页面仅仅是一个标题和文本输入框,这么简陋的反馈界面很容易降低“热心肠”的访问者的反馈热情。我觉得界面必须有这么几个元素:标题,分类,用户名(如果是匿名用户则提示输入邮箱),文本输入框,感谢的文字。总之界面要让人觉得你是在真心诚意地收集反馈,并尽可能的让他们确信反馈是有效的是能引起重视的。

在AWS上配置Apache+Subversion

花了一个下午的时间把这个给折腾好了。其中各种细节略去不表。直接贴出命令步骤。看得懂的迟早会看得懂,看不懂的永远看不懂。另外,我的AWS AMI是CentOS的。

yum -y install httpd subversion mod_dav_svn mod_ssl openssl
mkdir /svn
cd /svn
mkdir repos
chown -R apache:apache /svn
cd repos/
svnadmin create –fs-type fsfs myproject
chown -R apache:apache myproject
chmod -R g+w myproject
chmod g+s myproject/db 继续阅读 »