实现netty server多线程接收无序数据转换成有序数据的方法

===如果有需要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)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值