很多公司在招聘高级系统维护人员的时候,希望应聘者有一些开发经验或者要会写程序,至少也要读得懂用某些语言写的程序并能作简单修改。这并非是无事找事,故意抬高门槛。实际上,有的时候,为了能够更好地监控我们的系统,或者验证我们安装的系统满足应用的要求,我们必须要使用一些代码。比如,我们的应用服务器 运行正常吗?我们的数据库连接正常吗?我们的服务器把 HTTP request 正确地传给了我们的应用服务器吗?我们的MySQL + Resin 能正常处理汉字吗? 这些在实践中都是常常遇到的问题,下面我们来仔细说说。
一. 应用服务器运行正常吗?
相对于其它应用来说,WEB 应用比较特殊,因为它需要放到应用服务器或者说 WEB 容器里面来运行。由于既有 WEB 应用,又有 WEB 应用服务器,两者交织在了一起,当出现问题的时候,可能就会出现不能快速准确定位到底是 WEB 应用的问题还是 WEB 应用服务器的问题。所有,我们应该有相应的措施来区分问题到底出现在哪个身上。笔者在实践中采取的措施是,用了一个简单的 JSP 页面来实时验证 WEB 应用服务器的状况,当监控系统(比如what'sup)保障的时候,监控人员只需要通过查看 WEB 应用服务器监控页面,就知道到底是哪个的问题了,然后采取相应的措施。笔者所用的代码可以说是再简单不过了:
# cat $RESIN_HOME/webapps/ROOT/test_resin.jsp
<%= 1 + 1 %>
然后,在 What'sup 里面进行配置,让它去 GET 该页面,如果能够取到值2, 就显示正常,否则,报警。
二. 数据库连接正常吗?
有一段时间,数据库老出问题,监控人员分不清到底是系统问题还是数据库问题,就老是“骚扰”我,比较痛苦。为此,笔者“凑”了下面的代码,来监控数据库:
# cat $RESIN_HOME/webapps/ROOT/test_oracle.jsp
<%@ page contentType="text/html;charset=gb2312" import="java.sql.*" %>
<%
// define variables
String CLASS="oracle.jdbc.driver.OracleDriver";
String NODE1_AND_DB="jdbc:oracle:thin:@(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 172.17.1.3)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME =RACDB.GLOBAL)))";
String NODE2_AND_DB="jdbc:oracle:thin:@(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 172.17.1.4)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME =RACDB.GLOBAL)))";
String USER="";
String PASSWORD="";
String SQL="select count(*) from tab";
<%
// define variables
String CLASS="oracle.jdbc.driver.OracleDriver";
String NODE1_AND_DB="jdbc:oracle:thin:@(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 172.17.1.3)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME =RACDB.GLOBAL)))";
String NODE2_AND_DB="jdbc:oracle:thin:@(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = 172.17.1.4)(PORT = 1521))(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME =RACDB.GLOBAL)))";
String USER="
String PASSWORD="
String SQL="select count(*) from tab";
// init
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// check NODE1
try
{
Class.forName(CLASS);
conn = DriverManager.getConnection(NODE1_AND_DB,USER,PASSWORD);
stmt = conn.createStatement();
rs=stmt.executeQuery(SQL);
out.println("
");
out.println("172.17.1.3 is OK");
}
catch(SQLException e)
{
out.println("172.17.1.3 is not OK");
}
finally
{
try
{
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
}
catch(SQLException e)
{
out.println("DB close error " + getClass());
}
}
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// check NODE1
try
{
Class.forName(CLASS);
conn = DriverManager.getConnection(NODE1_AND_DB,USER,PASSWORD);
stmt = conn.createStatement();
rs=stmt.executeQuery(SQL);
out.println("
");
out.println("172.17.1.3 is OK");
}
catch(SQLException e)
{
out.println("172.17.1.3 is not OK");
}
finally
{
try
{
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
}
catch(SQLException e)
{
out.println("DB close error " + getClass());
}
}
// check NODE2
try
{
Class.forName(CLASS);
conn = DriverManager.getConnection(NODE2_AND_DB,USER,PASSWORD);
stmt = conn.createStatement();
rs=stmt.executeQuery(SQL);
out.println("
");
out.println("172.17.1.4 is OK");
}
catch(SQLException e)
{
out.println("172.17.1.4 is not OK");
}
finally
{
try
{
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
}
catch(SQLException e)
{
out.println("DB close error " + getClass());
}
}
%>
try
{
Class.forName(CLASS);
conn = DriverManager.getConnection(NODE2_AND_DB,USER,PASSWORD);
stmt = conn.createStatement();
rs=stmt.executeQuery(SQL);
out.println("
");
out.println("172.17.1.4 is OK");
}
catch(SQLException e)
{
out.println("172.17.1.4 is not OK");
}
finally
{
try
{
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
}
catch(SQLException e)
{
out.println("DB close error " + getClass());
}
}
%>
由于我们的数据库都是 RAC 来的,所以需要监控两个结点,从上面的代码可以看出,该页面能够监控RAC的两个结点。 然后,在 What'sup 里面配置,让它去 GET 该页面,如果返回"is OK",则显示正常,否则,报警。
注意,尽管该代码是监控 ORACLE RAC 的,但是只要经过简单修改,就可以用来监控其它数据库了。相信读者有修改这点简单代码的功力,所以笔者就不具体阐述了。
三. 服务器把 HTTP request 正确地传给了我们的应用服务器吗?
曾经遇到过一个怪事情,笔者维护的一台服务器的 RESIN 不能正常处理 HTTP GET 请求,但是 telnet 8080 端口又没有问题。开发人员怀疑,是不是笔者的服务器有没有把 HTTP GET 请求正确地发给 RESIN,或者是 RESIN 不能正确处理 HTTP GET 请求。于是,笔者搞了下面的代码,来表明自己的“清白”,呵呵呵呵。
# cat $RESIN_HOME/webapps/ROOT/disp_http_request_header.jsp
<%@ page contentType="text/html; charset=gb2312" language="java" import="java.sql.*,java.util.*"%>
<%
out.print("1.Local Address: " + request.getLocalAddr() + "
");
out.print("2.Local Name: " + request.getLocalName() + "
");
out.print("3.Server Name: " + request.getServerName() + "
");
out.print("4.Server Port: " + request.getServerPort() + "
");
out.print("5.Remote Host: " + request.getRemoteHost() + "
");
out.print("6.User Agent: " + request.getHeader("user-agent") + "
");
out.print("7.User Agent: " + request.getHeader("referer") + "
");
out.print("8.Protocol: " + request.getProtocol() + "
");
out.print("9.Method: " + request.getMethod() + "
");
out.print("10.Request URI: " + request.getRequestURI() + "
");
out.print("11.Servlet Path: " + request.getServletPath()+ "
");
out.print("12.Path Info: " + request.getPathInfo() + "
");
out.print("13.Real Path: " + request.getRealPath("/")+ "
");
%>
<%
out.print("1.Local Address: " + request.getLocalAddr() + "
");
out.print("2.Local Name: " + request.getLocalName() + "
");
out.print("3.Server Name: " + request.getServerName() + "
");
out.print("4.Server Port: " + request.getServerPort() + "
");
out.print("5.Remote Host: " + request.getRemoteHost() + "
");
out.print("6.User Agent: " + request.getHeader("user-agent") + "
");
out.print("7.User Agent: " + request.getHeader("referer") + "
");
out.print("8.Protocol: " + request.getProtocol() + "
");
out.print("9.Method: " + request.getMethod() + "
");
out.print("10.Request URI: " + request.getRequestURI() + "
");
out.print("11.Servlet Path: " + request.getServletPath()+ "
");
out.print("12.Path Info: " + request.getPathInfo() + "
");
out.print("13.Real Path: " + request.getRealPath("/")+ "
");
%>
然后,再从浏览器里面请求该页面,一切正常。看来,不是笔者服务器或者 RESIN 的问题。后来,经过开发的反复折腾,重要找到了问题,都是 filter 惹的祸 !!!
四. MySQL + Resin 能正常处理汉字吗?
摆弄过 MySQL 5.0.x + Resin 3.0.x 的读者可能遇到过,这种比较优秀的搭配,有时候在处理汉字的时候会“闹鬼”,不能正常处理汉字。所以,我们在安装了这个配置之后,最好是用代码来验证一下,自己的安装配置有没有问题。当然,首先你得保证,你的 LINUX 系统的字符集没有问题,这样配置 i18n 文件可以保证你的 LINUX 系统字符集没问题。
# cat /etc/sysconfig/i18n
LANG="zh_CN.GB18030"
LANGUAGE="zh_CN.GB18030:zh_CN.GB2312:zh_CN"
SUPPORTED="zh_CN.UTF-8:zh_CN:zh:en_US.UTF-8:en_US:en"
SYSFONT="lat0-sun16"
LANG="zh_CN.GB18030"
LANGUAGE="zh_CN.GB18030:zh_CN.GB2312:zh_CN"
SUPPORTED="zh_CN.UTF-8:zh_CN:zh:en_US.UTF-8:en_US:en"
SYSFONT="lat0-sun16"
接下来,在安装MySQL 5.0.x 的时候,选择默认字符集为 gb2312。然后,就可以用下面的代码来验证了,
# cat $RESIN_HOME/webapps/ROOT/test_resin_mysql_cn.jsp
<%@ page contentType="text/html;charset=gb2312" import="java.sql.*" %>
<%
// define variables
String CLASS="com.mysql.jdbc.Driver";
// note: default character set of mysql is gb2312
// version of connectorJ is greater 5.0
String dbString = "jdbc:mysql://localhost/test?user=admin&password=password&useUnicode=true&characterEncoding=gb2312";
String SQL_create_table = "create table test.cn(col1 varchar(10))";
String SQL_insert_cn = "insert into test.cn values('中文')";
String SQL_select_cn = "select * from test.cn";
<%
// define variables
String CLASS="com.mysql.jdbc.Driver";
// note: default character set of mysql is gb2312
// version of connectorJ is greater 5.0
String dbString = "jdbc:mysql://localhost/test?user=admin&password=password&useUnicode=true&characterEncoding=gb2312";
String SQL_create_table = "create table test.cn(col1 varchar(10))";
String SQL_insert_cn = "insert into test.cn values('中文')";
String SQL_select_cn = "select * from test.cn";
// init
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// check
try
{
Class.forName(CLASS);
conn = DriverManager.getConnection(dbString);
stmt = conn.createStatement();
stmt.execute(SQL_create_table);
stmt.execute(SQL_insert_cn);
rs = stmt.executeQuery(SQL_select_cn);
while(rs.next())
{
out.println(rs.getString("col1"));
out.println("
");
}
}
catch(SQLException e)
{
out.println("DB operation error " + getClass());
}
finally
{
try
{
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
}
catch(SQLException e)
{
out.println("DB close error " + getClass());
}
}
%>
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// check
try
{
Class.forName(CLASS);
conn = DriverManager.getConnection(dbString);
stmt = conn.createStatement();
stmt.execute(SQL_create_table);
stmt.execute(SQL_insert_cn);
rs = stmt.executeQuery(SQL_select_cn);
while(rs.next())
{
out.println(rs.getString("col1"));
out.println("
");
}
}
catch(SQLException e)
{
out.println("DB operation error " + getClass());
}
finally
{
try
{
if(rs != null) rs.close();
if(stmt != null) stmt.close();
if(conn != null) conn.close();
}
catch(SQLException e)
{
out.println("DB close error " + getClass());
}
}
%>
然后,用浏览器去请求该页面,如果你看到了“中文”这两个字,恭喜你,你的安装配置没问题。
总之,上面的这些代码并不复杂。但是,在实践中,它们确实有用,所有,学学还是有好处的。
注意,尽管笔者是在 RESIN 里面使用这些代码,但是,毫无疑问,只要稍做改变(甚至不需做任何改变),这些代码也可以用在其它 WEB 应用服务器里面。