/**
* Copyright 2016, Xiaomi.
* All rights reserved.
* Author: xiajun@xiaomi.com
*/
package com.xiaomi.infra.galaxy.lcs.log.core.appender;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import com.xiaomi.infra.galaxy.lcs.log.core.ILogger;
import com.xiaomi.infra.galaxy.lcs.log.core.LoggerConstants;
import com.xiaomi.infra.galaxy.talos.client.Utils;
import com.xiaomi.infra.galaxy.talos.client.serialization.MessageSerialization;
import com.xiaomi.infra.galaxy.talos.client.serialization.MessageSerializationFactory;
import com.xiaomi.infra.galaxy.talos.producer.BufferedMessageCount;
import com.xiaomi.infra.galaxy.talos.thrift.Message;
abstract public class LCSAppender {
protected ILogger logger;
protected String topicName;
protected long maxBufferMessageBytes;
protected long maxBufferMessageNumber;
protected boolean blockWhenBufferFull;
protected long flushMessageBytes;
protected long flushMessageNumber;
protected long flushIntervalMillis;
protected long periodCheckIntervalMillis;
private Queue<Message> messageQueue;
private List<Message> failedMessageList;
private BufferedMessageCount bufferedMessageCount;
private long lastFlushMessageTimestamp;
private long lastPeriodCheckTimestamp;
private final Object addMessageLock;
private final Object flushMessageLock;
private AtomicBoolean running;
private ScheduledExecutorService scheduledExecutorService;
public LCSAppender(ILogger logger) {
this.logger = logger;
maxBufferMessageBytes = LoggerConstants.DEFAULT_MAX_BUFFER_MESSAGE_BYTES;
maxBufferMessageNumber = LoggerConstants.DEFAULT_MAX_BUFFER_MESSAGE_NUMBER;
blockWhenBufferFull = LoggerConstants.DEFAULE_BLOCK_WHEN_BUFFER_FULL;
flushMessageBytes = LoggerConstants.DEFAULT_FLUSH_MESSAGE_BYTES;
flushMessageNumber = LoggerConstants.DEFAULT_FLUSH_MESSAGE_NUMBER;
flushIntervalMillis = LoggerConstants.DEFAULT_FLUSH_MESSAGE_INTERVAL_MILLIS;
periodCheckIntervalMillis = LoggerConstants.DEFAULT_PERIOD_PERIOD_INTERVAL_MILLIS;
messageQueue = new ConcurrentLinkedQueue<Message>();
failedMessageList = null;
lastFlushMessageTimestamp = System.currentTimeMillis();
lastPeriodCheckTimestamp = System.currentTimeMillis();
addMessageLock = new Object();
flushMessageLock = new Object();
running = new AtomicBoolean(true);
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
}
public String getTopicName() {
return topicName;
}
public void setTopicName(String topicName) {
this.topicName = topicName;
}
public long getMaxBufferMessageBytes() {
return maxBufferMessageBytes;
}
public void setMaxBufferMessageBytes(long maxBufferMessageBytes) {
this.maxBufferMessageBytes = maxBufferMessageBytes;
}
public boolean isBlockWhenBufferFull() {
return blockWhenBufferFull;
}
public void setBlockWhenBufferFull(boolean blockWhenBufferFull) {
this.blockWhenBufferFull = blockWhenBufferFull;
}
public long getMaxBufferMessageNumber() {
return maxBufferMessageNumber;
}
public void setMaxBufferMessageNumber(long maxBufferMessageNumber) {
this.maxBufferMessageNumber = maxBufferMessageNumber;
}
public long getFlushMessageBytes() {
return flushMessageBytes;
}
public void setFlushMessageBytes(long flushMessageBytes) {
this.flushMessageBytes = flushMessageBytes;
}
public long getFlushMessageNumber() {
return flushMessageNumber;
}
public void setFlushMessageNumber(long flushMessageNumber) {
this.flushMessageNumber = flushMessageNumber;
}
public long getFlushIntervalMillis() {
return flushIntervalMillis;
}
public void setFlushIntervalMillis(long flushIntervalMillis) {
this.flushIntervalMillis = flushIntervalMillis;
}
public long getPeriodCheckIntervalMillis() {
return periodCheckIntervalMillis;
}
public void setPeriodCheckIntervalMillis(long periodCheckIntervalMillis) {
this.periodCheckIntervalMillis = periodCheckIntervalMillis;
}
public void start() {
if (topicName == null || topicName.isEmpty()) {
throw new RuntimeException("please set topicName");
}
bufferedMessageCount = new BufferedMessageCount(maxBufferMessageNumber, maxBufferMessageBytes);
doStart();
scheduledExecutorService.submit(new Runnable() {
@Override
public void run() {
flushMessage();
}
});
}
public void close() {
running.set(false);
scheduledExecutorService.shutdown();
synchronized (flushMessageLock) {
flushMessageLock.notify();
}
try {
scheduledExecutorService.awaitTermination(120, TimeUnit.MINUTES);
} catch (InterruptedException e) {
}
doClose();
}
public void addMessage(Message message) {
if (!running.get()) {
logger.error("Appender is closed, we will dorp one message for topic: " + topicName);
return;
}
try {
Utils.checkMessageValidity(message);
} catch (Exception e) {
logger.error("Topic: " + topicName + " message invalid, we will drop it", e);
return;
}
while (bufferedMessageCount.isFull()) {
if (blockWhenBufferFull) {
try {
addMessageLock.wait();
} catch (Exception e) {
}
} else {
// add drop message counter
logger.error("Topic: " + topicName + " drop one message because of " +
"buffer full");
return;
}
}
doAddMessage(message);
}
private void doAddMessage(Message message) {
messageQueue.add(message);
bufferedMessageCount.increase(1L, MessageSerialization.getMessageSize(
message, MessageSerializationFactory.getDefaultMessageVersion()));
synchronized (flushMessageLock) {
flushMessageLock.notify();
}
}
private void flushMessage() {
while (true) {
try {
if (!shouldFlushMessage()) {
synchronized (flushMessageLock) {
try {
flushMessageLock.wait(getFlushMessageWaitTime());
} catch (Exception e) {
}
}
}
periodCheck();
if (shouldFlushMessage()) {
List<Message> messageList = getFlushMessage();
if (messageList.isEmpty()) {
break;
}
logger.debug("Topic: " + topicName + " flush [" + messageList.size() +
"] messages");
try {
doFlushMessage(messageList);
} catch (Exception e) {
logger.error("Topic: " + topicName + " flush [" + messageList.size() +
"] messages failed", e);
onFlushMessageFailed(messageList);
}
}
} catch (Exception e) {
logger.error("Topic: " + topicName + " flush message with unexcepted error", e);
}
}
}
private boolean shouldFlushMessage() {
if (!running.get()) {
return true;
}
if (failedMessageList != null) {
return true;
}
if (bufferedMessageCount.getBufferedMsgBytes() >= flushMessageBytes) {
return true;
}
if (bufferedMessageCount.getBufferedMsgNumber() >= flushMessageNumber) {
return true;
}
if (!bufferedMessageCount.isEmpty() &&
System.currentTimeMillis() - lastFlushMessageTimestamp >= flushIntervalMillis) {
return true;
}
return false;
}
private List<Message> getFlushMessage() {
if (failedMessageList != null) {
List<Message> messageList = failedMessageList;
failedMessageList = null;
return messageList;
}
long messageNumber = 0;
long messageBytes = 0;
List<Message> messageList = new ArrayList<Message>();
while (!messageQueue.isEmpty()) {
Message message = messageQueue.peek();
messageNumber ++;
messageBytes += message.getMessage().length;
if (messageNumber > flushMessageNumber ||
((!messageList.isEmpty()) && messageBytes > flushMessageBytes)) {
break;
}
message = messageQueue.poll();
bufferedMessageCount.descrease(1, MessageSerialization.getMessageSize(
message, MessageSerializationFactory.getDefaultMessageVersion()));
messageList.add(message);
}
lastFlushMessageTimestamp = System.currentTimeMillis();
synchronized (addMessageLock) {
addMessageLock.notifyAll();
}
return messageList;
}
private void onFlushMessageFailed(List<Message> messageList) {
failedMessageList = messageList;
}
private void periodCheck() {
if (System.currentTimeMillis() - lastPeriodCheckTimestamp >
periodCheckIntervalMillis) {
lastPeriodCheckTimestamp = System.currentTimeMillis();
doPeriodCheck();
}
}
private long getFlushMessageWaitTime() {
long waitTime = lastPeriodCheckTimestamp +
periodCheckIntervalMillis - System.currentTimeMillis();
waitTime = waitTime > 0 ? waitTime : 1;
if (messageQueue.size() != 0) {
long anotherWaitTime = lastFlushMessageTimestamp +
flushIntervalMillis - System.currentTimeMillis();
waitTime = anotherWaitTime > 0 && anotherWaitTime < waitTime ?
anotherWaitTime : waitTime;
}
return waitTime;
}
abstract protected void doStart();
abstract protected void doFlushMessage(List<Message> messageList) throws Exception;
abstract protected void doPeriodCheck();
abstract protected void doClose();
}