===如果有需要demo源码的,请留言=
前言:
今天项目组遇到一个局点问题,关于数据转发服务器
如何把接收到的无序数据
按照有序的顺序
发送给第三方服务器,数据转发服务器
通过netty server
从数据源多线程接收tcp协议数据报文
,netty server端接收数据无法保障数据的有序性
;
组网如下图:
并且处理此问题有两个条件:1)尽量避免数据丢失
; 2)保障数据发送到第三方是有序的
;
分析思路:
由于转发服务器使用netty server
多线程接收数据,所以绝对不可能百分之百保障数据的有序性
,只能在一定前提下保障数据的部分有序性
,同时也会存在一定数据丢失的问题,但是只要控制少量的数据丢失,这个也是可接受的;
实现步骤:
1)我们使用netty server
多线程接收数据,多线程接收数据
也能提高数据的时效性,避免数据延迟太大
;
2)接收到的数据放入阻塞队列
中,这个阻塞对接我们选择使用优先队列PriorityBlockingQueue
(实现BlockingQueue
接口),优先队列可以对我们存储的数据按照优先级规则进行排序,避免编码实现数据排序,PriorityBlockingQueue
是线程安全
的,而PriorityQueue
是非线程安全
的,由于存在多线程竞争的情况,我们选择使用线程安全的PriorityBlockingQueue
;
3)创建线程池
用于监控优先队列PriorityBlockingQueue
对头数据,设置消息最大延迟时间,如果队头数据延迟时间超过阈值P
,则消费队头数据发送三方服务器;
4)记录最后一条数据的优先级排序属性
,如果步骤1)接收到的数据的优先级条件+延迟时间小于当前时间,则直接丢弃此数据,因为比它晚的数据我们已经发送给了第三方,我们不可能再发送更早的数据,如果发送,则导致之前已发送数据乱序;
5)数据处理逻辑在入优先队列之前实现,因为出优先队列的线程是单线程
,有可能导致性能问题;
代码实现:
1)消息类
package com.sk.bean;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class MessageInfo implements Comparable<MessageInfo>{
private Integer orderId; //顺序标识
private String message; //消息内容
private Long sendTime; //优先级排序条件
@Override
public int compareTo(MessageInfo o) {
return sendTime <= o.sendTime ? -1 : 1;
}
}
2)优先级队列
package com.sk.common;
import com.sk.bean.MessageInfo;
import java.util.concurrent.PriorityBlockingQueue;
public class QueueTools {
public final static PriorityBlockingQueue<MessageInfo> queue = new PriorityBlockingQueue<>();
}
3)netty server接收数据
package com.sk.server;
import com.alibaba.fastjson.JSONObject;
import com.sk.bean.MessageInfo;
import com.sk.common.QueueTools;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class SocketHandler extends ChannelInboundHandlerAdapter {
public static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 读取到客户端发来的消息
*
* @param ctx ChannelHandlerContext
* @param msg msg
* @throws Exception e
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 由于我们配置的是 字节数组 编解码器,所以这里取到的用户发来的数据是 byte数组
byte[] data = (byte[]) msg;
log.info("收到消息: " + new String(data));
MessageInfo messageInfo = JSONObject.parseObject(new String(data),MessageInfo.class);
if(null != messageInfo) {
//获取数据入优先级队列
QueueTools.queue.add(messageInfo);
// 给其他人转发消息
for (Channel client : clients) {
if (!client.equals(ctx.channel())) {
client.writeAndFlush(data);
}
}
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info("新的客户端链接:" + ctx.channel().id().asShortText());
clients.add(ctx.channel());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
clients.remove(ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.channel().close();
clients.remove(ctx.channel());
}
}
4)优先队列,队列头消费实现
package com.sk.service;
import com.sk.bean.MessageInfo;
import com.sk.common.QueueTools;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Log4j2
@Configuration
public class Consumer {
//消息延迟消费时间
private int delayTime = 10 * 1000;
//记录新一条发送第三方成功数据标识
private Long lastConsumerTime = Long.MIN_VALUE;
@PostConstruct
private void run(){
new Thread(() -> {
while(true){
if(null == QueueTools.queue.peek()){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
Long nowTime = System.currentTimeMillis();
if(QueueTools.queue.peek().getSendTime()+delayTime > nowTime){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
MessageInfo messageInfo = null;
try {
messageInfo = QueueTools.queue.take();
if(messageInfo.getSendTime() > lastConsumerTime) {
log.info("有序数据:{}",messageInfo.toString());
lastConsumerTime = messageInfo.getSendTime();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
5)数据源发送数据
import com.alibaba.fastjson.JSONObject;
import com.sk.common.ConstTools;
import lombok.extern.log4j.Log4j2;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.Scanner;
@Log4j2
public class ChatClient {
String delimiter = "_$";
public void start(String name) throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8088));
socketChannel.configureBlocking(false);
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_READ);
// 监听服务端发来得消息
new Thread(new ClientThread(selector)).start();
// 监听用户输入
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String message = scanner.nextLine();
if (StringUtils.hasText(message)) {
System.out.println("====开始发送数据====");
Long nowTime = System.currentTimeMillis();
Random random = new Random();
for (int i = 0; i < 30; i++) {
Integer num = random.nextInt(30);
JSONObject jsonObject = new JSONObject();
jsonObject.put("orderId", num);
jsonObject.put("message", name + "===第" + num + "条数据");
jsonObject.put("sendTime", nowTime + num);
System.out.println("发送数据:" + jsonObject.toJSONString());
socketChannel.write(StandardCharsets.UTF_8.encode(jsonObject.toJSONString() + delimiter));
}
}
break;
}
}
}
初始化客户端
package com.sk.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
public class ClientThread implements Runnable{
private final Selector selector;
public ClientThread(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
for (; ; ) {
int channels = selector.select();
if (channels == 0) {
continue;
}
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeySet.iterator();
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
// 移除集合当前得selectionKey,避免重复处理
keyIterator.remove();
if (selectionKey.isReadable()) {
this.handleRead(selector, selectionKey);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 处理可读状态
private void handleRead(Selector selector, SelectionKey selectionKey) throws IOException {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
StringBuilder message = new StringBuilder();
if (channel.read(byteBuffer) > 0) {
byteBuffer.flip();
message.append(StandardCharsets.UTF_8.decode(byteBuffer));
}
// 再次注册到选择器上,继续监听可读状态
channel.register(selector, SelectionKey.OP_READ);
System.out.println(message);
}
}
执行结果:
1)客户端发送数据
Connected to the target VM, address: '127.0.0.1:14141', transport: 'socket'
send
====开始发送数据====
发送数据:{"orderId":13,"message":"李四===第13条数据","sendTime":1668790807323}
发送数据:{"orderId":10,"message":"李四===第10条数据","sendTime":1668790807320}
发送数据:{"orderId":15,"message":"李四===第15条数据","sendTime":1668790807325}
发送数据:{"orderId":9,"message":"李四===第9条数据","sendTime":1668790807319}
发送数据:{"orderId":21,"message":"李四===第21条数据","sendTime":1668790807331}
发送数据:{"orderId":26,"message":"李四===第26条数据","sendTime":1668790807336}
发送数据:{"orderId":27,"message":"李四===第27条数据","sendTime":1668790807337}
发送数据:{"orderId":11,"message":"李四===第11条数据","sendTime":1668790807321}
发送数据:{"orderId":24,"message":"李四===第24条数据","sendTime":1668790807334}
发送数据:{"orderId":22,"message":"李四===第22条数据","sendTime":1668790807332}
发送数据:{"orderId":9,"message":"李四===第9条数据","sendTime":1668790807319}
发送数据:{"orderId":21,"message":"李四===第21条数据","sendTime":1668790807331}
发送数据:{"orderId":15,"message":"李四===第15条数据","sendTime":1668790807325}
发送数据:{"orderId":17,"message":"李四===第17条数据","sendTime":1668790807327}
发送数据:{"orderId":18,"message":"李四===第18条数据","sendTime":1668790807328}
发送数据:{"orderId":26,"message":"李四===第26条数据","sendTime":1668790807336}
发送数据:{"orderId":25,"message":"李四===第25条数据","sendTime":1668790807335}
发送数据:{"orderId":16,"message":"李四===第16条数据","sendTime":1668790807326}
发送数据:{"orderId":15,"message":"李四===第15条数据","sendTime":1668790807325}
发送数据:{"orderId":27,"message":"李四===第27条数据","sendTime":1668790807337}
发送数据:{"orderId":9,"message":"李四===第9条数据","sendTime":1668790807319}
发送数据:{"orderId":28,"message":"李四===第28条数据","sendTime":1668790807338}
发送数据:{"orderId":15,"message":"李四===第15条数据","sendTime":1668790807325}
发送数据:{"orderId":15,"message":"李四===第15条数据","sendTime":1668790807325}
发送数据:{"orderId":8,"message":"李四===第8条数据","sendTime":1668790807318}
发送数据:{"orderId":17,"message":"李四===第17条数据","sendTime":1668790807327}
发送数据:{"orderId":8,"message":"李四===第8条数据","sendTime":1668790807318}
发送数据:{"orderId":8,"message":"李四===第8条数据","sendTime":1668790807318}
发送数据:{"orderId":13,"message":"李四===第13条数据","sendTime":1668790807323}
发送数据:{"orderId":26,"message":"李四===第26条数据","sendTime":1668790807336}
2)发送第三方数据顺序
01:00:17.529 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=8, message=李四===第8条数据, sendTime=1668790807318)
01:00:17.530 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=9, message=李四===第9条数据, sendTime=1668790807319)
01:00:17.530 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=10, message=李四===第10条数据, sendTime=1668790807320)
01:00:17.530 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=11, message=李四===第11条数据, sendTime=1668790807321)
01:00:17.530 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=13, message=李四===第13条数据, sendTime=1668790807323)
01:00:17.530 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=15, message=李四===第15条数据, sendTime=1668790807325)
01:00:17.531 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=16, message=李四===第16条数据, sendTime=1668790807326)
01:00:17.531 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=17, message=李四===第17条数据, sendTime=1668790807327)
01:00:17.531 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=18, message=李四===第18条数据, sendTime=1668790807328)
01:00:17.531 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=21, message=李四===第21条数据, sendTime=1668790807331)
01:00:17.531 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=22, message=李四===第22条数据, sendTime=1668790807332)
01:00:17.531 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=24, message=李四===第24条数据, sendTime=1668790807334)
01:00:17.532 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=25, message=李四===第25条数据, sendTime=1668790807335)
01:00:17.532 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=26, message=李四===第26条数据, sendTime=1668790807336)
01:00:17.532 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=27, message=李四===第27条数据, sendTime=1668790807337)
01:00:17.532 [Thread-8] INFO com.sk.service.Consumer.lambda$run$0(Consumer.java:45) - 有序数据:MessageInfo(orderId=28, message=李四===第28条数据, sendTime=1668790807338)