点赞作为一个高频率的操作,如果每次操作都读写数据库会增加数据库的压力,所以采用缓存+定时任务来实现。点赞数据是在redis中缓存半小时,同时定时任务是每隔5分钟执行一次,做持久化存储,这里的缓存时间和任务执行时间可根据项目情况而定。
优点
1.降低对数据库的影响
2.提高点赞的效率
缺点
1.如果任务挂了,会丢失点赞数据
2.持久化存储不是实时的
时序图
数据库设计
create table user_like(
id bigint(20) unsigned not null auto_increment comment 'id',
user_id bigint(20) not null default 0 comment '用户id',
liked_id varchar(21) not null default '' comment '被点赞的id',
liked_status int(11) not null default 0 comment '点赞状态,0未点赞,1已点赞',
liked_type int(11) not null default 0 comment '点赞的类型',
liked_time timestamp not null default '0000-00-00 00:00:00.000000' comment '点赞时间',
is_delete tinyint not null default '0' comment '是否逻辑删除',
create_time timestamp not null default CURRENT_TIMESTAMP comment '创建时间',
update_time timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP comment '更新时间',
primary key (id),
unique uniq_user_id_liked_id_type(user_id,liked_id,liked_type),
key idx_liked_id (liked_id),
key idx_create_time (create_time),
key idx_update_time (update_time)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='用户点赞表';
create table user_like_stat(
id bigint(20) unsigned not null auto_increment comment 'id',
liked_id varchar(21) not null default '' comment '被点赞id',
liked_count int(11) not null default 0 comment '点赞总数量',
is_delete tinyint not null default '0' comment '是否逻辑删除',
create_time timestamp not null default CURRENT_TIMESTAMP comment '创建时间',
update_time timestamp not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP comment '更新时间',
primary key (id),
unique uniq_info_num(liked_id),
key idx_create_time (create_time),
key idx_update_time (update_time)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT COMMENT='点赞统计表';
实现步骤
1.设计缓存数据格式
整个点赞模块主要采用缓存来完成,所以要选择合适数据结构,我选择hash数据结构来实现,应为它可以添加、获取、移除单个键值对,并且可以获取所有键值对。主要缓存两种数据,一种是用户的点赞状态,一种是被点赞id的点赞数量。这两种数据分别用两个key存储,这两个key中都是存储的多个键值对。键值对格式如下:
用户的点赞状态key-value------>{"被点赞的id::用户id" :"点赞状态::点赞时间::点赞类型"}
被点赞id的点赞数量key-value------>{"被点赞id" : "点赞数量"}
2.大key拆分
点赞的数据量比较大的情况下,上面的设计会造成单个key存储的value很大,由于redis是单线程运行,如果一次操作的value很大,会对整个redis的响应时间有影响,所以我们这里在将上面的两个key做拆分。固定key的数量,每次存取时都先在本地计算出落在了哪个key上,这个操作就类似于redis分区、分片。有利于降低单次操作的压力,将压力平分到多个key上。
//点赞状态key拆分
newHashKey = hashKey +"_"+ (userId% 5);
hset (newHashKey, field, value) ;
hget(newHashKey, field)
//点赞数量key拆分
newHashKey = hashKey +"_"+ Math.abs((hash*(被点赞id)) % 5);
hset (newHashKey, field, value) ;
hget(newHashKey, field)
3.代码实现
以下值截取了部分代码,提供思路。
1.点赞状态枚举
2.点赞类型枚举
3.用户点赞类
4.点赞接口实现
这里使用策略设计模式来实现,方便以后的扩展,对这个设计模式不了解的请点击
https://juejin.im/post/6844903703653138445
5.逻辑
取消点赞和这个接口相同,只需要替换下点赞状态和redis增量
6.定时任务
定时任务采用Azkaban任务调度系统,每个5分种运行一次任务,把点赞数据从redis缓存中取出做持久化到mysql。
4.改进点
现在的读取都是用的一个key,接下来可以优化为把key做读写分离。写入和读取分别用不同的key,这样做可以减少资源的浪费,要不每次跑定时任务都会把已经持久化并且缓存未失效的数据拿出来做一遍查询。
以上就是点赞的一个实现思路,大家有什么更好的方法或者改进的点,欢迎提出来。