IoT推送系统

IoT是什么

The Internet of things的简称IoT,即是物联网的意思,具体的知识请查阅:什么是Iot?什么是AIot?

IoT推送系统的设计

比如说,像一些智能设备,需要通过APP或者微信中的小程序等,给设备发送一条指令,让这个设备下载或者播放音乐,那么需要做什么才可以完成上面的任务呢?



首先需要推送服务器,这个服务器主要负责消息的分发,不处理业务消息;设备会连接到推送服务器,APP通过把指令发送到推送服务器,然后推送服务器再把指令分发给相应的设备。

可是,当买设备的人越来越多,推送服务器所能承受的压力就越大,这个时候就需要对推送服务器做集群,一台不行,就搞十台,那么还有一个问题,就是推送服务器增加了,设备如何找到相应的服务器,然后和服务器建立连接呢,注册中心可以解决这个问题,每一台服务器都注册到注册中心上,设备会请求注册中心,得到推送服务器的地址,然后再和服务器建立连接。

而且还会有相应的redis集群,用来记录设备订阅的主题以及设备的信息;APP发送指令到设备,其实就是发送了一串数据,相应的会提供推送API,提供一些接口,通过接口把数据发送过去;而推送API不是直接去连接推送服务器的,中间还会有MQ集群,主要用来消息的存储,推送API推送消息到MQ,推送服务器从MQ中订阅消息,以上就是简单的IoT推送系统的设计。

下面看下结构图:



注意:设备连接到注册中心的是短连接,设备和推送服务器建立的连接是长连接

心跳检测机制

简述心跳检测

心跳检测,就是判断对方是否还存活,一般采用定时的发送一些简单的包,如果在指定的时间段内没有收到对方的回应,则判断对方已经挂掉

Netty提供了IdleStateHandler类来实现心跳,简单的使用如下:

pipeline.addFirst(new IdleStateHandler(0, 0, 1, TimeUnit.SECONDS));

下面是IdleStateHandler的构造函数:

public IdleStateHandler(
long readerIdleTime, long writerIdleTime, long allIdleTime,
TimeUnit unit) {
this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
}

四个参数说明:

1:readerIdleTime,读超时时间

2:writerIdleTime,写超时时间

3:allIdleTime,所有事件超时时间

4:TimeUnit unit,超时时间单位

心跳检测机制代码示例

简单示例:

服务端:

static final int BEGIN_PORT = 8088;
static final int N_PORT = 100; public static void main(String[] args) {
new PingServer().start(BEGIN_PORT, N_PORT);
} public void start(int beginPort, int nPort) {
System.out.println("启动服务...."); EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.handler(new LoggingHandler(LogLevel.INFO));
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childOption(ChannelOption.SO_REUSEADDR, true); bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addFirst(new IdleStateHandler(0, 0, 1, TimeUnit.SECONDS));
pipeline.addLast(new PingHandler());
//每个连接都有个ConnectionCountHandler对连接记数进行增加
pipeline.addLast(new ConnectionCountHandler());
}
}); bootstrap.bind(beginPort).addListener((ChannelFutureListener) future -> {
System.out.println("端口绑定成功: " + beginPort);
});
System.out.println("服务已启动!");
}
public class PingHandler extends SimpleUserEventChannelHandler<IdleStateEvent> {
private static final ByteBuf PING_BUF = Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer("ping".getBytes())); private int count; @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String str = new String(data);
if ("pong".equals(str)) {
System.out.println(ctx + " ---- " + str);
count--;
}
ctx.fireChannelRead(msg);
} @Override
protected void eventReceived(ChannelHandlerContext ctx, IdleStateEvent evt) throws Exception {
if (evt.state() == ALL_IDLE) {
if (count >= 3) {
System.out.println("检测到客户端连接无响应,断开连接:" + ctx.channel());
ctx.close();
return;
} count++;
System.out.println(ctx.channel() + " ---- ping");
ctx.writeAndFlush(PING_BUF.duplicate());
}
ctx.fireUserEventTriggered(evt);
}
}

客户端:

//服务端的IP
private static final String SERVER_HOST = "localhost"; static final int BEGIN_PORT = 8088;
static final int N_PORT = 100; public static void main(String[] args) {
new PoneClient().start(BEGIN_PORT, N_PORT);
} public void start(final int beginPort, int nPort) {
System.out.println("客户端启动....");
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
final Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_REUSEADDR, true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new PongHandler());
}
}); int index = 0;
int port; String serverHost = System.getProperty("server.host", SERVER_HOST);
ChannelFuture channelFuture = bootstrap.connect(serverHost, beginPort);
channelFuture.addListener((ChannelFutureListener) future -> {
if (!future.isSuccess()) {
System.out.println("连接失败,退出!");
System.exit(0);
}
});
try {
channelFuture.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public class PongHandler extends SimpleChannelInboundHandler<ByteBuf> {
private static final ByteBuf PONG_BUF = Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer("pong".getBytes())); @Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String str = new String(data);
if ("ping".equals(str)) {
ctx.writeAndFlush(PONG_BUF.duplicate());
}
}
}

服务端输出结果:

百万长连接优化

连接优化代码示例

服务端:

    static final int BEGIN_PORT = 11000;
static final int N_PORT = 100; public static void main(String[] args) {
new Server().start(BEGIN_PORT, N_PORT);
} public void start(int beginPort, int nPort) {
System.out.println("启动服务...."); EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childOption(ChannelOption.SO_REUSEADDR, true); bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//每个连接都有个ConnectionCountHandler对连接记数进行增加
pipeline.addLast(new ConnectionCountHandler());
}
}); //这里开启 10000到100099这100个端口
for (int i = 0; i < nPort; i++) {
int port = beginPort + i;
bootstrap.bind(port).addListener((ChannelFutureListener) future -> {
System.out.println("端口绑定成功: " + port);
});
}
System.out.println("服务已启动!");
}

客户端:

//服务端的IP
private static final String SERVER_HOST = "192.168.231.129"; static final int BEGIN_PORT = 11000;
static final int N_PORT = 100; public static void main(String[] args) {
new Client().start(BEGIN_PORT, N_PORT);
} public void start(final int beginPort, int nPort) {
System.out.println("客户端启动....");
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
final Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_REUSEADDR, true); int index = 0;
int port; String serverHost = System.getProperty("server.host", SERVER_HOST);
//从10000的端口开始,按端口递增的方式进行连接
while (!Thread.interrupted()) {
port = beginPort + index;
try {
ChannelFuture channelFuture = bootstrap.connect(serverHost, port);
channelFuture.addListener((ChannelFutureListener) future -> {
if (!future.isSuccess()) {
System.out.println("连接失败,退出!");
System.exit(0);
}
});
channelFuture.get();
} catch (Exception e) {
} if (++index == nPort) {
index = 0;
}
}
}

ConnectionCountHandler类:

public class ConnectionCountHandler extends ChannelInboundHandlerAdapter {

    //这里用来对连接数进行记数,每两秒输出到控制台
private static final AtomicInteger nConnection = new AtomicInteger(); static {
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
System.out.println("连接数: " + nConnection.get());
}, 0, 2, TimeUnit.SECONDS);
} @Override
public void channelActive(ChannelHandlerContext ctx) {
nConnection.incrementAndGet();
} @Override
public void channelInactive(ChannelHandlerContext ctx) {
nConnection.decrementAndGet();
}
}

上述的代码会打包成jar放到linux上运行,对于上述的优化来说,程序方面的就暂时不做,下面会从操作系统层面进行优化,让其支撑起百万连接。

TCP连接四元组

在优化之前先来看下网络里的一个小知识,TCP连接四元组:

服务器的IP+服务器的POST+客户端的IP+客户端的POST

端口的范围一般是1到65535:

配置优化

现在在虚拟机上安装两个linux系统,配置分别是:

地址 CPU 内存 JDK 作用
192.168.15.130 VM-4核 8G 1.8 客户端
192.168.15.128 VM-4核 8G 1.8 服务端

启动服务端:

java -Xmx4g -Xms4g -cp network-study-1.0-SNAPSHOT-jar-with-dependencies.jar com.dongnaoedu.network.netty.million.Server > out.log 2>&1 &

启动客户端:

java -Xmx4g -Xms4g -Dserver.host=192.168.15.128 -cp network-study-1.0-SNAPSHOT-jar-with-dependencies.jar com.dongnaoedu.network.netty.million.Client

启动服务端后可以使用tail -f命令查看out.log中的日志:



客户端启动后,如果报了以下错误,需要修改系统的文件最大句柄和进程的文件最大句柄:

Caused by: java.io.IOException: Too many open files
at sun.nio.ch.FileDispatcherImpl.init(Native Method)
at sun.nio.ch.FileDispatcherImpl.<clinit>(FileDispatcherImpl.java:35)
... 8 more

优化系统最大句柄:

查看操作系统最大文件句柄数,执行命令cat /proc/sys/fs/file-max,查看最大句柄数是否满足需要,如果不满足,通过vim /etc/sysctl.conf命令插入如下配置:

fs.file-max = 1000000
  1. 设置单进程打开的文件最大句柄数,执行命令ulimit -a查看当前设置是否满足要求:
[root@test-server2 download]# ulimit -a | grep "open files"
open files (-n) 1024

当并发接入的Tcp连接数超过上限时,就会提示“Too many open files”,所有的新客户端接入将会失败。通过vim /etc/security/limits.conf 修改配置参数:

* soft nofile 1000000
* hard nofile 1000000

修改配置参数后注销生效。

  • 如果程序被中断,或报了异常
java.io.IOException: 设备上没有空间
at sun.nio.ch.EPollArrayWrapper.epollCtl(Native Method)
at sun.nio.ch.EPollArrayWrapper.updateRegistrations(EPollArrayWrapper.java:299)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:268)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
at sun.nio.ch.SelectorImpl.selectNow(SelectorImpl.java:105)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.selectNow(SelectedSelectionKeySetSelector.java:56)
at io.netty.channel.nio.NioEventLoop.selectNow(NioEventLoop.java:750)
at io.netty.channel.nio.NioEventLoop$1.get(NioEventLoop.java:71)
at io.netty.channel.DefaultSelectStrategy.calculateStrategy(DefaultSelectStrategy.java:30)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:426)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:905)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
  • 此时可以查看操作系统的日志more /var/log/messages,或在程序启动时执行tail -f /var/log/messages 监控日志。如果日志中出现以下内容,说明需要优化TCP/IP参数
Jun  4 16:55:01 localserver kernel: TCP: too many orphaned sockets
Jun 4 16:55:01 localserver kernel: TCP: too many orphaned sockets
Jun 4 16:55:01 localserver kernel: TCP: too many orphaned sockets
Jun 4 16:55:01 localserver kernel: TCP: too many orphaned sockets
Jun 4 16:55:01 localserver kernel: TCP: too many orphaned sockets
Jun 4 16:55:01 localserver kernel: TCP: too many orphaned sockets
Jun 4 16:55:01 localserver kernel: TCP: too many orphaned sockets

优化TCP/IP相关参数:

  • 查看客户端端口范围限制
cat /proc/sys/net/ipv4/ip_local_port_range
  • 通过vim /etc/sysctl.conf 修改网络参数

  • 客户端修改端口范围的限制

net.ipv4.ip_local_port_range = 1024 65535
  • 优化TCP参数
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_wmem = 4096 4096 16777216
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_keepalive_time = 1800
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

参数说明:

net.ipv4.tcp_mem: 分配给tcp连接的内存,单位是page(1个Page通常是4KB,可以通过getconf PAGESIZE命令查看),三个值分别是最小、默认、和最大。比如以上配置中的最大是3145728,那分配给tcp的最大内存=31457284 / 1024 / 1024 = 12GB。一个TCP连接大约占7.5KB,粗略可以算出百万连接≈7.51000000/4=1875000 3145728足以满足测试所需。

net.ipv4.tcp_wmem: 为每个TCP连接分配的写缓冲区内存大小,单位是字节。三个值分别是最小、默认、和最大。

net.ipv4.tcp_rmem: 为每个TCP连接分配的读缓冲区内存大小,单位是字节。三个值分别是最小、默认、和最大。

net.ipv4.tcp_keepalive_time: 最近一次数据包发送与第一次keep alive探测消息发送的事件间隔,用于确认TCP连接是否有效。

net.ipv4.tcp_keepalive_intvl: 在未获得探测消息响应时,发送探测消息的时间间隔。

net.ipv4.tcp_keepalive_probes: 判断TCP连接失效连续发送的探测消息个数,达到之后判定连接失效。

net.ipv4.tcp_tw_reuse: 是否允许将TIME_WAIT Socket 重新用于新的TCP连接,默认为0,表示关闭。

net.ipv4.tcp_tw_recycle: 是否开启TIME_WAIT Socket 的快速回收功能,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout: 套接字自身关闭时保持在FIN_WAIT_2 状态的时间。默认为60。

网络编程Netty IoT百万长连接优化的更多相关文章

  1. JAVA网络编程Socket常见问题 【长连接专题】

    一. 网络程序运行过程中的常见异常及处理 第1个异常是 java.net.BindException:Address already in use: JVM_Bind. 该异常发生在服务器端进行new ...

  2. 基于netty实现的长连接,心跳机制及重连机制

    技术:maven3.0.5 + netty4.1.33 + jdk1.8   概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...

  3. 《UNIX网络编程》之多客户连接服务端,可重用套接字对

    该网络编程之客户端与服务端程序模板支持: 1. 多客户端同时连接服务端,即服务程序可以同时为多个客户端服务: 2. 服务端支持套接字对重用,即即使处于TIME_WAIT状态,仍可支持服务端重启: 3. ...

  4. 网络编程Netty入门:Netty简介及其特性

    目录 Netty的简介 Netty的特性 Netty的整体结构 Netty的核心组件 Netty的线程模型 结束语 Netty的简介 Netty是一个java开源框架,是基于NIO的高性能.高可扩展性 ...

  5. 石墨文档Websocket百万长连接技术实践

    引言 在石墨文档的部分业务中,例如文档分享.评论.幻灯片演示和文档表格跟随等场景,涉及到多客户端数据同步和服务端批量数据推送的需求,一般的 HTTP 协议无法满足服务端主动 Push 数据的场景,因此 ...

  6. 网络编程中 TCP 半开连接和TIME_WAIT 学习

    https://blog.csdn.net/chrisnotfound/article/details/80112736 上面的链接就是说明来 SO_KEEPALIVE 选项 为什么还需要 在应用层开 ...

  7. netty实现TCP长连接

    所用jar包 netty-all-4.1.30.Final.jar 密码:rzwe NettyConfig.java,存放连接的客户端 import io.netty.channel.group.Ch ...

  8. Java网络编程 -- Netty入门

    Netty简介 Netty是一个高性能,高可扩展性的异步事件驱动的网络应用程序框架,它极大的简化了TCP和UDP客户端和服务器端网络开发.它是一个NIO框架,对Java NIO进行了良好的封装.作为一 ...

  9. 网络编程Netty入门:Netty的启动过程分析

    目录 Netty的启动过程 Bootstrap 服务端的启动 客户端的启动 TCP粘包.拆包 图示 简单的例子 Netty编解码框架 Netty解码器 ByteToMessageDecoder实现类 ...

随机推荐

  1. 为什么Linux需要虚拟内存

    本文转载自为什么 Linux 需要虚拟内存 导语 操作系统中的 CPU 和主内存(Main memory)都是稀缺资源,所有运行在当前操作系统的进程会共享系统中的 CPU 和内存资源,操作系统会使用 ...

  2. net面试总结的题目

    准备的面试题目. 1.private.protected.public.internal的访问权限? private : 私有成员,在类的内部才可以访问. protected :保护成员,该类内部和继 ...

  3. Java开发工程师最新面试题库系列——Spring部分(附答案)

    Spring Spring框架是什么? 答:Spring是轻量级的面向切面和控制反转的框架.初代版本为2002年发布的interface21,Spring框架是为了解决企业级应用开发的复杂性的出现的, ...

  4. Hystrix熔断器的使用步骤

    1.添加熔断器依赖 2.在配置文件中开启熔断器 feign.hystrix.enabled=true 3.写接口的实现类VodFileDegradeFeignClient,在实现类中写如果出错了输出的 ...

  5. oracle中关键字的执行顺序

    执行顺序: from where group by having select order by ******当having/select 中出现组函数,那么其他没有被组函数修饰的列就必须出现下gro ...

  6. 基于μcOS-II实时操作系统源码实现RMS和EDF调度(共享资源)

    μcOS-II多任务实验报告(RMS.EDF调度) 目录 μcOS-II多任务实验报告(RMS.EDF调度) 一.实验概述 二.环境搭建 三.代码分析 四.实验步骤 1 给TCB块添加扩展 2 创建并 ...

  7. 学习java之基础语法(三)

    学习java之基础语法(三) java运算符 计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量.我们可以把运算符分成以下几组: 算术运算符 关系运 ...

  8. (报错解决)Exception encountered during context initialization

    转: (报错解决)Exception encountered during context initialization 关键词 JavaEE JavaWeb eclipse XML AspectJ ...

  9. NPOI 在指定单元格导入导出图片

    NPOI 在指定单元格导入导出图片 Intro 我维护了一个 NPOI 的扩展,主要用来导入导出 Excel 数据,最近有网友提出了导入 Excel 的时候解析图片的需求,于是就有了本文的探索 导入E ...

  10. 微信小程序一周时间表

    <view class="dateView"> <image class="dateLeft" bindtap="prevWeek& ...