当前位置:网站首页>用redis做用户访问数据统计HyperLogLog及Bitmap高级数据类型
用redis做用户访问数据统计HyperLogLog及Bitmap高级数据类型
2022-06-26 17:05:00 【允许部分艺术家先富起来1】
文章目录
使用redis中的HyperLogLog来做活跃量统计
方法步骤:
- 因为要用的Redis,所以要确定Key的命名。
- 编写用户统计的 增 查(当天or间隔)方法
- 使用拦截器来 调 增方法(我们的页面被访问后,就需要对用户记录,进而改变Redis中的统计数据)
- 写Controller 实现 查 方法的调用
UV:页面访问量
DAU:页面活跃用户
两者区别:UV用 ip来记录,DAU用User来记录。即用户就算没登录页面,但打开了页面,访问量+1。只有用户登录了,代表活跃。
HyperLogLog做日访问量UV统计
HyperLogLog简介
基数
A{1 2 3 4 5}
B{4 5 6 7 8}
基数:8(统计去除重复元素的个数)
优点
占用内存是固定的,只需要占用12kb的内存!但是有0.81%的错误率,用于统计网页访问人数是可以接受度的。
确定Key
public class RedisKeyUtil {
private static final String PREFIX_UV = "uv";
private static final String PREFIX_DAU = "dau";
private static final String SPLIT = ":";
// 单日UV uv:20200812 代表2020年8月12日
public static String getUVKey(String date) {
return PREFIX_UV + SPLIT + date;
}
// 区间UV uv:20201001:20201010 代表2020年10月1日至2020年10月10日
public static String getUVKey(String startDate, String endDate) {
return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
}
}
编写Service 用户访问量的增 查
@Service
public class DataService {
@Autowired
private RedisTemplate redisTemplate;
private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
// 将指定的IP计入UV
public void recordUV(String ip) {
String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));
redisTemplate.opsForHyperLogLog().add(redisKey, ip);
}
// 统计指定日期范围内的UV
public long calculateUV(Date start, Date end) {
if (start == null || end == null) {
throw new IllegalArgumentException("参数不能为空!");
}
// 整理该日期范围内的key
List<String> keyList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
while (!calendar.getTime().after(end)) {
String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime()));
keyList.add(key);
calendar.add(Calendar.DATE, 1);
}
// 合并这些数据
String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));
redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());
// 返回统计的结果
return redisTemplate.opsForHyperLogLog().size(redisKey);
}
}
Bitmap做日活跃用户DAU统计
Bitmap简介
位存储
统计用户信息是否活跃,是否经常登录,是否打卡,两种状态的。Bitmap位图都是操作二进制来进行记录,只有0和1两个状态
- 查看用户一个星期的打卡情况
语法:setbit key offset value 且offset 和value必须为数字,value必须为数字0或1
#记录attendence中周日(0代表周日)未出勤(0代表未出勤)
127.0.0.1:6379> setbit attendence 0 0
(integer) 0
#记录attendence中周日(1代表周一)出勤(1代表未出勤)
127.0.0.1:6379> setbit attendence 1 1
(integer) 0
#记录attendence中周日(2代表周二)出勤(1代表未出勤)
127.0.0.1:6379> setbit attendence 2 1
(integer) 0
#记录attendence中周日(3代表周三)出勤(1代表未出勤)
127.0.0.1:6379> setbit attendence 3 1
(integer) 0
127.0.0.1:6379> setbit attendence 4 1
(integer) 0
127.0.0.1:6379> setbit attendence 5 1
(integer) 0
#记录attendence中周六(6代表周六)未出勤(0代表未出勤)
127.0.0.1:6379> setbit attendence 6 0
(integer) 0
127.0.0.1:6379>
- 查看某天是否打卡
127.0.0.1:6379> getbit attendence 0#查看周日是否出勤,未出勤则返回0
(integer) 0
127.0.0.1:6379> getbit attendence 3 #查看周四是否出勤,出勤则返回1
(integer) 1
127.0.0.1:6379>
- 统计操作
127.0.0.1:6379> bitcount attendence #统计出勤天数
(integer) 5
确定Key
// 单日活跃用户
public static String getDAUKey(String date) {
return PREFIX_DAU + SPLIT + date;
}
// 区间活跃用户
public static String getDAUKey(String startDate, String endDate) {
return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
}
编写Service 用户访问量的增 查
setBit(key,userid,ture) = 在key上的第userid那一位上置为1
代表:第key天,user为的登录过
查询月活跃用户,对30天的value做 或运算,因为同一个用户登录10天 其月活跃用户也计算为1个。
// 将指定用户计入DAU
public void recordDAU(int userId) {
String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));
redisTemplate.opsForValue().setBit(redisKey, userId, true);
}
// 统计指定日期范围内的DAU
public long calculateDAU(Date start, Date end) {
if (start == null || end == null) {
throw new IllegalArgumentException("参数不能为空!");
}
// 整理该日期范围内的key
List<byte[]> keyList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
while (!calendar.getTime().after(end)) {
String key = RedisKeyUtil.getDAUKey(df.format(calendar.getTime()));
keyList.add(key.getBytes());
calendar.add(Calendar.DATE, 1);
}
// 进行OR运算
return (long) redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));
connection.bitOp(RedisStringCommands.BitOperation.OR,
redisKey.getBytes(), keyList.toArray(new byte[0][0]));
return connection.bitCount(redisKey.getBytes());
}
});
}
编写拦截器
写HostHolder
- 因为单线程处理一个访问流程,所以使用ThreadLocal来 存储用户对象
/** * 持有用户信息,用于代替session对象. */
@Component
public class HostHolder {
private ThreadLocal<User> users = new ThreadLocal<>();
public void setUser(User user) {
users.set(user);
}
public User getUser() {
return users.get();
}
public void clear() {
users.remove();
}
}
写HostHolder拦截器组件
这里主要做以下几点操作:
- preHandle中(进入Controller前),通过cookie获取用户User实体,同时存入HostHolder
- afterCompletion中(完成Controller操作后),hostHolder.clear(),清除ThreadLocal中的User对象。
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从cookie中获取凭证
String ticket = CookieUtil.getValue(request, "ticket");
if (ticket != null) {
// 查询凭证
LoginTicket loginTicket = userService.findLoginTicket(ticket);
// 检查凭证是否有效
if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
// 根据凭证查询用户
User user = userService.findUserById(loginTicket.getUserId());
// 在本次请求中持有用户
hostHolder.setUser(user);
// 构建用户认证的结果,并存入SecurityContext,以便于Security进行授权.
Authentication authentication = new UsernamePasswordAuthenticationToken(
user, user.getPassword(), userService.getAuthorities(user.getId()));
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
User user = hostHolder.getUser();
if (user != null && modelAndView != null) {
modelAndView.addObject("loginUser", user);
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();
SecurityContextHolder.clearContext();
}
}
写数据统计拦截器组件
@Component
public class DataInterceptor implements HandlerInterceptor {
@Autowired
private DataService dataService;
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 统计UV
String ip = request.getRemoteHost();
dataService.recordUV(ip);
// 统计DAU
User user = hostHolder.getUser();
if (user != null) {
dataService.recordDAU(user.getId());
}
return true;
}
}
配置类中注册拦截器
为了使用拦截器,需要在配置类中注册我们写的几个拦截器组件。
注: 注册顺序 = 执行顺序
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginTicketInterceptor loginTicketInterceptor;
@Autowired
private DataInterceptor dataInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginTicketInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
registry.addInterceptor(dataInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}
}
ptorRegistry registry) {
registry.addInterceptor(loginTicketInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
registry.addInterceptor(dataInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}
}
边栏推荐
- 分布式缓存/缓存集群简介
- Microservice architecture practice: business management background and SSO design, SSO client design
- In those years, interview the abused red and black trees
- Discover K8E: minimalist kubernetes distribution
- Concurrent thread safety
- Sandboxed container: container or virtual machine
- Leetcode 1169. 查询无效交易(如果数据量不大,这种题还是得暴力枚举解决)
- Toupper function
- [recommendation system learning] technology stack of recommendation system
- Live broadcast preview | how can programmers improve R & D efficiency? On the evening of June 21, the video number and station B will broadcast live at the same time. See you or leave!
猜你喜欢

Discussion: the next generation of stable coins

Army chat -- registration of Registration Center

Introduction to distributed cache / cache cluster

20:第三章:开发通行证服务:3:在程序中,打通redis服务器;(仅仅是打通redis服务器,不涉及具体的业务开发)

【uniapp】uniapp手机端使用uni.navigateBack失效问题解决

Leetcode HOT100 (22--- bracket generation)

Programmer's essential toolkit, please collect!

In those years, interview the abused red and black trees

When I was in the library, I thought of the yuan sharing mode

Concurrent thread safety
随机推荐
Redis and database data consistency
Calculate a=1, a2=1/1=a1
20:第三章:开发通行证服务:3:在程序中,打通redis服务器;(仅仅是打通redis服务器,不涉及具体的业务开发)
In those years, interview the abused red and black trees
Detailed contract quantification system development scheme and technical description of quantitative contract system development
[matlab project practice] prediction of remaining service life of lithium ion battery based on convolutional neural network and bidirectional long short time (cnn-lstm) fusion
A simple membership card management system based on Scala
经典同步问题
分布式缓存/缓存集群简介
Notes on flowus
玩转Linux,轻松安装配置MySQL
Leetcode 1170. 比较字符串最小字母出现频次(可以,已解决)
[recommendation system learning] recommendation system architecture
Redis 概述整理
[C language] static modifies local variables
Interpretation of cloud native microservice technology trend
Multiply the values of the upper triangular elements of the array by M
并发之线程安全
Quantitative contract system development analysis case - detailed explanation of contract quantitative system development scheme
Here comes the hero League full skin Downloader