月度归档:2014年11月

Tomcat源码分析之—组件启动实现分析

Tomcat由多个组件组成,那么Tomcat是怎么对他们的生命周期进行管理的么,这里将从Tomcat源码去分析其生命周期的实现;

Bootstrape类为Tomcat的入口,所有的组件够通过实现Lifecycle接口来管理生命周期,Tomcat启动的时候只需调用Server容器的start(),然后父容器依序启动他所包含的子容器,关闭也是如此。

通过阅读源码可知一个Server里包含一个或多个Service,一个Service里包含一个Container,一个或多个ConnectorContainer又包含了EngineHostContextWrapper四个容器;


Tomcat的组件启动顺序为: 

StandardServer.start()——》StandardServer.startInternal() ——》StandardService().start()——StandardService.startInternal() ——>》StandardEngine().start() ——》StandardEngine.startInternal()—》StandardEngine中启动其他组件,组件关闭也是如此;

现在我们通过Demo简单模拟Tomcat的启动

类图

                      模拟Demo UML类图

 

时序图

               模拟Demo时序图

 

主要代码段如下:

Catalina类:

package co.solinx.Pattern.Observer;

/**
 * Created by LX on 2014/11/26.
 */
public class Catalina {

    public static void main(String[] args) {

        //Tomcat为多个组件组成的server为最外围最大的一个
        StandardServer server = new StandardServer();
        server.addLifecycleListener(new ContextConfig());    //为server添加监听器
        //添加一个service
        StandardService service = new StandardService();
        server.AddService(service);
        service.addLifecycleListener(new ContextConfig());   //为service添加监听器
        //添加一个engine
        StandardEngine standardEngine = new StandardEngine();
        standardEngine.addLifecycleListener(new EngineConfig());   //为engine添加监听器
        StandardHost standardHost = new StandardHost("localhost");
//        StandardHost testHost = new StandardHost("test");
//        standardHost.addLifecycleListener(new EngineConfig());
        standardEngine.addChild("localhost", standardHost);
//        standardEngine.addChild("test", testHost);
        //往service添加engine容器
        service.setContainer(standardEngine);
        try {
            server.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }

}

StandardServer类

package co.solinx.Pattern.Observer;

/**
 * Created by LX on 2014/11/26.
 */
public class StandardServer extends LifecycleBase implements Context {
    Service services[] = new Service[0];
    @Override
    protected void startInternal() throws LifecycleException {

        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }

        System.out.println("StandardServer start");
    }
    public void AddService(Service service) {
        Service result[] = new Service[services.length + 1];
        System.arraycopy(services, 0, result, 0, services.length);
        result[services.length] = service;
        services = result;
    }    
} 

StandardService类:

package co.solinx.Pattern.Observer;

/**
 * Created by LX on 2014/11/26.
 */
public class StandardService extends LifecycleBase implements Service, Context {

    protected ContainerBase container = null;

    @Override
    protected void startInternal() throws LifecycleException {

        container.start();
        System.out.println("StandardService start");
    }

    public void setContainer(ContainerBase container) {
        this.container = container;
    }

} 

StandardEngine类:

package co.solinx.Pattern.Observer;

/**
 * Created by LX on 2014/11/26.
 */
public class StandardEngine extends ContainerBase {
    @Override
    protected void startInternal() throws LifecycleException {
        super.startInternal();
        System.out.println("StandardEngine start");
    }

    protected void addChild(String key, Container container) {
        super.addChild(key, container);
    }
} 

LifecycleSupport类:

package co.solinx.Pattern.Observer;

/**
 * Created by LX on 2014/11/26.
 * 代理了具体监听者
 */
public class LifecycleSupport {
    public LifecycleSupport(Lifecycle lifecycle) {
        super();
        this.lifecycle = lifecycle;
    }
    private Lifecycle lifecycle = null;
    private LifecycleListener listeners[] = new LifecycleListener[0];
    private final Object listenersLock = new Object(); // Lock object for changes to listeners
    public void addLifecycleListener(LifecycleListener listener) {
        synchronized (listenersLock) {
            LifecycleListener results[] =
                    new LifecycleListener[listeners.length + 1];
            for (int i = 0; i < listeners.length; i++)
                results[i] = listeners[i];
            results[listeners.length] = listener;
            listeners = results;
        }
    }
    public LifecycleListener[] findLifecycleListeners() {
        return listeners;
    }
    public void fireLifecycleEvent(String type, Object data) {
        LifecycleEvent event = new LifecycleEvent(lifecycle, type, data);
        LifecycleListener interested[] = listeners;
        for (int i = 0; i < interested.length; i++)
            interested[i].lifecycleEvent(event);
    }
    public void removeLifecycleListener(LifecycleListener listener) {
        synchronized (listenersLock) {
            int n = -1;
            for (int i = 0; i < listeners.length; i++) {
                if (listeners[i] == listener) {
                    n = i;
                    break;
                }
            }
            if (n < 0)
                return;
            LifecycleListener results[] =
                    new LifecycleListener[listeners.length - 1];
            int j = 0;
            for (int i = 0; i < listeners.length; i++) {
                if (i != n)
                    results[j++] = listeners[i];
            }
            listeners = results;
        }
    }
} 


模拟程序运行结果:

result

查询数据过多页面反应慢引入缓存解决方案(Redis、H2)

问题:原系统查询接口不支持分页也不可能加入分页支持,导致Ajax查询数据过多,返回数据达到2W多条记录时响应已经极慢,查询功能不要求数据实时性,页面反应速度极慢、体验不好;经排查是由于数据量过大导致写回页面的时候慢,实现是直接Servlet输出流写到页面上(output.write(buffer, 0, b));
需求变更:加快页面相应速度,页面要有分页功能,可以改变原接口
在这种情况下有两种解决方案:
                      1、前端js分页
                      2、加入第三方缓存部件(内存数据库等)
两种解决方案的缺点:
    第一种方案的问题,虽然解决了分页问题但由于查询的数据量比较大,查询返回页面的数据达几万条有几兆甚至十多兆数据页面反应还是过慢,如果在前端使用js进行分页那查询反应过慢的问题还是没法决解,页面负担过重体验还是差;
    第二种方案问题,相对来说会比第一种复杂点,编码量会增多,如是处理实时查询较为复杂但也不是不可以,要考虑清理缓存数据已保证数据的有效性。
      经过权衡利弊,决定抛弃第一种方案,决定引入内存数据库已解决问题。
内存数据库现在也有不少种类选择如:redis、H2、HSQLDB等等,在这三个内存数据中redis和其他两个还是不一样的,redis是C语言实现的轻量的基于key-value的nosql数据库,而H2与HSQLDB都是用java实现的轻量关系型数据库;所以我想在nosql数据库和关系型数据库中各选一个,实现此功能最终看性能如何决定使用哪个。 H2相比HSQLDB有web操作界面,H2比HSQLDB更新也比较频繁,所以这里选择H2与redis。
    一、redis缓存数据实现分页功能。
         redis实现不太友好的地方,由于redis是C语言实现的所以redis无法实现嵌入Java代码中启动redis服务器(我没找到方案),需要另外启        动redis服务器,为了实现分页还需要再启动redis视乎有点过了;
       思路:使用redis的zset(Sorted-Sets)有序集和数据类型来存储数据,SeeesionID+数据类型当作key,score为接口查询来的顺序,value为拼装好的单条json数据,可以根据Session失效或设置redis key的失效时间来清除数据。实现代码如下:
          /**
             * 处理分页,查出数据填充到redis,然后再从redis中分页查询,每次查询都覆盖redis中数据
  * @param rtList
  * @param callback
  * @param response
  * @throws IOException
  * @linx
  * @Date 2014-11-10
            */
 public void processPager(List<CSTRTData> rtList, String callback,HttpServletRequest request,
   HttpServletResponse response,String top,String skip) throws IOException {
  Jedis jedis = new Jedis(“127.0.0.1”);
  OutputStream output = null;
  Set<String> setValues=new HashSet<String>();
  String dataList = “”;
  String keyName=request.getSession().getId()+”troubleCode”;
  if(top==null){
       top=”1″;
  }
  if(skip==null){
      skip=”0″;
  }
  if (rtList.size() > 0) {
   for (int x = 0; x < rtList.size(); x++) {
        JSONObject temp = JSONObject.fromObject(rtList.get(x));
        long recvTime = temp.getLong(“recvTime”);
        long obdTime = temp.getLong(“obdTime”);
        temp.remove(“recvTime”);
        temp.put(“recvTime”, DateUtil.formatYYYYMMDDHHMMSS(recvTime));
        temp.remove(“obdTime”);
        temp.put(“obdTime”, DateUtil.formatYYYYMMDDHHMMSS(obdTime));
        jedis.zadd(keyName, x, temp.toString());
   }
//开始行号,第一页开始行号为0, 第二页起为上一页skip+页大小
   int start=Integer.valueOf(skip);
//结束行号 top=页大小, 结束行号=skip+top-1
   int end = Integer.valueOf(top)+Integer.valueOf(skip)-1;
   //根据start、end 序号从小到大 从redis中读取出数据
   setValues = jedis.zrange(keyName, start, end);
  //    Set<String> setValues2 = jedis.zrevrange(“hackers”, 0, -1);
  //遍历拼装json
   for (Iterator iter = setValues.iterator(); iter.hasNext();) {
        if (iter.hasNext()) {
           dataList += (String) iter.next() + “,”;
        } else {
           dataList += (String) iter.next();
        }
   }
  }else{
      jedis.del(keyName);
  }
  //拼装页面最终需要的json串
  dataList = callback + “({” + “”d” : {” + “”results” : [” + dataList
    + “],” + “”__count” : “” + rtList.size() + “”” + “}”
    + “})”;
 // 转成输入流
  InputStream input = new ByteArrayInputStream(dataList.getBytes());
  output = response.getOutputStream();
  int b = 0;
  byte[] buffer = new byte[1024];
//通过输出流写到页面
  while ((b = input.read(buffer)) != -1) {
   output.write(buffer, 0, b);
   output.flush();
  }
  if (output != null)
   output.close();
  if (input != null)
      input.close();
 }
  二、H2缓存数据实现分页功能。
       H2有个好处就是可以嵌入在Java代码中启动H2数据库,由于它也是关系型数据库所以使用起来和我们常用的数据库没有什么区别,使用它的内存模式,你不用担心他的性能问题。
       思路:在H2中添加张表,然后把每条数据都转成JSON,然后按顺序存入添加的表中,就可以使用我们经常使用的分页Sql语句进行查询,每天点第一页的时候都调用原接口把数据重新覆盖到H2的表中。可以根据Session失效清除表数据。实现代码如下:
public class H2Opertion {
     public static void createTable(Connection conn) throws Exception {
           Statement stmt = conn.createStatement();
          stmt.executeUpdate(“CREATE TABLE temp(ID INT PRIMARY KEY,NAME VARCHAR(2000));”);
     }
     public static Connection open() throws Exception {
         Class.forName(“org.h2.Driver”);
         Connection conn = DriverManager.getConnection(“jdbc:h2:./h2db/demo”,”sa”, “”);
         return conn;
      }
    public static void insertData(Connection conn, List<String> list)
     throws Exception {
         Statement stmt = conn.createStatement();
         for (int i = 0; i < list.size(); i++) {
              stmt.executeUpdate(“INSERT INTO temp VALUES(” + i + “, ‘”   + list.get(i) + “‘);”);
         }
     }
     public static List query(Connection conn,int start,int end) throws Exception {
  Statement stmt = conn.createStatement();
//分页查询
  ResultSet rs = stmt
    .executeQuery(“select t2.* from (select rownum r,t1.* from temp t1 where rownum<=”+end+”) t2 where t2.r>”+start);
  while (rs.next()) {
       System.out.println(rs.getInt(“ID”) + “,” + rs.getString(“NAME”));
  }
  return null;
     }
 public static void deleteTable(Connection conn) throws Exception {
     Statement stmt = conn.createStatement();
     stmt.executeUpdate(“delete temp”);
 }
 public static void main(String[] args) {
  H2Server server = new H2Server();
  server.startServer();
  Connection conn = null;
  try {
       conn = H2Opertion.open();
   try {
        H2Opertion.createTable(conn); //创建表
   } catch (Exception e) {
         e.printStackTrace();
         H2Opertion.log.error(“表存在”, e);
   }
   H2Opertion.log.info(“删除表数据”);
   H2Opertion.deleteTable(conn); //删除表数据
  //模拟接口查询出来的数据
   List<String> listData = new ArrayList<String>();
   for (int i = 0; i < 30; i++) {
       listData.add(“content” + i);
   }
      //把数据插入到H2的表中
       H2Opertion.insertData(conn, listData);

//分页查询H2表中的数据,10-20条数据

       H2Opertion.query(conn,10,20);
  } catch (Exception e) {
      H2Opertion.log.error(e);
  } finally {
   try {
      conn.close();
   } catch (SQLException e) {
        H2Opertion.log.error(e);
   }
  }
    }
}