当前位置:网站首页>用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");
}
}
边栏推荐
- Web3去中心化存储生态图景
- Incomplete line spacing adjustment of formula display in word
- Leetcode daily [2022 - 02 - 16]
- Teach you to learn dapr - 5 Status management
- 类型多样的石膏PBR多通道贴图素材,速来收藏!
- The texstudio official website cannot be opened
- Detailed contract quantification system development scheme and technical description of quantitative contract system development
- Kubecon China 2021 Alibaba cloud special session is coming! These first day highlights should not be missed
- Classical synchronization problem
- 去中心化NFT交易协议将击败OpenSea
猜你喜欢
Redis OM . Net redis object mapping framework
[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
Leetcode 1169. Query invalid transactions (if the amount of data is small, this problem still needs to be solved by violent enumeration)
Byte interview: two array interview questions, please accept
A simple membership card management system based on Scala
Distributed Architecture Overview
Here comes the hero League full skin Downloader
[ten thousand words summary] starting from the end, analyze in detail how to fill in the college entrance examination volunteers
Alibaba's "high concurrency" tutorial "basic + actual combat + source code + interview + Architecture" is a god class
[recommendation system learning] technology stack of recommendation system
随机推荐
Find all primes less than or equal to Lim, store them in AA array, and return the number of primes
Calculate a=1, a2=1/1=a1
Secrets of gear contract
Leetcode 1169. 查询无效交易(如果数据量不大,这种题还是得暴力枚举解决)
Microservice architecture practice: business management background and SSO design: SSO design
Microservice architecture practice: user login and account switching design, order query design of the mall
【万字总结】以终为始,详细分析高考志愿该怎么填
Teach you to learn dapr - 1 The era of net developers
SQL injection for Web Security (3)
C language --- basic function realization of push box 01
Which low code platform is more friendly to Xiaobai? Here comes the professional evaluation!
Interpretation of cloud native microservice technology trend
Introduction to distributed cache / cache cluster
She said she was tired! So tired! I want to change my career
类型多样的石膏PBR多通道贴图素材,速来收藏!
Teach you to learn dapr - 4 Service invocation
Byte interview: two array interview questions, please accept
Count the number of each vowel letter in the string
Kubernetes essential tools: 2021
Various types of gypsum PBR multi-channel mapping materials, please collect them quickly!