簡體   English   中英

以大小限制的塊發送數據到數據庫

[英]Sending data to a database in size-limited chunks

我有一個方法,它采用Partition枚舉參數。 通過傳遞不同的partition值,該方法將在同一時間段內由多個后台線程(最多15個)調用。 這里dataHoldersByPartitionPartitionConcurrentLinkedQueue<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,我將從中提取clientKeyBytesprocessBytes 這是我正在做的驗證:

  • 如果clientKeyBytes長度大於255,那么我將跳過它並繼續迭代。
  • 我將繼續增加totalSize變量,它將是clientKeyLengthprocessBytesLength的總和,並且此totalSize長度應始終小於50000字節。
  • 一旦達到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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM