使用Apache+Tomcat构建应用服务器集群

1.安装Apache HTTP server:http://httpd.apache.org/

2.安装2-3个(或更多)Tomcathttp://tomcat.apache.org/,安装zip版,因为安装多个Windows Service Installer版会出现错误(可以安装一个Installer版,其他两个用zip版)。

3.配置3个Tomcat,使得3个Tomcat在一台机器上可以同时运行:

修改三个Tomcat安装目录下的bin/startup.bat,将其中的CATALINA_HOME环境变量修改成互不相同的名字,比如CATALINA_HOME1,CATALINA_HOME2,CATALINA_HOME3。

4.配置集群

1) 修改Apache安装目录下的conf/httpd.conf文件,在文件末尾加入如下几行ProxyRequests Off <proxy balancer://cluster> BalancerMember ajp://127.0.0.1:8009 loadfactor=1 route=jvm1 BalancerMember ajp://127.0.0.1:9009 loadfactor=1 route=jvm2 BalancerMember ajp://127.0.0.1:9099 loadfactor=1 route=jvm3 </proxy>8009,9009,9099分别是三个Apache用来于Tomcat连接的端口oadfactor是指每一个tomcat的负载系数。

2) 修改Tomcat下的conf/server.xml

a.将所有3个Tomcat的server.xml中的

<Engine name="Catalina" defaultHost="localhost">

修改为<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">,3个tomcat的jvmRoute分别为jvm1,jvm2,jvm3

b.修改所有3个server.xml中的<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />,将8009端口改为与上面Apache中配置的三个端口一致(这点很重要!),与上面配置的jvm1,jvm2也要一致。也就是说,配置为jvm1的,这里端口改为8009,配置为jvm2的tomcat,这里端口给为9009,jvm3的为9099。

c.修改server.xml中的所有其他端口,使得3个tomcat没有重复的端口,包括:<Server port="8005",<Connector port="8080",redirectPort="8443" 这三个,三个tomcat的这三个端口都要不一样,其中每个tomcat中有两个redirectPort,同一个tomcat中的redirectPort一样,不同tomcat中的redirectPort不一样。

4.现在,集群就应该已经正确的构建完毕,访问Apache(localhost,端口为80)时,Apache就会将请求转发给这3个tomcat中的一个,可以自己写一个JSP或者servlet试试,注意,三个tomcat中的webapp下面都需要有一份JSP/servlet。

5.写一个实验程序(servlet),发布到3个tomcat上,写一个客户端程序,多线程并发访问Apache,观察三个tomcat的负载均衡情况(可以调节一下loadfactor再观察),并绘制在线程越来越多的情况下,响应时间曲线。

由于规模较小的计算,响应时间非常快,可以忽略。所以选择一个计算量较大的运算: 判断一个数是否为素数,Sevlet代码如下:package com.hw; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Servlet implementation class JudgePrimeServlet */ public class JudgePrimeServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String TRUE_JSON = "{success:true}"; private static final String FALSE_JSON = "{success:false}"; public JudgePrimeServlet() { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String numStr = request.getParameter("number"); boolean ans; if(numStr.length() < 10) ans = isPrime(Long.parseLong(numStr)); else ans = isPrime(new BigInteger(numStr)); System.out.println(numStr + " : " + ans); response.getWriter().write(ans ? TRUE_JSON : FALSE_JSON); response.getWriter().flush(); response.getWriter().close(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } public static boolean isPrime(long n) { if(n % 2 == 0) { if(n == 2) return true; return false; } long up = (long)Math.sqrt((double)n); for(int i = 3; i <= up; i += 2) if(n % i == 0) return false; return true; } public static boolean isPrime(BigInteger n) { if(n.mod(BigInteger.valueOf(2)).equals(BigInteger.ZERO)) { if(n.equals(BigInteger.valueOf(2))) return true; return false; } MathContext mc = new MathContext(n.toString().length(), RoundingMode.HALF_DOWN); BigInteger up = new BigDecimal(Math.sqrt(n.doubleValue()) ,mc).toBigInteger(); for(BigInteger i = BigInteger.valueOf(3); i.compareTo(up) <= 0; i = i.add(BigInteger.valueOf(2))) if(n.mod(i).equals(BigInteger.ZERO)) return false; return true; } },客户端的线程代码如下:public class ClientTestThread extends Thread { public static final long[] primeArr = new long[] { 2055437, 2057317, 5298991, 5300467, 5352869, 5377679, 6220229, 7155887, 8491247, 8885837, 9999929, 9999931, 9999943, 9999971, 9999973, 9999991, 11547391, 12531809, 13192643, 15766453, 16854379, 26459779, 26456789, 26453803, 29999791, 1000003871, 1015490783, 1800000011, 1800000019, 1800000047, 1800000049, 1800000053, 1800000061, 1800000109, 1800000113, 1800001523, 1978012607, 1978012609, 1978012633, 1978012669, 1978012703, 1978012709, 1978012739, 1978012741, 1978012759, 1978012763, 2008693679, 2008693681, 2008693727, 2008693741, 2008693759, 2095462079, 2095462099, 2095462151, 2095462163, 2095462207, 2099999999 }; private static Long timer = 0L; @Override public void run() { URL url; HttpURLConnection conn; long millisec = GregorianCalendar.getInstance().getTimeInMillis(); try { url = new URL("http://localhost/ClusterExp/JudgePrimeServlet?number=" + 2099999999); conn = (HttpURLConnection) url.openConnection(); Scanner scan = new Scanner(conn.getInputStream()); if(scan.hasNext()) scan.nextLine(); scan.close(); ClientTestThread.addToTimer((GregorianCalendar.getInstance().getTimeInMillis() - millisec)); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static void addToTimer(long time) { synchronized (timer) { timer += time; } } public static void clearTimer() { synchronized (timer) { timer = 0L; } } public static Long getTimer() { return timer; } },客户端界面以及生成多线程、绘制响应时间曲线代码如下(使用SWT,如果要运行,需要使用eclipse.org的SWT包):package com.hw.client; import org.eclipse.swt.SWT; public class TestClient { protected Shell shell; private Text lowField; private Text highField; private Group lineGroup; private static final int unit = 10; private static final int off = 10; private static final int width = 800; private static final int height = 600; /** * Launch the application. * * @param args */ public static void main(String[] args) { try { TestClient window = new TestClient(); window.open(); } catch (Exception e) { e.printStackTrace(); } } /** * Open the window. */ public void open() { Display display = Display.getDefault(); createContents(); shell.open(); shell.layout(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } } /** * Create contents of the window. */ protected void createContents() { shell = new Shell(); shell.setSize(1024, 768); shell.setText("/u8D1F/u8F7D/u5E73/u8861/u6D4B/u8BD5/u5BA2/u6237/u7AEF"); shell.setLayout(null); Composite composite = new Composite(shell, SWT.NONE); composite.setBounds(10, 10, 655, 97); Group group = new Group(composite, SWT.NONE); group.setBounds(10, 23, 422, 64); group.setText("/u6D4B/u8BD5/u7684/u7EBF/u7A0B/u6570/u91CF/u533A/u95F4"); group.setLayout(null); Label label = new Label(group, SWT.NONE); label.setBounds(10, 27, 60, 12); label.setText("/u4E0B/u9650"); lowField = new Text(group, SWT.BORDER); lowField.setBounds(76, 24, 106, 18); Label label_1 = new Label(group, SWT.NONE); label_1.setBounds(198, 27, 60, 12); label_1.setText("/u4E0A/u9650"); highField = new Text(group, SWT.BORDER); highField.setBounds(265, 24, 106, 18); Button button = new Button(composite, SWT.NONE); button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent arg0) { GC gc = new GC(lineGroup); gc.setForeground(new Color(Display.getCurrent(), 255, 0, 0)); gc.drawLine(off, height - off, width + off, height - off); gc.drawLine(off, height - off, off, off); for(int i = 1; i < width / unit; ++ i) { if( i % 5 == 0) gc.drawString(String.valueOf(i), i * unit + off - 2, height - off + 3); } gc.drawString("线程数", width + off, height - off - 10); gc.setBackground(new Color(Display.getCurrent(), 255, 0, 0)); for(int i = 1; i < width / unit; ++ i) { gc.fillRectangle(i * unit + off, height - off - 3, 2, 3); } for(int i = 1; i < height / unit; ++ i) { gc.fillRectangle(off, height - off - i * unit, 3, 2); } int low = Integer.parseInt(lowField.getText()); int high = Integer.parseInt(highField.getText()); int lastX = off, lastY = height - off; int x, y; for (int i = low; i <= high; ++i) { Thread[] multiThread = new ClientTestThread[i]; for (Thread t : multiThread) { t = new ClientTestThread(); t.start(); } boolean allFinished = false; while (!allFinished) { allFinished = true; for (Thread t : multiThread) { if (t != null && t.isAlive()) { allFinished = false; Thread.yield(); break; } } } x = i * unit + off; y = height - off - ClientTestThread.getTimer().intValue() / i / 3; if(y < 0) y = 0; gc.drawLine(lastX, lastY, x, y); System.out.println(x + "," + y); lastX = x; lastY =y; //gc.fillOval(i, 600 - ClientTestThread.getTimer().intValue(), 3,3); // gc.drawPoint(i, ClientTestThread.getTimer().intValue()); ClientTestThread.clearTimer(); } } }); button.addMouseListener(new MouseAdapter() { @Override public void mouseDown(MouseEvent arg0) { } }); button.setBounds(476, 53, 72, 22); button.setText("/u6D4B/u8BD5"); lineGroup = new Group(shell, SWT.NONE); lineGroup.setText("/u8D1F/u8F7D/u66F2/u7EBF"); lineGroup.setBounds(10, 113, 996, 618); } } ,运行后,三个tomcat的控制台都会输出结果,说明请求被分发给三个tomcat,运行1-75个线程的响应时间曲线如下:使用Apache+Tomcat构建应用服务器集群