轉載自:
https://www.cnblogs.com/LQBlog/p/13365191.html
背景:
某項目做完后,對於系統內部的監控狀況幾乎為零,造成了后期處理問題的難度大幅增加,為了實現項目框架從黑盒到白盒的轉變,針對目前情況做了兩個重大的結構轉變。
1、對日志的格式進行重定義,使日志能夠以機器的形式閱讀,從中讀取信息,並反映到框架圖中
2、針對數據流,從頭到尾進行流程追蹤,同時監控數據流在每個業務點上的統計值。
以下內容,就是針對數據統計而做的。
redis-緩存設計-統計1秒 5秒 1分鍾 訪問數量
以下代碼采用JAVA編寫
實現要點:
1、采用ZSET集合統計值。采用KEY-VALUE會造成多個KEY,且也不易於刪除,ZSET可以使用zrembyscore刪除,即不會產生遺漏,也方便刪除全部內容。
2、用時間戳取整 * 間隔 方式自動生成ZSET的KEY。
3、ZSET的hincrby是原子性操作(類似的INCR/DECR也是原子性操作),能效上加速實現。KEY是監控目標,SCORE是每秒,每分鍾,每小時的訪問量
文章主目錄
記錄統計
獲取統計
數據清理
main方法
記錄統計
主要是通過精度算出時間各個時間片的開始時間 作為hash 相同時間片開始時間是一致的 天統計 時間片都是從日期的早8點開始
/** * 毫秒為單位 統計1秒 5秒 1分鍾 1小時 5小時 1天的統計信息 */ static Integer[] preisions = new Integer[]{1000, 5000, 60000, 300000, 3600000, 18000000, 86400000}; public static void updateCounter(Jedis conn, int productId, int count) { Long currentDate = System.currentTimeMillis(); for (int i = 0; i < preisions.length; i++) { Integer index = preisions[i]; //算出指定時間維度的開始時間片 Long startDate = (Long) (currentDate / index) * index; String hash = "product:" + productId; //指定時間片的精度+1 conn.hincrBy(hash, startDate.toString(), count); //將清理的key加入到一個回收的set 存儲key和精度 conn.zadd("recovery", 0, String.format("%s_%s_%s", hash, index, startDate)); } }
獲取統計
通過精度算出開始時間時間片 然后再hash獲取統計信息
/** * 獲得指定精度的統計數量 * @param conn * @param productId * @param preisions * @return */ public static String getCounter(Jedis conn,int productId,Integer preisions){ Long startDate = (Long) (System.currentTimeMillis() / preisions) * preisions; String hash = "product:" + productId; return conn.hget(hash,startDate.toString()); }
數據清理
隨着時間的增長 hash時間片會越來越多,清理老的時間片
/** * 這里應該使用管道,因為方便打印日志 所以沒有使用管道 * @param conn */ public static void clearCounter(Jedis conn) { new Thread(new Runnable() { @Override public void run() { String recoveryKey = "recovery"; int index = 0; while (true) { if (conn.zcard("recovery") <= 0) { try { Thread.sleep(1000);//沒有可回收的時候直接等待 休息一會兒 continue; } catch (InterruptedException e) { e.printStackTrace(); } } //每次檢查回收50個 Set<String> hashs = conn.zrange(recoveryKey, 0, 50); for (String hash : hashs) { String[] hashArray = hash.split("_"); int preision = Integer.valueOf(hashArray[1]);//取得精度 Long startDate = Long.valueOf(hashArray[2]); String productKey = hashArray[0];//取得數據hash key //開始時間加上精度 如果小於當前時間 表示時間片過了 執行刪除 final Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date(startDate)); calendar.add(Calendar.MILLISECOND, preision); //在時間片之內 if (calendar.getTimeInMillis() > System.currentTimeMillis()) { continue; } else { //執行刪除 Long result = conn.hdel(productKey, startDate.toString()); conn.zrem(recoveryKey,hash); System.out.println(String.format("移除了key:%s,過期數據%s,刪除結果:%s", productKey, startDate.toString(), result)); } } } } }).start(); }
main方法
public static void main(String[] args) throws Exception { Jedis conn = new Jedis("127.0.0.1", 6379); Jedis conn2 = new Jedis("127.0.0.1", 6379); Jedis conn3 = new Jedis("127.0.0.1", 6379); conn.flushDB(); //啟動清理器 clearCounter(conn2); for(int i=0;i<65;i++){ Thread.sleep(1000); updateCounter(conn,1,1); } //獲得 1分鍾的統計數量 System.out.println("一分鍾的統計數量:"+getCounter(conn3,1,60000)); }