本文主要关注用哪些技术解决了哪些问题,整理思路
系统架构图
整个项目构建于 Spring Boot 之上,Spring MVC 处理请求,MyBatis 访问数据库,Spring Security 管理用户的权限
Spring MVC 拦截器使用场景
拦截用户请求,在调用具体方法之前从cookie中获取凭证,构建用户认证的结果,存入存入SecurityContext,以便于Security进行授权,并存入 ThreadLocal,请求完成后对ThreadLocal 和 SecurityContext 中的对象进行清理。
- 在请求开始时查询登录用户
- 在本次请求中持有用户数据
- 在模板视图上显示用户数据
- 在请求结束时清理用户数据
拦截用户请求,在调用具体方法之前获取用户 IP 和 id 实现数据统计,如统计 UV 与 DAU。
Redis 使用场景
二级缓存:Redis 缓存帖子列表数据,当本地缓存查不到时就查询 Redis。
对性能要求高模块,需要高频访问的模块:
- 关注功能使用了 Redis 的 ZSet 数据结构实现关注列表和被关注列表,将当前时间做为权重,关注时将数据存入 Redis 中,取关时将 Redis 中的数据删除。
- 点赞功能使用了 Redis 的 Set 和 String 数据结构分别存储实体的赞和用户的赞,实现实时获取点赞状态和数量。
Redis 中的事务是细粒度的,当需要进行增加或删除操作时将事务打开,完成后提交事务。
统计网站 UV 使用了 Redis 的高级数据类型 HyperLogLog,其中日期做 key、IP 做 value 将指定的 IP 计入UV,实现了统计指定日期范围内的 UV。
统计网站 DAU 使用了 Redis 的高级数据类型 Bitmap,其中日期做 key、userId 做 value 将指定的 用户 计入UV,通过 OR 运算实现了统计指定日期范围内的 DAU。
使用 Redis 存储了用户的登录凭证,解决了分布式环境下的 Session 的共享。
Kafka 使用场景
当用户进行评论、点赞、关注,触发相应事件,发送到指定主题,事件消费者监听主题,消费消息发送站内通知。
当用户进行发帖和删帖时,发送异步消息,事件消费者监听到消息后,处理事件,包括检验格式、记录日志、对 Elasticsearch 数据库中的数据进行增删。
当用户进行分享时,发送异步消息,将上传文件与分享功能解耦,实现异步生成长图。
Elasticsearch 分布式搜索引擎使用场景
对帖子全文搜索 使用 Elasticsearch,对帖子进行了保存,搜索时根据关键字对帖子的标题和内容检索,并设置了排序规则和高亮显示匹配到的关键词。
多线程与定时任务使用场景
ThreadLocal实现线程隔离
为什么用 ThreadLocal,每个浏览器访问服务器时,服务器会创建独立的线程进行处理请求,服务器处于多线程的环境,存储用户信息时需要考虑多线程的情况,为了防止并发时产生冲突,将用户信息存入 ThreadLocal 实现线程隔离。
Quartz 分布式定时任务框架
使用 Quartz 做定时任务的原因,防止在分布式环境下,不同的服务器做同样的任务产生冲突,而 Quartz 是分布式定时任务框架,数据存储在数据库中,可以实现分布式环境下的定时任务。
业务中使用 Quartz 定时刷新帖子分数,用户后续热度排名。
本地定时任务 Spring 定时任务
业务中向云服务器上传图片采用了 ThreadPoolTaskScheduler,启用定时器,监视该图片,一旦生成了,则上传至云服务器。
Spring AOP 和 ControllerAdvice 使用场景
ControllerAdvice 实现全局异常处理。
Spring AOP 实现统一记录日志。
Spring 事务使用场景
对帖子评论时,需要同步更新帖子的评论数据量,设计到两个实体:评论和帖子,修改了两次数据库,先添加评论,后更新帖子的评论数量。
隔离级别选用 *READ_COMMITTED *解决第二类丢失更新、不可重复读以及幻读。
事务传播行为采用 REQUIRED 支持当前事务(外部事务),如果不存在则创建新事务。
数据结构与算法
利用 Trie 实现了发帖、评论时对敏感词过滤。
利用 * 的问答排名算法,计算帖子分数,实现帖子热门排名。
项目部署图