Java环境下Imagemagick中文图片处理问题非完美解决方案

在处理服务器与客户端交互时候,基于性能考虑(参考手机客户端网络加速技术方案实现思考),对软件介绍中的图片采用缩略图进行展现,这样涉及了实际图片缩略图的动态处理问题,采用Java的Runtime.getRuntime().exec直接来调用Imagemagick的命令来完成缩略图的动态生成。原本是一个很简单的问题,只不过由于在图片的目录路径及图片名称中存在中文,因此在处理时候又出现了FileNotFoundException,记录一下非完美解决方案。

1、系统环境:

操作系统:Red Hat Enterprise Linux AS release 4 (Nahant Update 4)

操作系统用户环境变量(参考Linux环境下资源下载中文目录及中文文件名称问题):

export LC_ALL=zh_CN.GB18030

export LANG= zh_CN.GB18030

JVM、Tomcat、数据库等相关的字符集(参考Struts2中Datetimepicker控件的中文问题):UTF-8

2、需求场景

手机客户端对于图片的请求分为两大类:图片的下载请求、图片的预览请求。

图片的下载请求:客户端请求下载实际的图片内容,此时候服务器端不需要对图片进行任何加工处理,只需要正确返回实际的图片内容。

图片的预览请求:客户端并不需要下载完整的图片内容,只需要在客户端能够预览图片,因此在分辨率及图片质量上可以稍稍降低,以节省带宽资源。在手机客户端发起图片预览请求时候,服务器端响应的所有的图片都采用缩略图(降低图片大小)及低分辨率(降低图片质量)的方式返回。

备注:通过手机浏览器或手机客户端下载1M左右以上的资源时候,如果采用Struts2的Stream Result,存在服务器端无响应的现象,具体原因没有查明,有空看看Struts2代码再说,采用传统的response写流的方式实现没有此问题。

3、实现机制

采用Runtime.getRuntime().exec(“/usr/bin/convert  -sample 75%x75% /test.jpg /test_thumb.jpg”)来动态生成缩略图。

备注:

a、考虑到jmagick并不能完整支持Imagemagick所有命令集且使用上并不是很方便,因此没有采用jmagick,直接使用Runtime.getRuntime().exec(“/usr/bin/convert  -sample 75%x75% /test.jpg /test_thumb.jpg”)的方案,使用方法具体可以参考在线主题制作技术实现方案

b、这里使用的imagemagick的命令只供测试使用,不一定是最佳的命令,具体查一下imagemagick的手册

c、由于Imagemagick生成图片相对较慢,因此并不需要每一次都生成缩略图,可以将缩略图存放在与原有图片相同的目录路径下,这样在客户端发起图片预览请求时候,服务器端先查看一下请求图片的预览图是否存在,如果存在直接返回以前生成的预览图,如果不存在则动态生成后再返回。

4、问题

在使用Runtime.getRuntime().exec调用convert来动态生成缩略图时候,由于资源在Linux服务器上存放目录及文件名称都存在中文(例如/products/material/img/101×80 /人物肖像101×8/测试.jpg),因此在调用时候会报FileNotFoundException错误。

5、方案测试

测试命令:

/usr/bin/convert -sample 75%x75%   /img/101×80/人物肖像101×8/测试.jpg  /img/101×80/人物肖像101×8/测试_thumb.jpg

试验1:

操作:操作系统编码为zh_CN.GB18030,因此在shell命令行执行convert,如果传入的字符串参数为正常的zh_CN.GBK(比zh_CN.GB18030范围小)的编码,那么不会乱码。

结果:在命令行执行,已验证。

试验2:

操作:JVM环境变量为UTF-8,Java内部本身也是采用UTF-8编码,Runtime.getRuntime().exec调用convert时候,如果将参数进行转码成zh_CN.GBK,则应该能够正常处理

private static final String CMD=”/usr/bin/convert “;
private static final String PRAM=” -sample 75%x75%  “;

inputPath=new String(inputPath.getBytes(“UTF-8″),”GBK”);
outPutPath=new String(outPutPath.getBytes(“UTF-8″),”GBK”);
Process pc=Runtime.getRuntime().exec(CMD+PRAM+inputPath+” “+outPutPath);

结果:以上代码不行,将getBytes的编码换成GBK、ISO8859-1,输出编码换成UTF-8、ISO8859-1等也一样

试验3:

操作:将JVM环境变量修改为GBK

结果:似乎也不行

试验4:

操作:在java代码中,将中文路径/img/101×80/人物肖像101×8/测试.jpg  /img/101×80/人物肖像101×8/测试_thumb.jpg写入普通文本文件/home/client.aouu.com/img/img2.txt,然后在命令行调用convert来生成

Java代码(示例,不规范):

FileWriter os = new FileWriter(“/home/client.aouu.com/img/img1.txt”);
String imgPath=inputPath+” “+outPutPath;
os.write(imgPath);
os.close();
Process pc=Runtime.getRuntime().exec(“/home/client.aouu.com/img/img.sh”);
pc.waitFor();

img.sh:

#!/bin/sh

cat /home/client.aouu.com/img/img1.txt |xargs /usr/bin/convert  -sample 75%x75%

结果:不行,报告-bash: ./img.sh: cannot execute binary file

试验5:

操作:与试验4操作相同

img.sh:

#!/bin/sh

iconv -f UTF-8 -t GBK -c /home/client.aouu.com/img/img1.txt -o /home/client.aouu.com/img/img2.txt
cat /home/client.aouu.com/img/img2.txt |xargs /usr/bin/convert  -sample 75%x75%

结果:OK

结果分析:

尽管在java代码中将中文路径、中文文件名称的编码转码成GBK,但java内部本身是UTF-8编码,因此JVM通过Runtime.getRuntime().exec传给/usr/bin/convert的参数实际上仍然为UTF-8编码(?是什么编码,尚待验证),并不是GBK编码,因此convert获取输入参数后无法在操作系统找到对应的文件。

通过FileWriter写入文件的字符集也为UTF-8,通过iconv强制将UTF-8编码转化为GBK,这样convert能够正常处理。

具体的原因及机理尚待进一步考察验证,现在尚无好的办法,姑且凑合使用此种方案再说。

在linux下使用nfs及软链接解决图片文件共享问题

    由于在门户社区中,诸如图片、下载软件这样的资源都较大,且在下载过程中,对带宽资源的占用也较大,因此为不影响正常业务应用,一般情况下都是将图片等资源部署到一台单独的服务器上以分配单独的带宽(在同一台服务器上也将这些资源存放在磁盘空间较大的地方),此种情况下图片等资源与应用不一定在同一台服务器上,即使在同一台服务器上也不一定部署在同一目录下。方案如下:

1、 图片等资源部署在单独的另外一台服务器上

此种模式可以通过linux的nfs来把完成远程文件mount为本地的文件目录(类似于windows的网络共享),步骤如下:

下面以将192.168.1.199(NFS服务器端)上的目录/liang mount到192.168.1.202(NFS客户端)上为例,说明一下远程mount的实现

1) NFS服务端(192.168.1.199)执行如下操作

  • 在NFS服务端(192.168.1.199)修改/etc/exports,内容如下
         /liang 192.168.1.0/255.255.255.0(ro,no_root_squash,sync)

格式如下:directory machine1(option11,option12)

       注意:好像采用很多文档所说的 /liang 192.168.1.*(ro,no_root_squash,sync)存在问题
  • 在NFS服务器端启动NFS:

/etc/rc.d/init.d/portmap start

/etc/rc.d/init.d/nfs start

  • exportfs命令:

在启动了NFS之后又修改了/etc/exports后可以用exportfs命令来使改动立刻生效,命令格式如下:

exportfs [-aruv]

-a :全部mount或者unmount /etc/exports中的内容

-r :重新mount /etc/exports中分享出来的目录

-u :umount 目录

-v :在 export 的时候,将详细的信息输出到屏幕上。

2) 在NFS客户端(192.168.1.202)执行如下操作

  • 建立本地mount点目录

mkdir /liang

  • mount远端目录到本地目录

mount 192.168.1.199:/liang /liang

  • 修改/etc/fstab以在系统重启时候自动mount:
     # device       mountpoint     fs-type     options      dump fsckorder
     192.168.1.199:/liang  /liang    nfs          rw            0    0
  • showmout命令

-e :显示指定的NFS SERVER上export出来的目录。

-a :是用来显示已经mount上本机nfs目录的cline机器,一般在NFS SERVER上使用

2、 图片等资源与应用部署在同一台服务器上,但资源目录与应用目录不在一处

此种情况,采用linux的软链接把图片等资源链接链接到应用的目录下。

ln  -s /image /www/newwap/wap/image

由于安全上的考虑,tomcat及jboss缺省情况下都不允许访问软链接的文件,改动如下:

在conf/server.xml中或conf/Catalina/localhost(根据虚拟主机配置情况做相应改变)下的context中增加如下内容:

<Context path=”" docBase=”/www/newwap/wap”  allowLinking=”true”>

<Resources className=”org.apache.naming.resources.FileDirContext”

allowLinking=”true”/>

<Logger className=”org.apache.catalina.logger.FileLogger”/>

</Context>

3、参考资料

http://nfs.sourceforge.net/nfs-howto/ar01s04.html

Technorati Tags: ,,,,,

Subversion Redhat Linux版本简易安装指南

  记录一下采用简易的安装步骤,没有与apache集成,只是把subversion作为一个单独的服务安装和使用。

操作系统版本:Redhat AS 4

1、 下载安装rpm包

http://the.earth.li/pub/subversion/summersoft.fay.ar.us/pub/subversion/latest/rhel-4/i386/

下载并安装如下包:

apr-0.9.12-2.i386.rpm              

apr-util-0.9.12-1.i386.rpm         

subversion-1.4.6-1.i386.rpm        

subversion-debuginfo-1.4.6-1.i386.rpm

subversion-devel-1.4.6-1.i386.rpm  

subversion-perl-1.4.6-1.i386.rpm   

subversion-tools-1.4.6-1.i386.rpm  

swig-1.3.25-1.i386.rpm             

由于AS4的Subversion的版本较老,使用最新版本的SVN,因此需要卸载与新版本冲突的包,安装最新的包:

卸载包:rpm –e 包名

安装包:rpm –ivh *rpm

2、 创建svn目录

mkdir /opt/svn

3、 修改svnserve.conf

anon-access = none

auth-access = write

password-db = passwd

4、 修改authz

[groups]

developer = liang1,liang2,liang3

[repository:/opt/svn/eSales]

@developer =rw

5、 在passwd中创建对应用户的密码

[users]

liang1= liang1

liang2=liang2

liang3=liang3

6、 在/opt/svn下创建eSales的代码库

svnadmin create /opt/svn/eSales

7、 导入eSales的代码

svn import eSales svn://localhost/eSales -m “Initial Import” –username liang1 –password liang1

8、 启动svn

svnserve -d -r /opt/svn

9、 把启动命令放到启动脚本中

echo “svnserve -d -r /opt/svn”>>/etc/rc.local

 

Technorati 标签: ,,

mysql 数据库cpu 占用99.9%问题调优札记

  新公司的系统一直很不稳定,店面销售人员经常报登不上系统或速度奇慢的情况,怀疑可能是代码存在数据库连接泄露及内存泄露现象,离春节只有几天时间,也来不急进行代码调优,只有从配置层面看有那些手段来采用,以便暂且缓解一下服务器压力,降低系统的故障率。为了第一时间能够知道服务器故障,基于nagios搭建了服务器监控程序,这样系统有故障时候,能够用短信方式通知系统故障,及时解决。

1、系统情况:

  操作系统:Redhat AS4

  数据库:mysql 4.1.18

  应用服务器:JBoss 3.2.7

  服务器: 4 x3.00GHz的Intel Xeon CPU

  数据库和应用服务器都部署在同一台服务器上。

  简单跟踪了一下,发现平常内存、io负载都不大,数据库连接数也不多。只是很奇怪的是mysql的cpu负载始终是99.9%,但整个系统的速度还行,开始怀疑是JVM、数据库参数、索引没有优化导致的,因此先着手对java虚拟机参数及数据库参数进行了调整。

2、java虚拟机调优

  • 调整虚拟机的参数

  JAVA_OPTS=”$JAVA_OPTS -Xms512m -Xmx1024m -server -XX:MaxPermSize=300m -XX:MaxNewSize=300m”

  • 调整jboss的数据库连接池,修改最大连接数及连接回收时间

<min-pool-size>20</min-pool-size>

<max-pool-size>300</max-pool-size>

<idle-timeout-minutes>1</idle-timeout-minutes>

<min-pool-size>20</min-pool-size>

3、数据库调优

  • 对所有的表,优化及增加索引。

发现一个好用的mysql工具navicat,感觉比ems 好用,用这东西增加索引方便多了。

  • 调整mysql参数

原来是基于my-medium.cnf 修改的参数,由于担心是大数据量查询sort区等不够及程序存在内存泄露问题,因此基于my-huge.cnf进行调整。

[client]
port            = 3306
socket          = /var/lib/mysql/mysql.sock
[mysqld]
port            = 3306
socket          = /var/lib/mysql/mysql.sock
skip-locking
key_buffer = 256M
max_allowed_packet = 1M
table_cache = 256
sort_buffer_size = 2M
read_buffer_size = 2M
read_rnd_buffer_size = 2M
myisam_sort_buffer_size = 64M
thread_cache_size = 8
query_cache_size= 32M
thread_concurrency = 8
max_connections=300

#skip-networking

#log-bin

server-id       = 1

[mysqldump]
quick
max_allowed_packet = 16M

[mysql]
no-auto-rehash
#safe-updates

[isamchk]
key_buffer = 128M
sort_buffer_size = 128M
read_buffer = 2M
write_buffer = 2M

[myisamchk]
key_buffer = 128M
sort_buffer_size = 128M
read_buffer = 2M
write_buffer = 2M

[mysqlhotcopy]
interactive-timeout

调整后,支撑了3天左右,除了mysql的cpu占用始终是99%外,系统整体运行基本正常,忙于其他事情,没有继续跟踪。没想到大年初一接了一堆报警短信,执行查看了系统参数,发现系统竟然没有swap区,欣喜一阵,可能是这原因吧,于是临时建立swap区。

4、增加swap区

  • 在/swap下生成1G的文件

     # mkdir /swap

  # dd if=/dev/zero of=/swap/swapfile bs=500M count=2

  • 创建为swap文件

  #mkswap /swap

  • 让swap生效

  #swapon /swap

  • 查看一下swap

  #swapon -s

  • 把新增的swap文件加到fstab文件中让系统引导时自动启动

  #vi /etc/fstab

  /swap/swapfile swap swap defaults 0 0

    增加后,重启应用及服务,mysql的cpu占用还是持续性为99.9%,而且运行上一段时间还是出现无法登录的情况。远程登录到系统,发现内存、io、swap区占用都很正常,数据库连接数也很正常,而且在停止mysql和jboss后,直接重启jboss,不能正常启动成功,需要等上一会儿,怀疑是文件句柄及tcp连接尚未正常释放。联系以前遇到的情况,怀疑与操作系统允许的最大句柄数有关。

用ulimit -a|grep open 命令查看了结果为:

open files                      (-n) 1024

用cat /proc/sys/fs/file-max查看结果为:

379816

由于数据库和jboss同时部署在同一台服务器上,在负荷较小的情况下用lsof -u root |wc -l查看root用户的句柄数仍然为700多,因此在负荷较高的情况下,用户的最大句柄数1024是有点小。

5、修改操作系统句柄数

5.1、修改操作系统的最大限制数

  • 修改 /etc/sysctl.conf

    增加fs.file-max = 8061540

  • 在/etc/pam.d/login 中添加  

    session     required      /lib/security/pam_limits.so

  • 在/etc/security/limits.conf 中添加
    root              -        nofile           1006154

  修改root用户的句柄数(包括hard和soft)限制为1006154

  • 修改 /etc/rc.local   添加
    echo 8061540 > /proc/sys/fs/file-max

 

5.2、修改用户最大限制数

考虑到重启服务器的风险,先暂时修改一下启动jboss的root用户的/root/.bash_profile,增加如下内容:

ulimit -n 65535

重启jboss和mysql。

连续观察了几天,发现cpu始终占用99.9%的情况解决掉了,继续观察中。

 

6、参考文档

http://www.bea.com.cn/support_pattern/Too_Many_Open_Files_Pattern.html

http://kbase.redhat.com/faq/FAQ_80_1540.shtm