一、导语
整个系统实现ETL流程,提供日志收集以及日志实时搜索功能。
二、技术设计
系统分为客户端和服务端两个模块。客户端会收集日志并且传输日志。服务端对上报日志进行处理,并进行持久化。
客户端
客户端流程图
日志API设计
一、api操作
1.注解 + aop + spring el实现api
使用样例:
@Log(
success = "订单制作成功,取餐号:#{ticket.takeMealNo},门店id:{context.storeId}",
fail = "订单制作失败",取餐号:#{ticket.takeMealNo,门店id:{context.storeId}},
key = "#{ticket.orderNo}"
)
public void doMaking(Ticket ticket){
log.info("开始制作,订单号:[{}]", ticket.orderNo);
......
}
2.编程方式
LogUtil.log("执行成功,取餐号:{},storeId:{}", ticket.getOrderNo(), LogRecordContext.get("storeId"));
3.类图
二、日志上下文
订单执行流程链路都比较长,需要通过上下文保存父级状态
上下文对象:
class LogRecordContext{
private Stack<Map<String,Object>> variableMapStack;
...
public static TransmittableThreadLocal<LogRecordContext> context;
}
上下文数据结构基于栈实现,栈顶为当前执行方法,栈底为入口方法。由于内部调用的方法也会被@Log注解修饰,所以用栈结构实现。
借助TransmittableThreadLocal实现线程间上下文传递。
日志收集异步队列
系统借助Disruptor队列实现日志异步收集。
Disrutpor队列优点:
1.生产者&消费者模型,开发者只需编写具体生产和消费逻辑。
2.支持多种写入拒绝策略,包括Ignore,Blocking等。
3.生产&消费流程可以流水线化,可以对具体流程组件进行编排。
4.消费失败可以重试。
Disruptor队列缺点:
1.框架不支持消费失败重试逻辑。
2.每个消费者每次只能消费一个消息,无法实现单消费者批量消费功能。
批量发送队列
批量上报可以减少网络连接传输次数,以及充分利用带宽。
方式一: LinkedBlockingQueue(共享队列) + LinkedList(批量队列) + guava Queues.drainTo(队列复制)
方式二: ThreadLocal + LinkedList
服务端
服务端流程图
服务端通过kafka队列来实现高吞吐量,通过elastic search提供快速、实时搜索,借助hdfs来实现大规模日志存储。
日志id
msgId格式 => uuid + storedId + 小时(timestamp / 3600 * 1000)
hdfs文件名 => 索引文件: storeId + 小时(timestamp / 3600 * 1000).idx
日志文件: storeId + 小时(timestamp / 3600 * 1000).data
日志Id需要做到自增,并且可以定位出hdfs 文件名。
hdfs文件粒度为<门店,小时>。会根据小时进行数据归档。
hdfs索引文件结构
索引文件需要根据msgId寻找到对应日志文件offset。
实现方式: 块索引 + 二分查找
elasticsearch 文件
文件名: storeId + 小时(timestamp / 3600 * 1000)
es在创建文件时候可以给文件起别名,可以给分区文件起一个别名,查询时候根据别名查询。
es文件粒度为<门店,小时>。会根据小时进行数据归档。