以多种方式发送数据,具体取决于您的发送方式

时间:2018-01-11 07:35:03

标签: java oop design-patterns bytebuffer single-responsibility-principle

我想通过将它们打包到一个字节数组中来发送到我们的消息队列中的一堆键和值。我将创建所有键和值的一个字节数组,该数组应始终小于50K,然后发送到我们的消息队列。

数据包类

public final class Packet implements Closeable {
  private static final int MAX_SIZE = 50000;
  private static final int HEADER_SIZE = 36;

  private final byte dataCenter;
  private final byte recordVersion;
  private final long address;
  private final long addressFrom;
  private final long addressOrigin;
  private final byte recordsPartition;
  private final byte replicated;
  private final ByteBuffer itemBuffer = ByteBuffer.allocate(MAX_SIZE);
  private int pendingItems = 0;

  public Packet(final RecordPartition recordPartition) {
    this.recordsPartition = (byte) recordPartition.getPartition();
    this.dataCenter = Utils.LOCATION.get().datacenter();
    this.recordVersion = 1;
    this.replicated = 0;
    final long packedAddress = new Data().packAddress();
    this.address = packedAddress;
    this.addressFrom = 0L;
    this.addressOrigin = packedAddress;
  }

  private void addHeader(final ByteBuffer buffer, final int items) {
    buffer.put(dataCenter).put(recordVersion).putInt(items).putInt(buffer.capacity())
        .putLong(address).putLong(addressFrom).putLong(addressOrigin).put(recordsPartition)
        .put(replicated);
  }

  private void sendData() {
    if (itemBuffer.position() == 0) {
      // no data to be sent
      return;
    }
    final ByteBuffer buffer = ByteBuffer.allocate(MAX_SIZE);
    addHeader(buffer, pendingItems);
    buffer.put(itemBuffer);
    SendRecord.getInstance().sendToQueueAsync(address, buffer.array());
    // SendRecord.getInstance().sendToQueueAsync(address, buffer.array());
    // SendRecord.getInstance().sendToQueueSync(address, buffer.array());
    // SendRecord.getInstance().sendToQueueSync(address, buffer.array(), socket);
    itemBuffer.clear();
    pendingItems = 0;
  }

  public void addAndSendJunked(final byte[] key, final byte[] data) {
    if (key.length > 255) {
      return;
    }
    final byte keyLength = (byte) key.length;
    final byte dataLength = (byte) data.length;

    final int additionalSize = dataLength + keyLength + 1 + 1 + 8 + 2;
    final int newSize = itemBuffer.position() + additionalSize;
    if (newSize >= (MAX_SIZE - HEADER_SIZE)) {
      sendData();
    }
    if (additionalSize > (MAX_SIZE - HEADER_SIZE)) {
      throw new AppConfigurationException("Size of single item exceeds maximum size");
    }

    final ByteBuffer dataBuffer = ByteBuffer.wrap(data);
    final long timestamp = dataLength > 10 ? dataBuffer.getLong(2) : System.currentTimeMillis();
    // data layout
    itemBuffer.put((byte) 0).put(keyLength).put(key).putLong(timestamp).putShort(dataLength)
        .put(data);
    pendingItems++;
  }

  @Override
  public void close() {
    if (pendingItems > 0) {
      sendData();
    }
  }
}

以下是我发送数据的方式。截至目前,我的设计仅允许通过调用上述sendToQueueAsync方法中的sendData()方法异步发送数据。

  private void validateAndSend(final RecordPartition partition) {
    final ConcurrentLinkedQueue<DataHolder> dataHolders = dataHoldersByPartition.get(partition);

    final Packet packet = new Packet(partition);

    DataHolder dataHolder;
    while ((dataHolder = dataHolders.poll()) != null) {
      packet.addAndSendJunked(dataHolder.getClientKey().getBytes(StandardCharsets.UTF_8),
          dataHolder.getProcessBytes());
    }
    packet.close();
  }

现在我需要扩展我的设计,以便我可以以三种不同的方式发送数据。由用户决定他想要发送数据的方式,&#34; sync&#34;或&#34; async&#34;。

  • 我需要通过调用sender.sendToQueueAsync方法异步发送数据。
  • 或者我需要通过调用sender.sendToQueueSync方法同步发送数据。
  • 或者我需要通过调用sender.sendToQueueSync方法在同一个套接字上同步发送数据。在这种情况下,我需要以某种方式传递socket变量,以便sendData知道此变量。

SendRecord类

public class SendRecord {
  private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
  private final Cache<Long, PendingMessage> cache = CacheBuilder.newBuilder().maximumSize(1000000)
      .concurrencyLevel(100).build();

  private static class Holder {
    private static final SendRecord INSTANCE = new SendRecord();
  }

  public static SendRecord getInstance() {
    return Holder.INSTANCE;
  }

  private SendRecord() {
    executorService.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        handleRetry();
      }
    }, 0, 1, TimeUnit.SECONDS);
  }

  private void handleRetry() {
    List<PendingMessage> messages = new ArrayList<>(cache.asMap().values());
    for (PendingMessage message : messages) {
      if (message.hasExpired()) {
        if (message.shouldRetry()) {
          message.markResent();
          doSendAsync(message);
        } else {
          cache.invalidate(message.getAddress());
        }
      }
    }
  }

  // called by multiple threads concurrently
  public boolean sendToQueueAsync(final long address, final byte[] encodedRecords) {
    PendingMessage m = new PendingMessage(address, encodedRecords, true);
    cache.put(address, m);
    return doSendAsync(m);
  }

  // called by above method and also by handleRetry method
  private boolean doSendAsync(final PendingMessage pendingMessage) {
    Optional<SocketHolder> liveSocket = SocketManager.getInstance().getNextSocket();
    ZMsg msg = new ZMsg();
    msg.add(pendingMessage.getEncodedRecords());
    try {
      // this returns instantly
      return msg.send(liveSocket.get().getSocket());
    } finally {
      msg.destroy();
    }
  }

  // called by send method below
  private boolean doSendAsync(final PendingMessage pendingMessage, final Socket socket) {
    ZMsg msg = new ZMsg();
    msg.add(pendingMessage.getEncodedRecords());
    try {
      // this returns instantly
      return msg.send(socket);
    } finally {
      msg.destroy();
    }
  }

  // called by multiple threads to send data synchronously without passing socket
  public boolean sendToQueueSync(final long address, final byte[] encodedRecords) {
    PendingMessage m = new PendingMessage(address, encodedRecords, false);
    cache.put(address, m);
    try {
      if (doSendAsync(m)) {
        return m.waitForAck();
      }
      return false;
    } finally {
      cache.invalidate(address);
    }
  }

  // called by a threads to send data synchronously but with socket as the parameter
  public boolean sendToQueueSync(final long address, final byte[] encodedRecords, final Socket socket) {
    PendingMessage m = new PendingMessage(address, encodedRecords, false);
    cache.put(address, m);
    try {
      if (doSendAsync(m, socket)) {
        return m.waitForAck();
      }
      return false;
    } finally {
      cache.invalidate(address);
    }
  }

  public void handleAckReceived(final long address) {
    PendingMessage record = cache.getIfPresent(address);
    if (record != null) {
      record.ackReceived();
      cache.invalidate(address);
    }
  }
}

来电者只会调用以下三种方法之一:

    通过传递两个参数
  • sendToQueueAsync
  • sendToQueueSync传递两个参数
  • sendToQueueSync传递三个参数

我应该如何设计我的PacketSendRecord类,以便告诉Packet类需要将这些数据以上述三种方式之一发送到我的消息队列。由用户决定他想将数据发送到消息队列的方式。截至目前我的Packet类的结构方式,它只能以一种方式发送数据。

6 个答案:

答案 0 :(得分:4)

I think your best option is the Strategy pattern (https://en.wikipedia.org/wiki/Strategy_pattern).

Using this pattern, you can encapsulate the behaviour of each type of "send", for example, an AsynchronousSend class, a SynchronousSend class and an AsynchronousSocketSend class. (You could probably come up with better names). The Packet class can then decide, based on some logic, which class to use to send the data to the queue.

答案 1 :(得分:2)

我在sender中没有看到Packet的定义。我假设它被定义为私有实例变量?

设计确实需要修复。 通过让Packet类进行发送,设计会违反Single responsibility principle。应该有一个单独的(可能是抽象的)类来准备要发送的数据(准备一个java.nio.Buffer实例),它可以有一个或多个子类,其中一个返回一个java.nio.ByteBuffer实例。 / p>

获得Buffer并执行发送的单独类。这个(可能是抽象的)类可以有不同发送平台和方法的子类。

然后,您需要另一个实现Builder pattern的类。希望发送数据包的客户端,使用构建器指定具体的PacketSender(以及可能的其他所需属性,如套接字号),然后调用发送的send()

答案 2 :(得分:2)

首先,您需要明确回答谁(或您的代码的哪一部分)负责决定使用哪种发送方法的问题。

  • 是否基于某些外部配置?
  • 是基于某些(动态)用户决策吗?
  • 是否基于正在处理的分区?
  • 是否基于邮件内容?

(仅举几例)

答案将决定哪种结构最合适。

然而,很明显,目前的sendData()方法是决定实施的地方。因此,这种方法需要提供使用的实现。在所有情况下,实际send()可能相似。它建议将发送功能封装到提供send()方法签名的接口中:

send(address, data);

如果要根据实际的消息数据确定目标套接字,那么您可能更喜欢

的常规签名
send(address, data, socket);

并使该套接字值可选或使用特定值来编码“无特定套接字”的情况。否则,您可以使用通过构造函数传入套接字的特定Sender实例。

我目前没有看到你所提供的有效理由,即在一个类中将三种不同的发送方法实现为三种不同的方法。如果公共代码是一个原因,那么使用公共基类将允许适当的共享。

这就提出了如何在Sender中提供适当的sendData()实施的具体实例的问题。

如果要在sendData()之外确定发送策略,则必须提交实现。无论是作为参数还是来自当前类实例的字段。如果本地数据正在确定发送策略,则应将正确实现的确定委派给将返回正确实现的选择类。该调用将类似于:

startegySelector.selectStartegy(selectionParameters).send(address,data);

但是,如果没有更清晰地了解修复内容和执行中的变量,那么很难建议最佳方法

如果决定基于数据,则整个选择和转移过程是Packet类的本地。

如果决定是在Packet外部做出的,您可能希望在该位置获得发送策略实施,并将其作为参数传递到addAndSendJunked()(或者更准确地说是sendData()

答案 3 :(得分:2)

你可以有一个枚举类,比如说PacketTransportionMode可以为不同类型的枚举值(SYNC,ASYNC,SYNC_ON_SOCKET)覆盖'send'方法,例如:。

public enum PacketTransportionMode {
SYNC {
    @Override
    public boolean send(Packet packet) {
        byte[] message = packet.getMessage();
        Socket socket = new Socket(packet.getReceiverHost(), packet.getReceiverPort());
        DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());
        dOut.writeInt(message.length); // write length of the message
        dOut.write(message);           // write the message
        return true;
    }
},
ASYNC {
    @Override
    public boolean send(Packet packet) {
        // TODO Auto-generated method stub
        return false;
    }
},
SYNC_ON_SOCKET

{
    @Override
    public boolean send(Packet packet) {
        // TODO Auto-generated method stub
        return false;
    }

};
public abstract boolean send(Packet packet);
}

另外,在数据包类中,引入transportMode变量。在packet.send()实现中,可以调用this.packetTransportationMode.send(this)

客户端可以在开头创建数据包对象并设置其transportMode,类似于设置RecordPartition。然后客户端可以调用packet.send();

或者不是将transportMode变量放在数据包类中并调用this.packetTransportationMode.send(this),客户端也可以创建Packet对象并直接调用PacketTransportionMode.SYNC.send(数据包)。

答案 4 :(得分:2)

使用enum varibale定义发送消息的类型

public enum TypeToSend {
    async, sync, socket 
}

public final class Packet implements Closeable {
TypeToSend typeToSend;
public Packet(TypeToSend typeToSend) {
        this.typeToSend = typeToSend;
    }
switch(typeToSend){
     case async:{}
     case sync:{}
     case socket:{}
}
}

答案 5 :(得分:1)

策略。与Kerri Brown的不同之处在于,Packet不应该在策略之间做出决定。相反,在Packet类之外决定它。

单个发送策略接口应该由3个不同的类实现,每个类对应于上述发送方法之一。将策略接口注入到数据包中,因此无论处理哪种策略,数据包都不必更改。

你说它必须基于用户的选择。因此,您可以提前询问用户,选择是什么,然后在此基础上,实例化与用户选择相对应的发送策略界面。然后,使用选定的发送策略实例实例化数据包。

如果您认为以后的选择可能不依赖于用户,那么请将其设为工厂。那么你的解决方案就变成了工厂和战略的结合。

在这种情况下,Packet可以注入Factory接口。 Packet要求Factory给它发送策略。接下来,它使用从工厂获得的策略发送。工厂要求用户输入,以后可以通过基于某些其他条件而不是用户输入进行选择来替换。您可以通过以后以不同方式实现Factory接口并注入新工厂而不是此工厂(即基于用户输入的工厂与其他基于条件的工厂)来实现这一目标。

这两种方法都会为您提供遵循开/关原则的代码。但如果你真的不需要工厂,尽量不要过度工程。

相关问题