支持并发的定时器+数据归档解决查询效率

项目背景

客户的数据量徒增,每天1QWPV,1个月1张表的话大约这一个客户有30亿数据,页面上一些接口的响应变慢。

原因是:后端数据存在了ES中,ES特点是模糊查询很开,但是但是如果用到了一些叫复杂的聚合函数需要进行索引扫描,所以效率了堪忧。

优化方案

  • 每天,用定时器将客户网站数据归档,目前是采用mysql保存。
  • 前端查询采用缓存数据+当天数据的方式呈现给客户,当日的数据很小所以可以满足性能上的要求。
  • 考虑到将来会有很多客户,可能一个定时器执行效率慢,所以定时器要支持水平扩展。

具体执行

  1. 将数据库中所有的网站ID放到redis的zset中,为什么用zset?有序-支持分页,排重;
  2. 利用setNx做锁, 从zset取出一条数据锁住:setNx的key是:prefix:statslock:id:yyyyMMdd, ttl:1天;
    • 因为采用的是RedisTemplate不支持setNx和expire排异常轻快无法解锁,所以锁是按天锁住数据
  3. ES查询昨天的数据,归档
  4. 前端查询ES当钱数据+归档数据返回,测试从10sec请求变为了600ms

示例代码如下:

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
public void doWafDataArchiveByDate(LocalDateTime localDateTime) {
String dateFormater = localDateTime.format(YYYYMMDD);

//分页
Long total = statsRedis.domainSize();
Long pageNo = (total - 1L) / ZSET_PAGE_SIZE + 1;
Long curPage = 1L;

do {
//从redis获取数据
Long startIdx = (curPage - 1L) * ZSET_PAGE_SIZE;
Long endIdx = startIdx + ZSET_PAGE_SIZE - 1L;
Set<String> set = statsRedis.queryRange(startIdx, endIdx);

curPage++;
for (String domainUuid : set) {
try {
//锁1天
if (taskLock.lock(domainUuid, dateFormater)) {
try {
//do logic
} catch (Exception e) {
e.printStackTrace();
}
} else {
}
} finally {
taskLock.unlock(domainUuid,dateFormater);
}
}
} while (curPage <= pageNo);
}

遗留的坑以及方案

待补充