首页 > 代码库 > 日志系统数据采集客户端的实现--并发编程容器选型
日志系统数据采集客户端的实现--并发编程容器选型
一个集中的日志系统,第三方应用每次写日志,都需要发送一个远程的rpc或者http请求,造成写日志的延时比较大。
改进的做法是:提供一个写日志调用包,第三方应用写日志时,先把日志缓存到一个线程安全的容器里,然后后台线程实时消费容器内的日志,如果有持久化的需求,就可以实时的把日志flush到文件中,然后再用另外一个线程消费文件真正把日志发送到日志服务器。
所以我们需要一个线程安全的容器,以下是常见的并发编程容器。
CopyOnWriteArrayList是线程安全的, 且处理读操作不需要进行同步和加锁. 所以读操作具有很好的并发性。
CopyOnWriteArrayList的写操作是代价很大的, 所以CopyOnWriteArrayList只适用于读操作频率远远大于写操作频率的场景。
ConcurrentHashMap在read时几乎不用加锁, 而write时使用的是细粒度的分段锁, ConcurrentHashMap甚至可以做到并发write。
ConcurrentLinkedQueue是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。ConcurrentLinkedQueue的API 原来.size() 是要遍历一遍集合的,所以尽量要避免用size而改用isEmpty()。
LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
针对引言提出的场景需求,就可以用LinkedBlockingQueue,因为阻塞的调用,只有当有日志的时候才会触发消费,这样不需要while定时检测,可以支持实时消费。
实时消费日志可以写成线程,如下:
while(true) { StringBuilder content = new StringBuilder(); //如果容器里没有数据,会一直阻塞 take和poll的配合还是值得一提的 String log = logQueue.take(); int count = 0; //凑齐一定数量log,就持久化log while(log != null) { content.append(log+"\r\n"); count++; if(count >= 100) break; //这时候不用take,而是用poll方法,因为poll有超时机制。因为有超时机制,所以不会因为凑不齐设定数量的log永久耽误之前的log持久化 log = logQueue.poll(100,TimeUnit.MILLISECONDS); } appendToFile(content.toString()); }