当前位置:网站首页>用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");
}
}
边栏推荐
- 【uniapp】uniapp手机端使用uni.navigateBack失效问题解决
- Jouer avec Linux et installer et configurer MySQL facilement
- Over the weekend: 20000 words! Summary of JVM core knowledge, 18 serial cannons as a gift
- 探讨:下一代稳定币
- MySQL index
- Day10 daily 3 questions (1): sum gradually to get the minimum value of positive numbers
- Calculate the sum of the main diagonals of the array
- [recommendation system learning] recommendation system architecture
- Sandboxed container: container or virtual machine
- What is the preferential account opening policy of securities companies now? Is it safe to open an account online now?
猜你喜欢

20: Chapter 3: develop the pass service: 3: get through the redis server in the program; (it only connects with the redis server and does not involve specific business development)

Basic requirements: 7 problems in singleton mode

Introduction to minimal API

Turtle cartography

Microservice architecture practice: business management background and SSO design: SSO design

Daily record 2
![[Error] ld returned 1 exit status](/img/38/ec4645880d4c14e3766fbcabe8ddde.jpg)
[Error] ld returned 1 exit status

一起备战蓝桥杯与CCF-CSP之大模拟炉石传说

Notes on flowus

类型多样的石膏PBR多通道贴图素材,速来收藏!
随机推荐
Count the number of each vowel letter in the string
MySQL index
Wechat app mall, review products, upload commodity pictures, and score Commodity Services
防火 疏散 自救…这场安全生产暨消防培训干货满满!
Romance of the Three Kingdoms: responsibility chain model
对NFT市场前景的7个看法
Can Luo Yonghao succeed in entering the AR field this time?
Day10 daily 3 questions (2): count the number of the largest groups
Getting started with mongodb
Set up your own website (16)
Teach you to learn dapr - 1 The era of net developers
关于FlowUs这一款国民好笔记
Leetcode HOT100 (22--- bracket generation)
分布式缓存/缓存集群简介
Platform management background and merchant menu resource management: merchant registration management design
Play with Linux and easily install and configure MySQL
Deployment and operation of mongodb partitioned cluster
Strength and appearance Coexist -- an exclusive interview with Liu Yu, a member of Apache pulsar PMC
Toupper function
Teach you to learn dapr - 9 Observability