面试题答案
一键面试方案思路
- 数据存储:将日志数据存储在Redis中,为了满足按时间范围和日志级别查询,采用有序集合(Sorted Set)存储日志记录,其中成员为日志详细信息的序列化字符串,分值(score)用于表示日志时间戳。同时,使用哈希表(Hash)来统计不同日志级别的数量,方便快速获取每个级别日志的总数。
- 查询实现:通过EVAL脚本操作有序集合和哈希表来实现复杂查询。对于按时间范围查询,利用有序集合的ZRANGEBYSCORE命令;对于按日志级别查询,从哈希表获取该级别日志总数,同时结合有序集合过滤出该级别日志。
Redis数据结构
- 有序集合(Sorted Set):
- 键:例如
log:all
,用于存储所有日志记录。 - 成员:日志详细信息的序列化字符串,如
{"timestamp": 1690000000, "level": "INFO", "message": "This is an info log"}
。 - 分值(score):日志的时间戳,用于按时间排序。
- 键:例如
- 哈希表(Hash):
- 键:例如
log:level:count
,用于统计不同日志级别的数量。 - 字段:日志级别,如
INFO
、WARN
、ERROR
等。 - 值:对应日志级别的数量。
- 键:例如
EVAL脚本逻辑
- 按时间范围查询:
-- 获取有序集合中指定时间范围的日志
local start_time = ARGV[1]
local end_time = ARGV[2]
local logs = redis.call('ZRANGEBYSCORE', 'log:all', start_time, end_time)
return logs
- 按日志级别查询:
-- 获取指定日志级别的数量
local level = ARGV[1]
local count = redis.call('HGET', 'log:level:count', level)
-- 获取该级别所有日志
local logs = {}
local all_logs = redis.call('ZRANGE', 'log:all', 0, -1)
for _, log in ipairs(all_logs) do
local log_obj = cjson.decode(log)
if log_obj.level == level then
table.insert(logs, log)
end
end
return {count, logs}
- 按时间范围和日志级别查询:
-- 获取指定时间范围和日志级别的日志
local start_time = ARGV[1]
local end_time = ARGV[2]
local level = ARGV[3]
local logs = {}
local range_logs = redis.call('ZRANGEBYSCORE', 'log:all', start_time, end_time)
for _, log in ipairs(range_logs) do
local log_obj = cjson.decode(log)
if log_obj.level == level then
table.insert(logs, log)
end
end
return logs
以上方案通过合理利用Redis数据结构和EVAL脚本,能够满足按时间范围、日志级别等复杂查询需求,并在一定程度上优化性能。