开发PDI(Kettle) Step Plugin

Pentaho Data Integration (PDI)是著名的ETL工具Kettle的现用名,这个工具允许用户以图形化的方式构造数据处理流程,除了内置丰富的数据处理节点以外,还允许用户自定义开发自己的数据处理节点以便实现更复杂或更定制的处理逻辑,使用的开发语言是Java。

在某项目里为了实现一个项目特定的数据转换,我开发了一个这样的处理节点,整体感受还是十分流畅的,记录如下(代码略):

从github克隆项目:

git clone git@github.com:pentaho/pdi-sdk-plugins.git

里面包含的kettle-sdk-step-plugin模块即是一个例子,可以基于这个模块的代码按需修改。
开发完成后运行自带的三个测试用例,这样部署后成功率比较高。
打包命令仍然是:

mvn package

打好的是一个jar包,放在${pdi_path}/plugins/steps/<demo_plugin_name>/目录下即可,如果目录不存在可按需创建。

使用Apache Drill处理数据文件

本文针对drill版本1.8。

安装Drill

官网下载tar包并解压即可,linux和windows都是如此。
注意:drill要求java版本是8或以上。

命令行使用Drill

最简单的方式是用embedded模式启动drill(启动后可以在浏览器里访问 http://localhost:8047 来管理drill):

bin/drill-embedded

这样就以嵌入模式启动了drill服务并同时进入了内置的sqline命令行。如果drill服务已经启动,则可以这样进入sqline命令行(参考):

bin/sqline -u jdbc:drill:drillbit=localhost

作为例子,用SQL语法查询一下drill自带的数据(命令行里的cp表示classpath,注意查询语句最后要加分号结尾):

apache drill> SELECT * FROM cp.`employee.json` LIMIT 3;

查询任意数据文件的内容:

apache drill> SELECT * FROM dfs.`/home/hao/apache-drill-1.16.0/sample-data/region.parquet`;

退出命令行用!quit

配置和查看Drill参数

如果要永久性修改参数值,需要修改$DRILL_HOME/conf/drill-override.conf文件(见文档);SET、RESET命令可以在当前session里修改参数值(文档)。

配置参数:

SET `store.parquet.block-size` = 536870912

重置参数为缺省值:

RESET `store.parquet.block-size`

查看参数:

select * from sys.options where name = 'store.parquet.block-size'

在java代码里使用Drill

下面是在java代码里使用Drill的例子代码,要注意的一点是,JDBC的URL是jdbc:drill:drillbit=localhost,而不是很多教程上说的jdbc:drill:zk=localhost

package com.acme;

import java.sql.*;

public class DrillJDBCExample {
    static final String JDBC_DRIVER = "org.apache.drill.jdbc.Driver";
    //static final String DB_URL = "jdbc:drill:zk=localhost:2181";
    static final String DB_URL = "jdbc:drill:drillbit=localhost"; //for embedded mode installation

    static final String USER = "admin";
    static final String PASS = "admin";

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try{
            Class.forName(JDBC_DRIVER);
            conn = DriverManager.getConnection(DB_URL,USER,PASS);

            stmt = conn.createStatement();
            /* Perform a select on data in the classpath storage plugin. */
            String sql = "select employee_id,first_name,last_name from cp.`employee.json`";
            ResultSet rs = stmt.executeQuery(sql);

            while(rs.next()) {
                int id  = rs.getInt("employee_id");
                String first = rs.getString("first_name");
                String last = rs.getString("last_name");

                System.out.print("ID: " + id);
                System.out.print(", First: " + first);
                System.out.println(", Last: " + last);
            }

            rs.close();
            stmt.close();
            conn.close();
        } catch(SQLException se) {
            //Handle errors for JDBC
            se.printStackTrace();
        } catch(Exception e) {
            //Handle errors for Class.forName
            e.printStackTrace();
        } finally {
            try{
                if(stmt!=null)
                    stmt.close();
            } catch(SQLException se2) {
            }
            try {
                if(conn!=null)
                    conn.close();
            } catch(SQLException se) {
                se.printStackTrace();
            }
        }
    }
}

让Drill访问数据库

根据要访问的数据库的不同,需要为Drill添加相应的驱动,方法见RDBMS Storage Plugin

利用Drill将csv格式转换为parquet格式

原理是在drill里创建一张格式为parquet的表,该表的路径(下例中的/parquet1)对应的是磁盘上的一个目录。

ALTER SESSION SET `store.format`='parquet';
ALTER SESSION SET `store.parquet.compression` = 'snappy';

CREATE TABLE dfs.tmp.`/parquet1` AS 
SELECT * FROM dfs.`/my/csv/file.csv`;

让drill支持.zip、.arc压缩格式

(暂缺)

参考资料

Drill in 10 Minutes
How to convert a csv file to parquet

pytorch环境搭建和简单的人脸识别应用

注:本文是一边安装一边记录形成的,其中有不少问题是由于centos版本较低导致的,建议使用高版本(>=7.6)安装以节约时间。

安装python

使用Anaconda管理环境

设置anaconda使用tuna(清华大学镜像):

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --set show_channel_urls yes

tuna还维护了一些第三方镜像,包括conda-forge, menpo等,需要的话也可以像上面这样添加,tuna页面有说明。

在conda环境里安装pytorch:

conda install pytorch-cpu -c pytorch

CUDA toolkit下载:https://developer.nvidia.com/accelerated-computing-toolkit

cuDNN下载:https://developer.nvidia.com/cudnn

用pip安装dlib(win下未成功,linux下没试):

https://pypi.org/simple/dlib/ 下载.whl格式的dlib,然后用pip安装:

pip install xxx.whl

用conda安装dlib:

用conda安装dlib(如果已经将menpo源添加到conda,则可以省略-c menpo选项):

conda install -c menpo dlib

conda提示Killed:

Update 201902: 另一个引发Killed的原因是内存不足,例如这台ec2上只有约300MB可用内存,经常出现Killed,增加512MB(总共800MB可用内存)后Killed的情况就少了很多。

这里遇到一个坑:用conda安装各种包时总报错“Killed”,网上也没找到类似问题,最后发现居然是磁盘空间满了,原来anaconda3居然占用了接近4GB的空间把硬盘填满了。另一个原因,需要conda update conda更新一下conda版本,之后就能正常安装了。

解决上述磁盘空间满问题后,发现conda install dlib时还会报错"Killed",加-vvv参数后发现可能与TUNA源有关:

从~/.condarc里删除相关TUNA源后问题即解决(应该主要是conda-forge这个镜像源的问题)。

偶尔还会遇到killed提示,但再试一次通常可以成功,因此猜测网络条件也会产生影响。

dlib与python版本:

在python 3.5环境里安装的dlib,虽然安装成功,但import dlib失败:

提示“ImportError: /root/miniconda3/envs/py35/lib/python3.5/site-packages/dlib.cpython-35m-x86_64-linux-gnu.so: undefined symbol: _PyThreadState_UncheckedGet”:

重新换python 3.6环境里安装dlib,可以import dlib成功:

升级glibc

由于是在centos6.5环境里,glibc版本比较低(2.12),而menpo源的dlib 19要求glibc 2.14,因此需要手工升级glibc。链接

安装必要的python库

conda install dlib -c menpo
conda install opencv -c menpo
conda install numpy
pip install imutils

如果安装opencv报内存不足(300MB剩余内存有此问题,700MB可以通过),可以尝试用pip安装opencv:

pip install opencv-python==3.4.1.15

Python编码问题

print()命令打印来自mysql的中文时报错如下:

UnicodeEncodeError: 'ascii' codec can't encode character '\xe9'

解决(绕过)方法:用logging.info()替代print()命令。

import logging
logging.basicConfig(level=logging.DEBUG,format='[%(levelname)s][%(asctime)s] %(message)s')
logging.info("my messages")

注意,默认logging输出到的是stderr而不是stdout。

一个简单的人脸检测程序

检测一张图片里的多个人脸,用绿色矩形标记并输出为图片。参考链接

from imutils import face_utils
import imutils
import dlib
import cv2
image = cv2.imread('/root/person01.jpg')
image = imutils.resize(image, width=500)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
detector = dlib.get_frontal_face_detector()
rects = detector(gray, 1)
for (i, rect) in enumerate(rects):
    (x, y, w, h) = face_utils.rect_to_bb(rect)
    cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 1)
cv2.imwrite('/root/output.jpg',image)

python连接mysql数据库

过去常用的MySQLdb是c语言实现的,目前更建议使用MySQL官方提供的Connector/Python,是一个纯python的实现。

pip install mysql-connector-python

不过Connector/Python对mysql服务器版本有要求,例如2.0版以上就无法连接5.1版的mysql服务器,会报Bad Handshake错误。

另一个纯python的实现是PyMySQL,使用起来兼容性比较好。安装方法是:

pip install PyMySQL

用PyMySQL的一个简单例子:

import pymysql.cursors
cnx = pymysql.connect(user='xxx',password='xxx', host='localhost', database='xxx', charset='utf8mb4')
try:
    with cnx.cursor() as cursor:
        sql = "SELECT id, name, gender FROM user"
        cursor.execute(sql)
        for row in cursor:
            print(row)
finally:
    cnx.close()

安装tensorflow

tensorflow支持python3.5,所以用conda创建一个python 3.5环境后安装(参考链接):

conda create -n tensorflow python=3.5
source activate tensorflow
pip install tensorflow

如果是在centos 6上,由于自带的gcc是4.4.7版本过低,所以需要升级到gcc 4.8版本。直接下载编译的时间比较长(也比较容易遇到问题),可以使用devtools2辅助升级(参考链接):

wget http://people.centos.org/tru/devtools-2/devtools-2.repo -O /etc/yum.repos.d/devtools-2.repo
yum install devtoolset-2-gcc devtoolset-2-binutils devtoolset-2-gcc-c++
ln -s /opt/rh/devtoolset-2/root/usr/bin/* /usr/local/bin/

此时执行tensorflow程序可能会提示找不到库GLIBCXX_3.4.17的问题:

ImportError: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.17' not found (required by /root/miniconda3/envs/tensorflow/lib/python3.5/site-packages/tensorflow/python/_pywrap_tensorflow_internal.so)

此时可以从conda自带的lib目录里复制一个libstdc++.so.6.0.24版本并更新软连接解决:

cp /root/miniconda3/lib/libstdc++.so.6.0.24 /usr/lib64/
ln -s /usr/lib64/libstdc++.so.6.0.24 /usr/lib64/libstdc++.so.6

这时执行tensorflow程序遇到glibc版本过低的提示:

ImportError: /lib64/libc.so.6: version `GLIBC_2.16' not found

查看系统的glibc版本是2.14,因此需要升级。但是按照前述升级到glibc 2.14的方法(链接),升级为2.16时却遇到了下面的问题:

error while loading shared libraries: __vdso_time: invalid mode for dlopen(): Invalid argument

由于libc.so.6有问题,导致一些基本命令(例如rm)不可用,此时要切换回glibc 2.14的方法如下:

LD_PRELOAD=/opt/glibc-2.14/lib/libc-2.14.so rm -f /lib64/libc.so.6
LD_PRELOAD=/opt/glibc-2.14/lib/libc-2.14.so ln -s /opt/glibc-2.14/lib/libc-2.14.so /lib64/libc.so.6

原因(可能)是因为升级glibc时没有包含完整环境,使用下面的configure参数:

../configure --prefix=/opt/glibc-2.17 --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin

上面命令有可能在check过程中出错(不支持--no-whole-archive),原因是binutils版本过低(2.23),需要升级到2.25,方法见链接

升级binutils后虽然能够支持--no-whole-archive参数了,但升级glibc 2.16后的异常却没有任何变化。

至此,决定升级linux版本解决(从centos 6.5升级到7.6)。Update: 在centos 7.6下安装tensorflow十分顺利。

 

参考资料:

https://github.com/jcjohnson/pytorch-examples

Anaconda使用总结

mysql数据库ibdata1文件过大问题处理

问题

一个mysql数据库,由于数据不断积累,目前ibdata1文件已经达到100GB大小,接近磁盘存储空间上限。

使用mysqlshow命令查看各个表占用的空间大小,发现dap_app_usage表占用的空间最大。其中data_length是数据大小,index_length是索引大小,data_free是未被使用的空间(通常是删除数据后留下的,这些空间不会被文件系统回收,但未来新插入的数据可以使用)。

mysqlshow --status apptimer

由于dap_app_usage表里保存了2014~2018年的数据,其中2015年及以前的数据属于冷数据,因此可以备份这些数据并从数据库里删除。

备份数据

我们按月备份数据以方便未来使用这些数据,每个月预估压缩后的文件大小为100MB,写一个脚本循环调用mysqldump命令以节约手工输入命令的时间并避免出错:

#!/bin/sh
#Dump dap_app_usage table to files by month
for i in {201401..201412}
do
    echo [date] Backing up $i ...
    mysqldump -uroot apptimer dap_app_usage -t --where="date between '${i}01' and '${i}31'"|gzip>~/dap_app_usage_${i}_data.sql.gz
done

删除数据

一次删除大量(如一亿条)数据效率很低,编写一个脚本小批量多次删除:

#!/bin/sh
# Delete specified range data from dap_app_usage table
# Make sure you have backup of these data

st=20140101
ed=20141231

if [[ ${st} -lt 19700101 ]]; then
    echo 'Wrong start date'
    exit
fi

if [[ ${ed} -lt ${st} ]]; then
        echo 'End date must be greater than start date'
        exit
fi

if [[ $((${ed}-${st})) -gt 1130 ]]; then
    echo 'Date range too large (1 year max)'
    exit
fi

echo "Delete data from ${st} to ${ed}"
for i in {1..99999}
do
    echo [date] delete iteration ${i}
    mysql -uroot apptimer -e "select count(*) from dap_app_usage where date between '${st}' and '${ed}'"
    mysql -uroot apptimer -e "delete from dap_app_usage where date between '${st}' and '${ed}' limit 1000"
    sleep 1
done

后续

为避免以后再出现类似问题,应定期执行上述清理操作,保证在dap_app_usage表里只保存近期一段时间的数据,这样可以维持数据库文件大小在一个固定值。

参考资料:

How to shrink/purge ibdata1 file in MySQL

JVM内存模型

JVM的内存分为堆(heap)和栈(thread stack)两类区域,分别存放不同数据,规则如下。

以下数据存放在heap中:

  • 所有对象(object)。但对象的method里的local variable存放在stack中。
  • 所有对象的member variable,不论是primitive或是指向一个对象。
  • 所有静态类的variable

以下数据存放在stack中(每个线程有自己的thread stack,互相不可见):

  • 当前线程执行过的所有方法(method)
  • 当前线程内所有local variable(method里的变量)。如果此变量指向一个对象,则变量存放在stack中而对象仍然存放在heap中。
  • 当前线程内所有primitive类型(如int, long, boolean)的local variable

java-memory-model-3

参考资料:

Java Memory Model

Java 内存区域和GC机制

Amazon EC2配置步骤和一些问题

file

上次使用Amazon EC2的步骤没有记下来,导致这次配置新帐号时比较麻烦,这里把配置云服务器的常用操作记录在一起提高效率。2015/5/19注:最早我使用的是Amazon EC2,后来由于价格和SSD的原因改为使用DigitalOcean的服务,再后来由于国内访问速度原因改为使用阿里云的服务,但一直用CentOS 6.5 64bit的环境,所以很多步骤是相同的。

注册EC2

1、注册amazon aws帐号,需要一张信用卡和一个固定电话,过程不再赘述。此过程中可得到一个.pem文件。

2、进入aws management console,在EC2部分,点击launch instance按钮启动一个ec2 instance。综合价格和国内访问速度,我建议选择US-WEST, Oregon区域。使用cloudping这个在线工具可以实测连接速度。

3、 用puttygen(随putty安装)选择conversions->import key菜单项导入前面获得的.pem文件,然后点击save private key按钮即得到.ppk文件。(参考链接)

接下来是按需要配置instance,这里以免费的AMI为例,先运行“sudo su”进入root身份: 

配置基本环境

1、创建用户

> useradd xxx
> passwd xxx

2、设置时区

> cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 

不过上面的方式重启后好像会失去效果,建议用下面的方式:

> ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 

设置完成后最好重启服务器,否则一些服务(例如crond)日志里仍然使用原来的时区。参考链接

20150318注:还要注意/etc/sysconfig/clock文件(此文件表示硬件时钟使用的时区)里配置的内容,曾经有过一台机器是ZONE="America NewYork",导致tomcat里的时间不正确,改为ZONE=Etc/UTC后解决。

3、设置环境变量

/etc/profile.d下建立一个.sh文件,内容如下:

my_var=xxx
export my_var

注:阿里云服务器初始化和挂载数据盘的方法见这个链接

安装和配置MySQL

1、安装:

> yum install mysql mysql-server mysql-libs

数据库文件位于/var/lib/mysql, 配置文件是/etc/my.cnf

设置数据库的缺省字符集:在/etc/my.cnf里加入character_set_server=utf8(命令show variables like 'char%';可以查看当前值)。

为了避免8小时自动关闭连接,在my.cnf里的[mysqld]下(注意不要放在[mysqld_safe]下)增加interactive_timeout=288000wait_timeout=288000(两个必须同时改否则不生效),即把默认8小时改为80小时,一般够用了。  

若要修改表格列的编码方式,用下面命令:

alter table 'my_table' modify column 'my_column' varchar(45) character set utf8 not null;

启动MySQL服务:

> service mysqld start

2、创建数据库和导入数据:

先进入mysql命令行:

> mysql -uroot

在mysql命令行下,创建一个空的数据库:

> create database mydatabase;

创建所需用户同时授予权限: 

> grant all privileges ON mydatabase.* TO 'username'@'localhost' identified by 'mypassword' with grant option;
> flush privileges;

向新建的数据库里导入所需数据(先退出mysql命令环境回到bash下,输入文件一般由mysqldump命令导出得到):

> mysql -uroot mydatabase < myexportedfile.sql

附导出mysql数据库的命令行:

> mysqldump -uroot -p mydb | gzip > my_dumped_file.sql.gz

若导出时想排除某些个表:

> mysqldump -uroot -p mydb --ignore-table=mydb.table1 --ignore-table=mydb.table2 | gzip > my_dumped_file.sql.gz

另,在mysql命令行里使用select ... into outfile和load data命令也可实现数据的导入导出,示例如下:

SELECT * INTO OUTFILE 'c:/temp/my_file.txt' FROM my_table;

恢复时(注意要加LOCAL关键字,否则会提示Can't get stat of 'xxx'错误,参考链接):

LOAD DATA LOCAL INFILE 'c:/temp/my_file.txt' REPLACE INTO TABLE my_table character set utf8;

3、如果想在浏览器里管理数据,一般使用phpMyAdmin,步骤如下:

创建远程用户admin并授予所有权限:

> GRANT ALL PRIVILEGES ON *.* TO admin@"%" IDENTIFIED BY 'mypassword' WITH GRANT OPTION;

安装phpMyAdmin(需添加额外yum源):

> yum install phpmyadmin

配置ftp服务

这里我们使用vsftp。首先是安装,缺省安装目录在/etc/vsftpd

> yum install vsftpd

修改配置文件:

> vi /etc/vsftpd/vsftpd.conf

anonymous_enable=YES改为NO,在文件最后部分加上下面内容(防火墙里要开对应端口范围):

pasv_enable=YES
pasv_min_port=62222
pasv_max_port=63333

以上配置让vsftpd只接受pasv模式的连接,所以ftp客户端需要注意一下相应配置。 

有时上传大文件结束时提示timeout错误,文件越大出现概率也越大,调整了与timeout有关的参数后依然无效。暂时的解决方法是将文件分卷压缩,上传先合并(cat xxx.*>yyy)再解压缩。注意,分卷解压缩之前要保持各分卷文件的顺序,我曾遇到一个情况是当分卷多于100个时,因为xxx.z100排在了xxx.z11之前,导致合并后的.zip文件无法解压缩。这时,要手工把xxx.z11改名为xxx.z011,其余文件类似处理,可以用excel等工具批量生成命令行(从z01到z99共99个)。

配置Apache

安装:

> yum install httpd

启动Apache服务:

> service httpd start

设置Apache https

1、先安装mod_ssl模块

> yum install mod_ssl

注意:根据linux环境(mod_ssl环境?)不同,这里至少有两种情况:

a) mod_ssl安装后会在/etc/httpd/conf.d/下生成ssl.conf文件,里面已经有LoadModule语句,所以在httpd.conf里不用重复添加;

b) mod_ssl安装后得到的文件是/apache/conf/httpd-ssl.conf,可能需要在httpd.conf里手动添加LoadModule语句,并添加Include "/apache/conf/httpd-ssl.conf"语句。参考链接

2、创建证书

#生成1024位密钥(至少使用1024否则提示RSA_sign:digest too big for rsa key错误,参考链接)
>openssl genrsa 1024 > server.key 
#生成证书请求文件,这一步要按要求回答若干问题并设置一个challenge password
>openssl req -new -key server.key > server.csr 
#生成证书,365是有效天数
>openssl req -x509 -days 365 -key server.key -in server.csr > server.crt

3、配置apache支持https

修改ssl.conf(或有些linux版本的httpd-ssl.conf)文件,当然上一步骤里得到的server.keyserver.crt文件要先拷贝到对应目录。:

SSLCertificateFile /etc/httpd/conf.d/server.crt
SSLCertificateKeyFile/etc/httpd/conf.d/server.key 

最后,在ssl.conf里把DocumentRoot、ServerName按实际修改(和httpd.conf内容相同)。如果与tomcat集成过,记得把JkMount也加上。

配置Tomcat

安装,缺省安装目录在/usr/share/tomcat6

> yum install tomcat6

提醒一下:如果还没有安装过jdk就启动tomcat,会直接在命令行里报Error Code 4错误,yum安装java-1.7.0-openjdk即可解决。

如果你的webapp里有连接池等资源,需要在/usr/share/tomcat6/conf/server.xml里配置好,以下是一个例子:

<Context path="" docBase="/var/www/html/mywebapp" debug="0" reloadable="true" crossContext="true">
        <Resource name="jdbc/myresourcename" auth="Container" type="javax.sql.DataSource"
               maxActive="100" maxIdle="30" maxWait="10000"
               removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true"
               username="myusername" password="mypassword" driverClassName="com.mysql.jdbc.Driver"
               url="jdbc:mysql://localhost:3306/mydatabase?characterEncoding=utf8"/>
</Context>

注:linux下使用tomcat6 dbcp会报classnotfound的问题。暂时解决方法:复制tomcat-dbcp.jar到/usr/share/tomcat6/lib。(参考链接

如果用连接池,还需要把数据库的驱动程序复制到/usr/share/tomcat6/lib下,仅仅在webapp里包含驱动是不行的。

此外,当url里会有中文出现时,在server.xml里的<Connector>标签需要加上URIEncoding="UTF-8"属性。如果是与Apache整合的情况,注意给8009也设置这个属性。

要开启压缩功能,可以按下面的方式设置:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" 
      redirectPort="8443" URIEncoding="UTF-8" 
      compressableMimeType="text/html,text/xml,text/plain,application/json" 
      compression="on" compressionMinSize="256" />

另注:若启动tomcat时发现catalina.out里提示UnknownHostException,可修改/etc/hosts文件,把/etc/sysconfig/network里定义的HOSTNAME映射为localhost。

整合apache与tomcat

Apache可以通过jk模块与n个位于相同或不同主机上的tomat实例配合,达到负载均衡的目的,缺省情况下这些tomcat监听8009端口。具体的配置方法如下:

首先在apache网站下载mod_jk(文件名如mod_jk-1.2.31-httpd-2.2.x.so,注意32位与64位有区别), 放到/etc/httpd/modules目录下,并chmod 755,下载地址32位/64位供参考。

进入/etc/httpd/conf目录,创建一个workers.properties文件,内容如下:

workers.tomcat_home=/usr/share/tomcat6
workers.java_home=/usr/lib/jvm/jre
ps=/
worker.list=ajp13
worker.ajp13.port=8009
worker.ajp13.host=localhost
worker.ajp13.type=ajp13
worker.ajp13.lbfactor=1

可参考:The Apache Tomcat Connector - Generic HowTo

编辑/etc/httpd/conf/httpd.conf文件,把下面的内容放在所有LoadModule命令的最后,其中JkMount命令后面的参数根据需要调整:

LoadModule jk_module modules/mod_jk-1.2.31-httpd-2.2.x.so
JkWorkersFile /etc/httpd/conf/workers.properties
JkLogFile "logs/mod_jk.log"
JkLogLevel info
JkMount /*.jsp ajp13
JkMount /*.do ajp13

发布web应用(webapp)时应直接放到apache页面目录下(/var/www/html/mywebapp),而不需要在tomcat/webapps下发布。注意apache不会像tomcat那样自动解压缩.war文件,所以需要手动解压缩,命令是:unzip mywebapp.war -d mywebapp

要让你的域名能直接访问到这个web应用,有两处需要注意。1)在/etc/httpd/conf/httpd.conf里增加一个VirtualHost,内容见下面代码(DocumentRoot不需要修改);2)在/usr/share/tomcat6/conf/server.xml里该web应用的<context>的path属性应设置为""(不能设置为"/",否则tomcat里request.getContextPath()将返回"/"而非空字符串,可能导致一些URL失效,参考链接)。Update 2010/10/10: 在httpd.conf里还应该指定ServerName为你的域名,否则启动httpd时会提示“无法可靠获取域名”的警告。

<VirtualHost *:80>
    DocumentRoot /var/www/html/myapp
    ServerName www.mydomain.com
    JkMount /* ajp13
</VirtualHost>

配置虚拟主机

Apache里的配置(注意使用ServerAlias使得www.mydomain.cnmydomain.cn都可访问。“The ServerAlias directive sets the alternate names for a host, for use with name-based virtual hosts. The ServerAlias may include wildcards” 参考链接):

<VirtualHost *:80>
    DocumentRoot /var/www/html/myapp
    ServerName www.mydomain.cn
    ServerAlias mydomain.cn
    JkMount /* ajp13
</VirtualHost>

Tomcat里的配置(同样注意Alias的使用。 “A common use case for this scenario is a corporate web site, where it is desireable that users be able to utilize either www.mycompany.com or company.com to access exactly the same content and applications.” 参考链接):

<Host name="www.mydomain.cn" appBase="webapps" unpackWARs="true" autoDeploy="true">
    <Alias>mydomain.cn</Alias>
    <Context ...>...</Context>
</Host>

配置smtp/pop服务

使用postfix实现smtp,用cyrus实现pop和imap,过程可参考《PostFix安装配置详解》。 

需要注意的,cyradm登录时要指定--auth plain

安装Discuz!论坛

这里安装的是Discuz! 7.2版本,方法可参考此链接。 其中可能需要yum install php,php-mysql,php安装以后会自动配置apache的参数(配置文件在conf.d下),重启apache即可。

配置好以后,进入http://hostname/install/index.php即可完成后续配置,包括config.inc.php文件都可以在向导里完成了。

服务器启动时自动启动服务

chkconfig vsftpd on
chkconfig httpd on
...

检查是否已设定成功(完整服务列表在/etc/rc.d/init.d/下)

chkconfig --list

给micro instance增加swap分区

micro版本的虚拟机只分配600m左右的内存,如果无法支持应用所需,可以考虑使用swap分区。

方法参考此链接 ,但是要注意增加swap后,EBS的I/O次数会上升很多,导致额外的费用,经验教训参考此链接

增加EBS分区

如果EC2的磁盘容量不够用,可以在aws console里创建新的EBS分区,然后挂载到EC2上使用。方法也比较简单见此链接,要注意的是在aws console里attach后,到EC2里别忘了要先mkfs一下才能mount,最好在fstab里设置为自动挂载。

定时启动/停止ec2 instance以节省费用

如果服务器只在白天有人访问,可以让它晚上关闭,这样可以节约大约1/3的费用。

需要在另外一台ec2服务器上设定定时命令,参考这个链接。当然,在停止期间是无法继续对外提供服务的。

注意,在crontab里直接运行ec2-start-instances这样的命令可能因为环境问题而失败,最好把这些命令写成单独的脚本文件,然后在crontab里调用。参考链接 

另外注意服务器的时区是否与任务执行的时间匹配。

安装SVN服务

用以下命令安装subversion并在指定目录创建一个repository。

> yum install subversion
> svnadmin create /root/svn/repo1

然后分别修改/root/svn/repo1/conf目录下的svnserve.confpasswdauthz这三个文件就可以了。具体的配置方法不复杂,可参考这个链接

启动svn服务:

svnserve -d -r /root/svn

开机自动启动svn服务:编辑start-svn.sh脚本内容如下并添加执行权限(chmod +x):

#!/bin/bash
svnserve -d -r /root/svn

然后在/etc/rc.local文件最后一行增加对start-svn.sh的调用。

注:svn实际是安装在DigitalOcean提供的云主机上,而非amazon的ec2上,环境为CentOS 6.5 x64。

安装pptpd服务(VPN)

可参考这篇帖子,如果连接出现问题,可tail /var/log/messages查看具体原因,然后google解决。

另一篇帖子

使用中遇到的问题和解决

1、instance运行正常但无法访问(ssh, http, ftp)

这种情况应该比较少遇到,forum给的答案是host环境出了些问题,解决方法是强制停止并重启,这时aws会在其他host上部署这个instance,问题也就随之解决。原话引用:

The instance is on a host that is experiencing some issues, at this point the best way to recover your instance would be to perform a force stop (adding --force to ec2-stop-instances command or by using the "force stop" action in ElasticFox) in order to have the instance stopped and be able to start it again on a new host.

2、问题同上

2012/8/14夜间遇到和上面一样的问题,这次的原因是elastic IP被墙。解决方法是不停更换elastic IP并"telnet xxx 22"直到能连接,然后将域名指向新的IP地址。

3、Tomcat吃掉过多VIRT内存的问题

尚未找到解决方案。(网上有不少人问过这个问题,目前的结论是不需要过于担心VIRT的数字,以RES的值为准。)

4、httpd进程占用过多内存

服务器运行一段时间以后,top发现有多个httpd进程并且每个进程都占用不少内存。首先free -m查看实际可用内存还有多少(重点看-/+ buffers/cache这一行的值,Linux Ate My Ram,居然有专门的域名解释这个问题),如果还有很多则不用管,这只是linux管理内存的方式而已。如果确实没有多少可用内存了,试试网上查到的这个解决方法:在httpd.conf里将workers.c模块下的MaxRequestsPerChild值设置为一个较小的值,例如50(缺省为0,表示不限制数量)。参考链接

更新2015/10/21: 今天检查/var/log/httpd/access_log时发现,有大量对/xmlrpc.php的请求,来自三个ip,平均每秒两三次,应该是服务器被利用wordpress的xmlrpc漏洞了(aliyun的云盾没有提示)。

file

初步先删除wordpress/xmlrpc.php文件解决,稍后看效果是否需要进一步处理。备选方案链接

更新:觉得还是禁止ip的方式更好,方法很简单,在httpd.conf里加下面的语句(apache建议在能修改httpd.conf的情况下不要使用.htaccess方式,会拖慢服务器速度)。

<Directory "/var/www/html/wordpress">
    Deny from 159.253.151.106
    Deny from 159.253.151.108
    Deny from 159.253.151.110
</Directory>

GIS相关问题记录

本文记录使用Java处理GIS数据过程中遇到的问题和解决方法。

1、uDig里创建新图层

uDig is an open source (EPL and BSD) desktop application framework, built with Eclipse Rich Client (RCP) technology.

我用uDig版本1.1.1,找了很久才找到怎样创建(而不是“添加”)一个新图层,感谢HXY,应该是在主菜单里选择Layer->Create。 主菜单File->New->New Layer打开的实际是“添加”图层对话框,而在Map的右键菜单里也只有Add...而没有Create。在Layer的右键菜单里倒是有Operations->Create Feature Type,但当时我没把它和Create Layer联系起来,而且它上面还有一个Add Feature Type菜单项,实现的是一模一样的功能,不知道为什么要设置两个不同名字。感觉uDig的UI在“User Friendly”方面还有很大的改进空间。

2、uDig对已有的Layer添加(修改)Attributes

可通过Layer的Operations->Reshape操作实现,具体步骤见uDig帮助的Users Guide: Adding a column to a shapefile参考链接

3、在用OpenLayers展示WMS(Web Map Service)地图数据时指定所需要的样式(Style)

一般我们用GeoServer作为服务器的时候,在后台已经为每个地图都指定了对应的样式。客户端如果需要改变这个缺省的风格,可以通过styles参数指定。对OpenLayers来说,就是在创建OpenLayers.Layer.WMS对象的时候指定这个参数,它的值必须是服务器上已经存在的风格的名称。下面是一个例子:

var myLayer =new OpenLayers.Layer.WMS(  
                "My Layer", "http://127.0.0.1:8080/geoserver/wms",
                {
                    srs: 'EPSG:4326',
                    width: '800',
                    styles: 'another_style_registered_on_the_server',
                    height: '494',
                    layers: 'topp:CHN_water_areas_dcw',
                    format: 'image/png',
                    tiled: 'true',
                    tilesOrigin : "85.65425605773926,18.746308612823487",
                    transparent: true                },
                {
                    'opacity': 0.50, 'isBaseLayer': true, 'wrapDateLine': true, 'buffer':0
                }

            );

暂时还没有找到不需要事先在服务器上注册样式实现这个目的(即完全在客户端控制图层显示)的方法,而OpenLayers的样式控制功能里的StyleMap只能实现对Vector类型图层的显示控制。另一种可能的方式是动态生成SLD(Dynamic SLD),似乎(因为在OpenLayers的文档里没有提到)WMS有一个"SLD"参数可以指定使用任意地址的SLD,这个地址有一些讨论,但我加上这个参数后没有试出任何效果。

4、在GeoServer的样例页面里查看发布的地图时提示错误信息_The requested Style can not be used with this layer_. The _style_ specifies an attribute of XXX and the _layer_ is: topp:xxxx

原因是图层topp:xxxx里不包含样式文件(SLD)里引用的"XXX"属性,例如样式里指定地图上根据人口对不同国家着色,但图层里没有人口这个属性,就会出现这个异常。多数情况是在GeoServer里发布数据的时候指定错了图层。

JSF开发问题和解决

file

本文记录使用Java Server Faces开发web应用过程中遇到的问题和解决方法。

1、在<f:subview>里的<h:commandLink>的action不执行

很多时候<f:subview>是在包含页面的情况下被用到(例如包含一个导航页面),而被包含的页面里如果有非JSF标签(如<a>)的时候,必须额外使用<f:verbatim>包含它才不会报错。问题是<f:verbatim>包含的内容是不算在JSF的Component Tree里的,因此这里的<h:commandLink>的action就不会被执行了。解决的办法是不要在<f:verbatim>里用<h:commandLink>,即尽量全部使用JSF的标签比较不容易出问题。参考链接

2、还是在<f:subview>里,action属性的方法虽然执行了,但不能转到faces-config里定义的目标页面

检查faces-config.xml<from-view-id>,如果页面被包含的话,则<from-view-id>可能应为/*,而不是如/navigatorbar.jspx这样。

3、结合EMF使用时,页面抛出找不到属性异常Error getting property 'xxx' from bean of type XXXX

EMF生成的XXXImpl里的构造方法是protected修饰的,改为public即可。(注意修改@generated修饰,否则下次重新生成时会被覆盖回来)

另(不仅限于EMF的情况):如果一个Bean里有两个同名但参数不同的方法,例如Customer有getRecords()getRecords(int year)这两个方法,则在JSF页面里用#{customer.value}会抛出同样的异常,我暂时还不确定是EL的问题还是JSF实现(我用的trinidad)的问题,部分异常stack如下所示:

严重: Servlet.service() for servlet faces threw exception
javax.faces.el.PropertyNotFoundException: Error getting property 'xxx' from bean of type XXXX
    at com.sun.faces.el.PropertyResolverImpl.getValue(PropertyResolverImpl.java:107)
    at com.sun.faces.el.impl.ArraySuffix.evaluate(ArraySuffix.java:167)
    at com.sun.faces.el.impl.ComplexValue.evaluate(ComplexValue.java:151)
    at com.sun.faces.el.impl.ExpressionEvaluatorImpl.evaluate(ExpressionEvaluatorImpl.java:243)
    at com.sun.faces.el.ValueBindingImpl.getValue(ValueBindingImpl.java:173)
    at com.sun.faces.el.ValueBindingImpl.getValue(ValueBindingImpl.java:154)
    at org.apache.myfaces.trinidad.bean.FacesBeanImpl.getProperty(FacesBeanImpl.java:66)
    at org.apache.myfaces.trinidad.component.UIXComponentBase.getProperty(UIXComponentBase.java:1100)
    at org.apache.myfaces.trinidad.component.UIXIterator.getValue(UIXIterator.java:415)
    at org.apache.myfaces.trinidad.component.UIXCollection._flushCachedModel(UIXCollection.java:1127)
    at org.apache.myfaces.trinidad.component.UIXCollection.encodeBegin(UIXCollection.java:511)
    at org.apache.myfaces.trinidadinternal.uinode.UIComponentUINode._renderComponent(UIComponentUINode.java:317)
    at org.apache.myfaces.trinidadinternal.uinode.UIComponentUINode.render(UIComponentUINode.java:279)

解决的办法是把带参数的那个方法改名。

4、从session里删除一个bean

ValueBinding binding =FacesContext.getCurrentInstance().getApplication().createValueBinding("#{MyBean}");
binding.setValue(context, null);

参考链接

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2009/02/19/1394275.html