查詢數據過多頁面反應慢引入緩存解決方案(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());
               
   }
   
   int start=Integer.valueOf(skip); //開始行號,第一頁開始行號為0, 第二頁起為上一頁skip+頁大小
   int end = Integer.valueOf(top)+Integer.valueOf(skip)-1; //結束行號 top=頁大小, 結束行號=skip+top-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);
   }
  }
    }
 
}

文章首發地址:Solinx 

http://www.solinx.co/archives/67


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM