做项目时想要实现一个top榜,于是有了一个想法,然后试着实现。之后想去网上看一下有没有什么更好好的做法,但是找不到特别细节的就想把我的想法写下来。
如有不妥之处欢迎指正。
接下来是有关实现一个大数据量的实时的包含年周日的访问量排行榜。
总体想法对时间进行分段统计,存储每个一定时间的区间的访问量,同时对于低频访问的数据直接是由mysql数据库进行存储,而高频数据则是使用redis进行统计,然后定期同步到数据库中。
针对于此产生的问题将会一一解决。
1.存储实时访问量的数据库访问量肯定是要持久化存储到外存上的,针对于此在项目中我采取了这种结构。
| 字段 | 类型 | 说明 |
| id | bitint | |
| context_id | bigint | 被点击内容的id |
| stats_time | varchar | 统计的时间 |
| hit_count | bigint | 点击量 |
关于统计的时间这里采用了字符串类型,实际上用datatime或许更好,只是自己想要在时间上体现的更直观。
这里想要统计的是每12小时的点击量,存储格式为,由年月日组成的八位字符串加0或1表示是哪一个半天。例如”202208091“就是2022年8月9日的从12点到24点的访问量统计。
2.判断高频数据前面提到过对于不同频率的访问量采取的存储方式是不同的。假设在数据量非常大的情况下,redis无法将数据全部存入,虽然redis有内存淘汰策略,但是大量的数据可能会使得redis的内存淘汰过于频繁产生抖动。所以采取对不同数据进行不同的处理有利于内存的高效利用。
那么如何判断高频数据呢?
我采取了这么一种方法。当用户点击时,让redis产生一个key并设置一个比较短过期,当下一次访问时,令这个字段自增1同时并将结果返回。
之后判断这个值是否达到了一个访问阈值,如果是那么就判断该数据是一个高频数据,之后进行下一步的处理。
而这个key的过期时间可以是1s,2s,5s等,这个判定阈值5,10,100或者其他数字。这两个数据的值,需要根据数据量,实际的访问频率等方面综合考虑。
3.高频数据的转储现在已经判断处那些是高频数据,接下来就是将统计频率的任务从数据库转移到redis中去。
因为要统计月的实时榜,也就是最近30天的数据,所以最后进行实时数据统计时,这30天的数据会有一个较高频次的访问。
在数据转储的过程中还没完成时,其他的请求同样判断为高频数据,也要进行转储,但是我们只需要转储一次即可。所以为了应对这种情况,我们应该进行加锁。以下为示例:
class Demo{
static Object lock = new Object;
public void toRedis(Long id){
// ...
synchronized (lock){
if(redisIsNotHave(id)){ // 如果已经转好了就再转了
// 转储的过程
}
}
// ...
}
}
有些类似单例模式,但是每次都要加锁,那怕时转储不同的数据也要等上一个完成,不合适,应该一种数据一个锁,我们用Map记录锁,就改成了以下这样。
public class Demo{
static Map lockMap = new ConcurrentHashMap();
// ...
public void addRead(Long id){
// ...
lockMap.putIfAbsent(id,new ReentrantLock()); //
if(dataNotInRedis(id)){
try {
lockMap.get(id).lock();
if(allUtils.redis().get(key12hour) == null){
// 转储数据
}
} finally {
if(lockMap.get(id)!=null){
try {
lockMap.get(id).unlock();
} finally {
lockMap.remove(id); // 移除锁
}
}
}
// ...
}
}
数据在此已经转储好,所以不用再该数据是否是高频数据,结合之前数据判断,总体流程如下。
当希望增加点击量时
1.判断该条数据是否存储再redis中。是,直接12小时自增一返回,否执行以下步骤。
2.创建或自增redis中的频率记录字段,并且返回数据。
3.判断数据是否达到设定的默认值。否,数据库中数据自增1。是,转储最近30天的数据。
4.其他一是除了上述内容外还需要做的有,每隔一定时间将数据从redis中储存到myaql中,
二是统计每12个小时访问应该在这12小时前一定时间内存储好。比如,在每天的11点,23点提前创建好下一个12小时的访问统计字段。
三是在同步数据时可以判断在redis中的数据,最近30天的数据访问情况,根据访问情况的不同将数据从redis中移除掉。



