一、背景
在链路追踪中,会不断生成数据,这些数据先缓存在本地,然后异步批量上报,服务端接收上报的数据然后做一些处理、整合,再写入到数据库中。由于需要将数据先缓存到本地,所以就涉及到本地数据的缓存方式
二、业界处理方案-zipkin与Skywalking
1、zipkin
使用本地内存作为队列,不断生成的数据写入到队列中,再启动一个线程消费队列中的数据,将数据发送给kafka,另一端从kafka中消费数据并加以处理。
由于使用本地内存作为队列缓存数据,所以存在数据丢失风险,当数据过多超过内存时,直接丢弃数据。
代码如下:
final class ByteBoundedQueue {
final ReentrantLock lock = new ReentrantLock(false);
final Condition available;
final int maxSize;
final int maxBytes;
final byte[][] elements;
int count;
int sizeInBytes;
int writePos;
int readPos;
ByteBoundedQueue(int maxSize, int maxBytes) {
this.available = this.lock.newCondition();
this.elements = new byte[maxSize][];
this.maxSize = maxSize;
this.maxBytes = maxBytes;
}
boolean offer(byte[] next) {
this.lock.lock();
boolean var2;
try {
if (this.count != this.elements.length) {
if (this.sizeInBytes + next.length > this.maxBytes) {
var2 = false;
return var2;
}
this.elements[this.writePos++] = next;
if (this.writePos == this.elements.length) {
this.writePos = 0;
}
++this.count;
this.sizeInBytes += next.length;
this.available.signal();
var2 = true;
return var2;
}
var2 = false;
} finally {
this.lock.unlock();
}
return var2;
}
int drainTo(ByteBoundedQueue.Consumer consumer, long nanosTimeout) {
try {
this.lock.lockInterruptibly();
int var12;
try {
for(long nanosLeft = nanosTimeout; this.count == 0; nanosLeft = this.available.awaitNanos(nanosLeft)) {
if (nanosLeft <= 0L) {
byte var6 = 0;
return var6;
}
}
var12 = this.doDrain(consumer);
} finally {
this.lock.unlock();
}
return var12;
} catch (InterruptedException var11) {
return 0;
}
}
int clear() {
this.lock.lock();
int var2;
try {
int result = this.count;
this.count = this.sizeInBytes = this.readPos = this.writePos = 0;
Arrays.fill(this.elements, (Object)null);
var2 = result;
} finally {
this.lock.unlock();
}
return var2;
}
int doDrain(ByteBoundedQueue.Consumer consumer) {
int drainedCount = 0;
int drainedSizeInBytes = 0;
while(drainedCount < this.count) {
byte[] next = this.elements[this.readPos];
if (next == null || !consumer.accept(next)) {
break;
}
++drainedCount;
drainedSizeInBytes += next.length;
this.elements[this.readPos] = null;
if (++this.readPos == this.elements.length) {
this.readPos = 0;
}
}
this.count -= drainedCount;
this.sizeInBytes -= drainedSizeInBytes;
return drainedCount;
}
interface Consumer {
boolean accept(byte[] var1);
}
}
2.Skywalking
使用本地内存作为循环队列,不断生成的数据写入到队列中,如果写入的位置还没有被消费,就采取三种不同的策略:
阻塞:等待这个位置的数据被消费,然后再写入
覆盖:将之前未被消费的数据覆盖
跳过:将这个数据丢弃