很多公司在招聘高级系统维护人员的时候,希望应聘者有一些开发经验或者要会写程序,至少也要读得懂用某些语言写的程序并能作简单修改。这并非是无事找事,故意抬高门槛。实际上,有的时候,为了能够更好地监控我们的系统,或者验证我们安装的系统满足应用的要求,我们必须要使用一些代码。比如,我们的应用服务器 运行正常吗?我们的数据库连接正常吗?我们的服务器把 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="<username>";
   String PASSWORD="<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("<br>");
      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("<br>");
      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()  + "<br>");
   out.print("2.Local Name:      " + request.getLocalName()  + "<br>");
   out.print("3.Server Name:     " + request.getServerName() + "<br>");
   out.print("4.Server Port:       " + request.getServerPort() + "<br>");
   out.print("5.Remote Host:     " + request.getRemoteHost() + "<br>");
   out.print("6.User Agent:        " + request.getHeader("user-agent") + "<br>");
   out.print("7.User Agent:        " + request.getHeader("referer")    + "<br>");
   out.print("8.Protocol:            " + request.getProtocol()   + "<br>");
   out.print("9.Method:             " + request.getMethod()     + "<br>");
   out.print("10.Request URI:    " + request.getRequestURI() + "<br>");
   out.print("11.Servlet Path:     " + request.getServletPath()+ "<br>");
   out.print("12.Path Info:         " + request.getPathInfo()   + "<br>");
   out.print("13.Real Path:         " + request.getRealPath("/")+ "<br>");
%>
然后,再从浏览器里面请求该页面,一切正常。看来,不是笔者服务器或者 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"
接下来,在安装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";
   // 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("<br>");
      }
   }
   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 应用服务器里面。