[英]Sending data to a database in size-limited chunks
我有一個方法,它采用Partition
枚舉參數。 通過傳遞不同的partition
值,該方法將在同一時間段內由多個后台線程(最多15個)調用。 這里dataHoldersByPartition
是Partition
和ConcurrentLinkedQueue<DataHolder>
的映射。
private final ImmutableMap<Partition, ConcurrentLinkedQueue<DataHolder>> dataHoldersByPartition;
//... some code to populate entry in `dataHoldersByPartition`
private void validateAndSend(final Partition partition) {
ConcurrentLinkedQueue<DataHolder> dataHolders = dataHoldersByPartition.get(partition);
Map<byte[], byte[]> clientKeyBytesAndProcessBytesHolder = new HashMap<>();
int totalSize = 0;
DataHolder dataHolder;
while ((dataHolder = dataHolders.poll()) != null) {
byte[] clientKeyBytes = dataHolder.getClientKey().getBytes(StandardCharsets.UTF_8);
if (clientKeyBytes.length > 255)
continue;
byte[] processBytes = dataHolder.getProcessBytes();
int clientKeyLength = clientKeyBytes.length;
int processBytesLength = processBytes.length;
int additionalLength = clientKeyLength + processBytesLength;
if (totalSize + additionalLength > 50000) {
Message message = new Message(clientKeyBytesAndProcessBytesHolder, partition);
// here size of `message.serialize()` byte array should always be less than 50k at all cost
sendToDatabase(message.getAddress(), message.serialize());
clientKeyBytesAndProcessBytesHolder = new HashMap<>();
totalSize = 0;
}
clientKeyBytesAndProcessBytesHolder.put(clientKeyBytes, processBytes);
totalSize += additionalLength;
}
// calling again with remaining values only if clientKeyBytesAndProcessBytesHolder is not empty
if(!clientKeyBytesAndProcessBytesHolder.isEmpty()) {
Message message = new Message(partition, clientKeyBytesAndProcessBytesHolder);
// here size of `message.serialize()` byte array should always be less than 50k at all cost
sendToDatabase(message.getAddress(), message.serialize());
}
}
以下是我的Message
類:
public final class Message {
private final byte dataCenter;
private final byte recordVersion;
private final Map<byte[], byte[]> clientKeyBytesAndProcessBytesHolder;
private final long address;
private final long addressFrom;
private final long addressOrigin;
private final byte recordsPartition;
private final byte replicated;
public Message(Map<byte[], byte[]> clientKeyBytesAndProcessBytesHolder, Partition recordPartition) {
this.clientKeyBytesAndProcessBytesHolder = clientKeyBytesAndProcessBytesHolder;
this.recordsPartition = (byte) recordPartition.getPartition();
this.dataCenter = Utils.CURRENT_LOCATION.get().datacenter();
this.recordVersion = 1;
this.replicated = 0;
long packedAddress = new Data().packAddress();
this.address = packedAddress;
this.addressFrom = 0L;
this.addressOrigin = packedAddress;
}
// Output of this method should always be less than 50k always
public byte[] serialize() {
int bufferCapacity = getBufferCapacity(clientKeyBytesAndProcessBytesHolder); // 36 + dataSize + 1 + 1 + keyLength + 8 + 2;
ByteBuffer byteBuffer = ByteBuffer.allocate(bufferCapacity).order(ByteOrder.BIG_ENDIAN);
// header layout
byteBuffer.put(dataCenter).put(recordVersion).putInt(clientKeyBytesAndProcessBytesHolder.size())
.putInt(bufferCapacity).putLong(address).putLong(addressFrom).putLong(addressOrigin)
.put(recordsPartition).put(replicated);
// now the data layout
for (Map.Entry<byte[], byte[]> entry : clientKeyBytesAndProcessBytesHolder.entrySet()) {
byte keyType = 0;
byte[] key = entry.getKey();
byte[] value = entry.getValue();
byte keyLength = (byte) key.length;
short valueLength = (short) value.length;
ByteBuffer dataBuffer = ByteBuffer.wrap(value);
long timestamp = valueLength > 10 ? dataBuffer.getLong(2) : System.currentTimeMillis();
byteBuffer.put(keyType).put(keyLength).put(key).putLong(timestamp).putShort(valueLength)
.put(value);
}
return byteBuffer.array();
}
private int getBufferCapacity(Map<byte[], byte[]> clientKeyBytesAndProcessBytesHolder) {
int size = 36;
for (Entry<byte[], byte[]> entry : clientKeyBytesAndProcessBytesHolder.entrySet()) {
size += 1 + 1 + 8 + 2;
size += entry.getKey().length;
size += entry.getValue().length;
}
return size;
}
// getters and to string method here
}
基本上,我必須確保無論何時調用sendToDatabase
方法, message.serialize()
字節數組的大小應始終小於50k。 我的sendToDatabase
方法發送來自serialize
方法的字節數組。 由於這種情況,我正在進行驗證以及其他一些事情。 在該方法中,我將迭代dataHolders
CLQ,我將從中提取clientKeyBytes
和processBytes
。 這是我正在做的驗證:
clientKeyBytes
長度大於255,那么我將跳過它並繼續迭代。 totalSize
變量,它將是clientKeyLength
和processBytesLength
的總和,並且此totalSize
長度應始終小於50000字節。 clientKeyBytesAndProcessBytesHolder
映射發送到sendToDatabase
方法並清除映射,將totalSize
重置為0並再次開始填充。 dataHolders
變空,那么它將發送它擁有的任何內容。 我相信我當前的代碼中存在一些錯誤,因為根據我的情況,某些記錄可能沒有正確發送或丟棄到某處,我無法弄清楚這一點。 看起來要正確實現這個50k條件我可能必須使用getBufferCapacity
方法在調用sendToDatabase
方法之前正確計算出大小?
我檢查了你的代碼,根據你的邏輯它看起來很好。 如你所說,它將始終存儲小於50K的信息,但它實際上將信息存儲到50K。 要使其小於50K,您必須將if條件更改為if (totalSize + additionalLength >= 50000)
。
如果您的代碼仍未滿足您的要求,即當totalSize + additionalLength
大於50k時存儲信息,我可以建議您很少想到。
由於超過50個線程調用此方法,您需要考慮代碼中的兩個部分進行同步。 一個是全局變量,它是容器dataHoldersByPartition
對象。 如果在此容器對象中發生多個並發和並行搜索,則結果可能不完美。 只需檢查容器類型是否同步。 如果沒有像下面那樣制作這個塊: -
synchronized(this){
ConcurrentLinkedQueue<DataHolder> dataHolders = dataHoldersByPartition.get(partition);
}
現在,我只能提出兩個建議來解決這個問題。 一個是if (totalSize + additionalLength > 50000)
你可以檢查對象clientKeyBytesAndProcessBytesHolder
的大小if(sizeof(clientKeyBytesAndProcessBytesHolder) >= 50000)
(在java中檢查sizeof的適當方法)。 第二個是縮小區域范圍以檢查它是否是多線程的副作用。 所有這些建議都是為了找出確切問題所在的區域,並且只能從最終解決問題。
首先檢查您的方法validateAndSend
是否完全滿足您的要求。 為此,首先同步整個validateAndSend
方法並檢查一切是否正常或仍然具有相同的結果。 如果仍然具有相同的結果,則意味着它不是因為多線程而是您的編碼不符合要求。 如果它的工作正常,則意味着它是多線程的問題。 如果方法同步正在修復您的問題但會降低性能,您只需從中移除同步並集中代碼的每個小塊,這可能會導致問題,並使其同步阻止並刪除,如果仍然無法修復您的問題。 最后你找到實際創建問題的代碼塊,並將其保持為同步以最終修復它。
例如第一次嘗試: -
`private synchronize void validateAndSend`
第二次嘗試:從方法中刪除同步關鍵字並執行以下步驟: -
synchronize(this){
Message message = new Message(clientKeyBytesAndProcessBytesHolder, partition);
sendToDatabase(message.getAddress(), message.serialize());
}
如果您認為我沒有正確理解您,請告訴我。
在您的validateAndSend
我將整個數據放入隊列,並在單獨的線程中進行整個處理。 請考慮命令模型。 這樣所有線程都會將其負載放在隊列中。 消費者線程擁有所有數據,所有信息都已到位,並且可以非常有效地處理它。 唯一復雜的部分是將響應/結果發送回調用線程。 因為在你的情況下,這不是一個問題 - 更好。 這種模式還有一些好處 - 請看netflix / hystrix 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.