現在想要統計某一網站的累積訪問用戶人數和日均活躍人數(連續多少天訪問該網站的人數),可以通過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






