7. 集成 Redis 实现访问量
7.1 数据一致性
- 文章阅读【缓存实现访问量】:减少访问数据库的次数,存在一个 BUG,只与点击链接的次数相关,没有与用户的 id 进行绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class PostController extends BaseController {
/**
* 详情detail
*/
public String detail(long id) {
/**
* 一条(post实体类、PostVo实体类)
*/
//一条:selectOnePost(表 文章id = 传 文章id),因为Mapper中select信息中,id过多引起歧义,故采用p.id
PostVo postVo = postService.selectOnePost(new QueryWrapper<Post>().eq("p.id", id));
//req:PostVo实体类 -> CategoryId属性
req.setAttribute("currentCategoryId", postVo.getCategoryId());
//req:PostVo实体类(回调)
req.setAttribute("postVoData", postVo);
/**
* 评论(comment实体类)
*/
//评论:page(分页信息、文章id、用户id、排序)
IPage<CommentVo> results = commentService.selectComments(getPage(), postVo.getId(), null, "created");
//req:CommentVo分页集合
req.setAttribute("commentVoDatas", results);
/**
* 文章阅读【缓存实现访问量】:减少访问数据库的次数,存在一个BUG,只与点击链接的次数相关,没有与用户的id进行绑定
*/
postService.putViewCount(postVo);
return "post/detail";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {
RedisUtil redisUtil;
/**
* 文章阅读【缓存实现访问量】:减少访问数据库的次数,存在一个BUG,只与点击链接的次数相关,没有与用户的id进行绑定
*/
public void putViewCount(PostVo postVo) {
//1.从缓存中获取当前访问量viewCount
String hKey = "day:rank:post:" + postVo.getId();
Integer viewCount = (Integer)redisUtil.hget(hKey, "post-viewCount");
//2.若缓存中存在viewCount,则viewCount+1;若不存在,则postVo.getViewCount()+1
// 注意一点,项目启动前会对【7天内的文章】进行缓存,因此,还会存在【7天前的文章】未进行缓存
if (viewCount != null) {
postVo.setViewCount(viewCount + 1);
} else {
postVo.setViewCount(postVo.getViewCount() + 1);
}
//3.将viewCount同步到缓存中
redisUtil.hset(hKey, "post-viewCount", postVo.getViewCount());
}
}
7.2 定时器定时更新
- 每分钟同步一次(缓存 -> 同步到数据库)
1
2
3
4
5
6
7
8
9//开启定时器
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
System.out.println("http://localhost:8080");
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58/**
* 定时器定时更新
*/
public class ViewCountSyncTask {
RedisUtil redisUtil;
RedisTemplate redisTemplate;
PostService postService;
//每分钟同步一次(缓存 -> 同步到数据库)
public void task() {
//1.查询缓存中"day:rank:post:"的全部key
Set<String> keys = redisTemplate.keys("day:rank:post:" + "*");
//2.遍历全部key,如果某个key中含有“post-viewCount”,则通过ArrayList数组依次将【带有post-viewCount的文章postId】存放
List<String> ids = new ArrayList<>();
for (String key : keys) {
if (redisUtil.hHasKey(key, "post-viewCount")) {
String postId = key.substring("day:rank:post:".length());
ids.add(postId);
}
}
//3.将【全部缓存中的postId】同步到数据库
if (ids.isEmpty()) {
//3.1 如果[没有需要更新阅读量的文章】,则直接返回
return;
} else {
//3.2 如果[存在需要更新阅读量的文章】,则先【根据ids查询全部的文章】,再【从缓存中获取该postId对应的访问量】,然后【给Post重新赋值viewCount】
List<Post> posts = postService.list(new QueryWrapper<Post>().in("id", ids));
for (Post post : posts) {
Integer viewCount = (Integer) redisUtil.hget("day:rank:post:" + post.getId(), "post-viewCount");
post.setViewCount(viewCount);
}
//3.3 同步操作
if (posts.isEmpty()) {
//如果【数据库中刚好删除完全部文章,即不存在文章】,则直接返回
return;
} else {
//同步数据,并删除缓存
if (postService.updateBatchById(posts)) {
for (String id : ids) {
redisUtil.hdel("day:rank:post:" + id, "post-viewCount");
System.out.println(id + "---------------------->同步成功");
}
}
}
}
}
}