現在想要統計某一網站的累積訪問用戶人數和日均活躍人數(連續多少天訪問該網站的人數),可以通過redis來實現類似功能。
筆者使用的數據結構是Redis中的bitmap,其在大數據量下的空間占用量很小。大概思路就是每一位用戶都是bitmap中的一位,為1就代表其訪問了,為0就代表沒訪問。比如說現在有5位用戶,第1、3位用戶訪問了,而2、4、5沒訪問,如果以索引位置作為其userId的話,那么bitmap存儲的就是10100。
累計用戶的key設置為“totalKey”,其值為到今天為止所有用戶訪問的信息,為1就代表其訪問過該網站,為0就代表該用戶直到今天都沒有訪問過該網站;日均活躍人數的key設置為“activeKey:[當前的日期]”,比如說2019年5月31日的日均活躍人數key為“activeKey:20190531”,2019年5月30日的日均活躍人數key為“activeKey:20190530”,等等。所以如果要統計日均活躍人數的話,只要將這幾個key做交集就可以了(因為只有都為1,相與后結果才為1,如果有一個為0,相與后結果就不是1),然后統計交集結果的1的個數,結果即為統計值。
實現代碼如下所示,在main函數中模擬了用戶訪問的情況。在2019年5月31日有userId為0到14一共15個人訪問該網站,而在2019年5月30日有userId為6到14一共9個人訪問過該網站:
package com.htli.redis; import JAVA.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.Apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.BitOP; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Pipeline; /** * 統計累計和日均活躍用戶人數 * @author Robert Hou * @date 2019年5月31日 */ public class Counter { /** * ip地址 */ private static final String IP_ADDRESS = "192.168.253.129"; /** * 端口號 */ private static final int PORT = 6379; /** * jedis客戶端 */ private Jedis jedis; /** * 累計用戶人數key */ private static final String TOTAL_KEY = "totalKey"; /** * 日均活躍用戶人數key */ private static final String ACTIVE_KEY = "activeKey:"; public Counter() { GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxTotal(50); poolConfig.setMaxIdle(50); poolConfig.setMaxWaitMillis(1000); JedisPool jedisPool = new JedisPool(poolConfig, IP_ADDRESS, PORT); jedis = jedisPool.getResource(); } /** * 更新累計和日均活躍用戶人數 * @param userId 用戶id * @param time 當前日期 */ private void updateUser(long userId, String time) { if (StringUtils.isBlank(time)) { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); time = sdf.format(new Date()); } Pipeline pipeline = jedis.pipelined(); pipeline.setbit(TOTAL_KEY, userId, true); pipeline.setbit(ACTIVE_KEY + time, userId, true); pipeline.syncAndReturnAll(); } /** * 獲取累計用戶人數 * @return 累計用戶人數 */ private Long getTotalUserCount() { Pipeline pipeline = jedis.pipelined(); pipeline.bitcount(TOTAL_KEY); List<Object> totalKeyCountList = pipeline.syncAndReturnAll(); return (Long) totalKeyCountList.get(0); } /** * 獲取指定天數內的日均活躍人數 * @param dayNum 指定天數 * @return */ private Long getActiveUserCount(int dayNum) { if (dayNum < 1) { return (long) 0; } List<String> pastDaysKey = new ArrayList<>(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); StringBuilder sb = new StringBuilder(); for (int i = 0; i < dayNum; i++) { //保存距今dayNum天數的key的集合 sb.Append(ACTIVE_KEY).append(sdf.format(DateUtils.addDays(new Date(), -i))); pastDaysKey.add(sb.toString()); sb.delete(0, sb.length()); } if (pastDaysKey.isEmpty()) { return (long) 0; } String lastDaysKey = "last" + dayNum + "DaysActive"; Pipeline pipeline = jedis.pipelined(); pipeline.bitop(BitOP.AND, lastDaysKey, pastDaysKey.toArray(new String[pastDaysKey.size()])); pipeline.bitcount(lastDaysKey); //設置過期時間為5分鐘 pipeline.expire(lastDaysKey, 300); List<Object> activeKeyCountList = pipeline.syncAndReturnAll(); return (Long) activeKeyCountList.get(1); } public static void main(String[] args) { Counter c = new Counter(); for (int i = 0; i < 15; i++) { c.updateUser(i, "20190531"); } for (int i = 6; i < 15; i++) { c.updateUser(i, "20190530"); } System.out.println("累計用戶數:" + c.getTotalUserCount()); System.out.println("兩天內的活躍人數:" + c.getActiveUserCount(2)); } }
運行結果如下所示:
累計用戶數:15
兩天內的活躍人數:9