From b6b03fcba3333873b351510d3bac6ae6d41a393e Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Sun, 16 May 2021 21:13:45 +0800 Subject: [PATCH 001/482] md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 68e554a5..3d8b7224 100644 --- a/README.md +++ b/README.md @@ -194,5 +194,10 @@ curl -H "Content-Type: application/json" -X POST -d '{"topic": "test/teus", "qos [Apache License, Version 2.0](LICENSE) +## 麻烦关注下公众号! +![image](icon/icon.jpg) + +- 添加微信号`Lemon877164954`,拉入smqtt官方交流群 +- 加入qq群 `700152283` -- Gitee From d85407a592e689d0729543817592def526169f6b Mon Sep 17 00:00:00 2001 From: luxurong Date: Wed, 26 May 2021 16:53:51 +0800 Subject: [PATCH 002/482] icon --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c5c27aec..dba23f52 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ SMQTT基于Netty开发,底层采用Reactor3反应堆模型,支持单机部署,支持容器化部署,具备低延迟,高吞吐量,支持百万TCP连接,同时支持多种协议交互,是一款非常优秀的消息中间件! ## smqtt目前拥有的功能如下: +![架构图](icon/component.png) 1. 消息质量等级实现(支持qos0,qos1,qos2) 2. 会话消息 -- Gitee From 98826d0833039a44e9db8ed082694940ae9cafdf Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Sat, 26 Jun 2021 21:42:58 +0800 Subject: [PATCH 003/482] =?UTF-8?q?doc=20=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.properties | 10 +- docs/http/1.wd.md | 3 + "docs/\345\205\245\351\227\250/1.jar.md" | 132 +++++++++++++----- .../io/github/quickmsg/AbstractStarter.java | 46 +++--- .../common/bootstrap/BootstrapKey.java | 5 +- .../io/github/quickmsg/core/Bootstrap.java | 11 +- 6 files changed, 151 insertions(+), 56 deletions(-) diff --git a/config.properties b/config.properties index 156849c5..319da376 100644 --- a/config.properties +++ b/config.properties @@ -32,8 +32,14 @@ smqtt.http.port=60000 smqtt.http.accesslog=true # 开启ssl smqtt.http.ssl.enable=false -# smqtt.http.ssl.crt =; -# smqtt.http.ssl.key; +# smqtt.http.ssl.crt = +# smqtt.http.ssl.key = +# 开启管理后台(必须开启http) +smqtt.http.admin.enable = true +# 管理后台登录用户 +smqtt.http.admin.username=smqtt +# 管理后台登录密码 +smqtt.http.admin.password=smqtt # 开启集群 smqtt.cluster.enable=false # 集群节点地址 diff --git a/docs/http/1.wd.md b/docs/http/1.wd.md index 40a27357..346bc2c9 100644 --- a/docs/http/1.wd.md +++ b/docs/http/1.wd.md @@ -23,6 +23,9 @@ Bootstrap.HttpOptions参数: | ssl | 开启ssl加密 |否 | | sslContext | ssl证书配置,为空则使用系统生成 |否 | | httpPort | http服务端口,为空则随机端口 |否 | +| enableAdmin | 管理后台开启(必须使用60000端口) |否 | +| username |管理后台登录用户名 |否 | +| password | 管理后台登录密码 |否 | | accessLog | http日志开启 |否 | sslContext参数: diff --git "a/docs/\345\205\245\351\227\250/1.jar.md" "b/docs/\345\205\245\351\227\250/1.jar.md" index e615a457..f836b589 100644 --- "a/docs/\345\205\245\351\227\250/1.jar.md" +++ "b/docs/\345\205\245\351\227\250/1.jar.md" @@ -16,41 +16,103 @@ sort: 2 ## 使用配置文件 config.properties ```markdown - - # 开启tcp端口 - smqtt.tcp.port=1883 - # 高水位 - smqtt.tcp.lowWaterMark=4000000 - # 低水位 - smqtt.tcp.highWaterMark=80000000 - # 开启ssl加密 - smqtt.tcp.ssl=false - # 证书crt smqtt.tcp.ssl.crt = - # 证书key smqtt.tcp.ssl.key = - # 开启日志 - smqtt.tcp.wiretap=false - # boss线程 - smqtt.tcp.bossThreadSize=4 - # work线程 - smqtt.tcp.workThreadSize=8 - # websocket端口 - smqtt.websocket.port=8999 - # websocket开启 - smqtt.websocket.enable=true - # smqtt用户 - smqtt.tcp.username=smqtt - # smqtt密码 - smqtt.tcp.password=smqtt - # 开启http - smqtt.http.enable=true - # 开启http端口 - smqtt.http.port=1999 - # 开启http日志 - smqtt.http.accesslog=true - # 开启ssl - smqtt.http.ssl.enable=false - # smqtt.http.ssl.crt = - # smqtt.http.ssl.key= +# 日志级别 ALL|TRACE|DEBUG|INFO|WARN|ERROR|OFF +smqtt.log.level=info +# 开启tcp端口 +smqtt.tcp.port=1883 +# 高水位 +smqtt.tcp.lowWaterMark=4000000 +# 低水位 +smqtt.tcp.highWaterMark=80000000 +# 开启ssl加密 +smqtt.tcp.ssl=false +# 证书crt smqtt.tcp.ssl.crt = +# 证书key smqtt.tcp.ssl.key = +# 开启日志 +smqtt.tcp.wiretap=false +# boss线程 +smqtt.tcp.bossThreadSize=4 +# work线程 +smqtt.tcp.workThreadSize=8 +# websocket端口 +smqtt.websocket.port=8999 +# websocket开启 +smqtt.websocket.enable=true +# smqtt用户 +smqtt.tcp.username=smqtt +# smqtt密码 +smqtt.tcp.password=smqtt +# 开启http +smqtt.http.enable=true +# 开启http端口 +smqtt.http.port=60000 +# 开启http日志 +smqtt.http.accesslog=true +# 开启ssl +smqtt.http.ssl.enable=false +# smqtt.http.ssl.crt = +# smqtt.http.ssl.key = +# 开启管理后台(必须开启http) +smqtt.http.admin.enable = true +# 管理后台登录用户 +smqtt.http.admin.username=smqtt +# 管理后台登录密码 +smqtt.http.admin.password=smqtt +# 开启集群 +smqtt.cluster.enable=false +# 集群节点地址 +smqtt.cluster.url=127.0.0.1:7771,127.0.0.1:7772 +# 节点端口 +smqtt.cluster.port=7771 +# 节点名称 +smqtt.cluster.node=node-1 + +# 数据库配置(选配) +db.driverClassName=com.mysql.jdbc.Driver +db.url=jdbc:mysql://127.0.0.1:3306/smqtt?characterEncoding=utf-8&useSSL=false&useInformationSchema=true&serverTimezone=UTC +db.username=root +db.password=123 +# 连接池初始化连接数 +db.initialSize=10 +# 连接池中最多支持多少个活动会话 +db.maxActive=300 +# 向连接池中请求连接时,超过maxWait的值后,认为本次请求失败 +db.maxWait=60000 +# 回收空闲连接时,将保证至少有minIdle个连接 +db.minIdle=2 + +# redis配置(选配) +# 单机模式:single 哨兵模式:sentinel 集群模式:cluster +redis.mode=single +# 数据库 +redis.database=0 +# 密码 +redis.password= +# 超时时间 +redis.timeout=3000 +# 最小空闲数 +redis.pool.min.idle=8 +# 连接超时时间(毫秒) +redis.pool.conn.timeout=3000 +# 连接池大小 +redis.pool.size=10 + +# 单机配置 +redis.single.address=127.0.0.1:6379 + +# 集群配置 +redis.cluster.scan.interval=1000 +redis.cluster.nodes=127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005 +redis.cluster.read.mode=SLAVE +redis.cluster.retry.attempts=3 +redis.cluster.slave.connection.pool.size=64 +redis.cluster.master.connection.pool.size=64 +redis.cluster.retry.interval=1500 + +# 哨兵配置 +redis.sentinel.master=mymaster +redis.sentinel.nodes=127.0.0.1:26379,127.0.0.1:26379,127.0.0.1:26379 + ``` ## 不使用配置文件 diff --git a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java index b5611d8d..9d4c9339 100644 --- a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java +++ b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java @@ -30,10 +30,6 @@ public abstract class AbstractStarter { private static final Integer DEFAULT_CLUSTER_PORT = 4333; - - private static final Integer DEFAULT_HTTP_PORT = 12000; - - private static final String DEFAULT_AUTH_USERNAME_PASSWORD = "smqtt"; public static void start(Function function) { @@ -102,9 +98,11 @@ public abstract class AbstractStarter { .highWaterMark(highWaterMark); if (ssl) { - String sslCrt = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_SSL_CRT, function.apply(BootstrapKey.BOOTSTRAP_SSL_CRT))) + String sslCrt = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_SSL_CRT, + function.apply(BootstrapKey.BOOTSTRAP_SSL_CRT))) .map(String::valueOf).orElse(null); - String sslKey = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_SSL_KEY, function.apply(BootstrapKey.BOOTSTRAP_SSL_KEY))) + String sslKey = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_SSL_KEY, + function.apply(BootstrapKey.BOOTSTRAP_SSL_KEY))) .map(String::valueOf).orElse(null); if (sslCrt == null || sslKey == null) { builder.sslContext(null); @@ -113,11 +111,14 @@ public abstract class AbstractStarter { } } if (clusterEnable) { - Integer clusterPort = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_CLUSTER_PORT, function.apply(BootstrapKey.BOOTSTRAP_CLUSTER_PORT))) + Integer clusterPort = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_CLUSTER_PORT, + function.apply(BootstrapKey.BOOTSTRAP_CLUSTER_PORT))) .map(Integer::parseInt).orElse(DEFAULT_CLUSTER_PORT); - String clusterUrl = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_CLUSTER_URL, function.apply(BootstrapKey.BOOTSTRAP_CLUSTER_URL))) + String clusterUrl = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_CLUSTER_URL, + function.apply(BootstrapKey.BOOTSTRAP_CLUSTER_URL))) .map(String::valueOf).orElse(null); - String clusterNode = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_CLUSTER_NODE, function.apply(BootstrapKey.BOOTSTRAP_CLUSTER_NODE))) + String clusterNode = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_CLUSTER_NODE, + function.apply(BootstrapKey.BOOTSTRAP_CLUSTER_NODE))) .map(String::valueOf).orElse(UUID.randomUUID().toString().replaceAll("-", "")); ClusterConfig clusterConfig = ClusterConfig.builder() @@ -129,7 +130,8 @@ public abstract class AbstractStarter { builder.clusterConfig(clusterConfig); } if (isWebsocket) { - Integer websocketPort = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_WEB_SOCKET_PORT, function.apply(BootstrapKey.BOOTSTRAP_WEB_SOCKET_PORT))) + Integer websocketPort = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_WEB_SOCKET_PORT, + function.apply(BootstrapKey.BOOTSTRAP_WEB_SOCKET_PORT))) .map(Integer::parseInt).orElse(DEFAULT_WEBSOCKET_MQTT_PORT); builder.isWebsocket(true) .websocketPort(websocketPort); @@ -137,17 +139,29 @@ public abstract class AbstractStarter { if (httpEnable) { Bootstrap.HttpOptions.HttpOptionsBuilder optionsBuilder = Bootstrap.HttpOptions.builder(); - Boolean accessLog = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_ACCESS_LOG, function.apply(BootstrapKey.BOOTSTRAP_HTTP_ACCESS_LOG))) + Boolean accessLog = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_ACCESS_LOG, + function.apply(BootstrapKey.BOOTSTRAP_HTTP_ACCESS_LOG))) .map(Boolean::parseBoolean).orElse(false); - - Boolean httpSsl = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_SSL_ENABLE, function.apply(BootstrapKey.BOOTSTRAP_HTTP_SSL_ENABLE))) + Boolean httpSsl = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_SSL_ENABLE, + function.apply(BootstrapKey.BOOTSTRAP_HTTP_SSL_ENABLE))) .map(Boolean::parseBoolean).orElse(false); - + Boolean adminEnable = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_ADMIN_ENABLE, + function.apply(BootstrapKey.BOOTSTRAP_HTTP_ADMIN_ENABLE))).map(Boolean::parseBoolean).orElse(false); + optionsBuilder.enableAdmin(adminEnable); + if (adminEnable) { + String httpAdminUsername = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_ADMIN_USERNAME, + function.apply(BootstrapKey.BOOTSTRAP_HTTP_ADMIN_USERNAME))).orElse(null); + String httpAdminPassword = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_ADMIN_PASSWORD, + function.apply(BootstrapKey.BOOTSTRAP_HTTP_ADMIN_PASSWORD))).orElse(null); + optionsBuilder.username(httpAdminUsername).password(httpAdminPassword); + } if (httpSsl) { - String httpSslCrt = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_SSL_CRT, function.apply(BootstrapKey.BOOTSTRAP_HTTP_SSL_CRT))) + String httpSslCrt = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_SSL_CRT, + function.apply(BootstrapKey.BOOTSTRAP_HTTP_SSL_CRT))) .map(String::valueOf).orElse(null); - String httpSslKey = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_SSL_KEY, function.apply(BootstrapKey.BOOTSTRAP_HTTP_SSL_KEY))) + String httpSslKey = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_HTTP_SSL_KEY, + function.apply(BootstrapKey.BOOTSTRAP_HTTP_SSL_KEY))) .map(String::valueOf).orElse(null); if (httpSslKey == null || httpSslCrt == null) { optionsBuilder.sslContext(null); diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java index abede646..24fb33e5 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java @@ -46,10 +46,11 @@ public class BootstrapKey { public final static String BOOTSTRAP_HTTP_SSL_KEY = "smqtt.http.ssl.key"; - public final static String BOOTSTRAP_HTTP_USERNAME = "smqtt.http.username"; + public final static String BOOTSTRAP_HTTP_ADMIN_ENABLE = "smqtt.http.admin.enable"; - public final static String BOOTSTRAP_HTTP_PASSWORD = "smqtt.http.password"; + public final static String BOOTSTRAP_HTTP_ADMIN_USERNAME = "smqtt.http.admin.username"; + public final static String BOOTSTRAP_HTTP_ADMIN_PASSWORD = "smqtt.http.admin.password"; public final static String BOOTSTRAP_CLUSTER_ENABLE = "smqtt.cluster.enable"; diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java b/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java index d9c8dd98..4c51116f 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java @@ -166,7 +166,10 @@ public class Bootstrap { HttpConfiguration httpConfiguration = new HttpConfiguration(); Optional.ofNullable(this.httpOptions.accessLog).ifPresent(httpConfiguration::setAccessLog); Optional.ofNullable(this.httpOptions.sslContext).ifPresent(httpConfiguration::setSslContext); - Optional.ofNullable(this.httpOptions.httpPort).ifPresent(httpConfiguration::setPort); + Optional.ofNullable(this.httpOptions.enableAdmin).ifPresent(httpConfiguration::setEnableAdmin); + Optional.ofNullable(this.httpOptions.username).ifPresent(httpConfiguration::setUsername); + Optional.ofNullable(this.httpOptions.password).ifPresent(httpConfiguration::setPassword); + httpConfiguration.setPort(this.httpOptions.httpPort); return httpConfiguration; } @@ -189,6 +192,12 @@ public class Bootstrap { @Builder.Default private Boolean accessLog = false; + private Boolean enableAdmin; + + private String username; + + private String password; + } public Bootstrap doOnStarted(Consumer started) { -- Gitee From 1e76ac76f6af87cfe77f3c692413efcdcaa1e16f Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Sat, 26 Jun 2021 21:44:03 +0800 Subject: [PATCH 004/482] =?UTF-8?q?doc=20=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8178277e..351b5e45 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,8 @@ docker run -it -v <配置文件路径目录>:/conf -p 1883:1883 -p 1999:1999 1 curl -H "Content-Type: application/json" -X POST -d '{"topic": "test/teus", "qos":2, "retain":true, "message":"我来测试保留消息3" }' "http://localhost:1999/smqtt/publish" ``` - +## 压测文档 +[点这里](https://blog.csdn.net/JingleYe/article/details/118190935) ## wiki地址 -- Gitee From 7499f2a36abc8e4e528e55a31f52727ff77a75bf Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Sun, 27 Jun 2021 21:26:29 +0800 Subject: [PATCH 005/482] doc --- docs/test/1.init.md | 216 ++++++++++++++++++++++++++++++++++++++++++++ docs/test/README.md | 6 ++ 2 files changed, 222 insertions(+) create mode 100644 docs/test/1.init.md create mode 100644 docs/test/README.md diff --git a/docs/test/1.init.md b/docs/test/1.init.md new file mode 100644 index 00000000..af760777 --- /dev/null +++ b/docs/test/1.init.md @@ -0,0 +1,216 @@ +--- +sort: 1 +--- +# 压力测试 +## 背景 +使用`Coolpy7_benchmark`测试,但是**一定要去看下他的测试代码**,基本他的测试逻辑不会是你想要的(作者不要骂我哈,这个工具是相当好,我得提醒下其他小伙伴逻辑的一些问题),但是你可以改动它。 +Coolpy7_benchmark地址:[https://github.com/Coolpy7/coolpy7_benchmark](https://github.com/Coolpy7/coolpy7_benchmark) + +比如这里: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210624170154770.png) +假设你设置的链接1万个,就是`workers`=1万,它会整1万个线程,每个线程给1万个链接发消息,那就是一下子有1亿消息,我设置了2.8万发布者和2.8万订阅者,打崩`smqtt`了(单机,cpu4核内存8G,达到cpu400%),确实是这个测试代码的问题,因为我还去测试了算比较稳定的`emqx`,也是把cpu打到400%,内存都还好。 + +这里可以看我修改后,[ps:为此我学了go的基础语法,基础不难,你也可以。] +地址:[https://gitee.com/leafseelight/coolpy7_benchmark/tree/master/test_all_cus](https://gitee.com/leafseelight/coolpy7_benchmark/tree/master/test_all_cus) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625132536968.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +这边有代码也有编译好的文件,可以自己选,如果你跟我一样是首次接触go,编译自己写的代码没有响应,其实是仓库无法访问,运行:`go env -w GOPROXY=https://goproxy.cn`即可,具体可以参考这篇文章:[点这里](https://blog.csdn.net/weixin_42306122/article/details/107571480) + +## 假定测试目标 +> 假订一个场景,我们这个业务用在单车上,当前业务假定4000台设备,假定设备逐年翻倍增长。 +第1年 4000 +第2年 8000 +第3年 1.6万 +第4年 3.2万 +第5年 6.4万 +预先支撑个3-4年的发展就够了,就是说4万个,应该是足够。 + +那我们就假定有40000个设备,同一时间,设备通常只有1个人在操作,可以不考虑授权的用户,同样假定40000个用户。 + +这样子设备端就有40000个链接,订阅40000个topic,设备被操作一次,会反馈2条消息(运行中,成功),针对高峰期App端12000条/小时,这里应该是24000/条小时(400条/小时,6.6条/秒) + +而APP高峰期假设1小时有25%的用户,就是10000个链接(bugly看的,高峰最高不超过25%,低的时候只有千分之五) +那App就是10000个链接,订阅10000个topic(设备的topic),部分用户可能重复操作,假设20%用户,消息就有12000条/小时,每秒3.3条。 + +综合:高峰期总链接数是,5万个,topic:4万个,消息是36000条/小时(600条/每分钟,10条/秒) + +## 开始测试 +因为我们准备做集群,而集群通过nginx的stream模块来统一提供地址和端口,也就是压力是可以平均分给集群下的机器,我们这边假定要集群部署2台,那单机只要测试上面目标的一半就够了。 + +另外,服务器(mqtt服务器端)参数调优,客户端(测试端)参数调优,参考cp7文档即可。文档地址:[点这里](https://coolpy7.gitbook.io/coolpy7book/kai-shi-shi-yong/dan-ji-qian-wan-ji-lian-jie-ce-shi-shuo-ming) + +不过这里有个重要的东西,**所有云服务都是禁用虚拟ip的**,就是你设置了,实际不能生效。但是,你必须设置最少1个(代码可能有点问题),不然测试代码跑不起来。就是说云服务你也只能当做单机测试,最多支持发起`6.4`万左右的链接。这里还有一个要说明,公司电脑测试可能是行不通的,除非你公司的网络是千兆万兆网络,路由器工业级别,不然因为路由器是有链接数限制的,我们公司的我之前测试2千多个链接,结果所有人都上不了网了。汗! +### 单机测试 +#### 环境 +3台阿里云服务器,其实2台就可以,这里走云服务器内网,可以忽略服务器带宽。 +第1台,4核8G内存,装smqtt +第2台,4核8G内存,装cp7,执行设备端模拟 +第3台,4核8G内存,装cp7,APP端模拟 + +#### 模拟执行情况QOS为0 +第2台,设备端模拟:2万链接,2万订阅者,2万topic,5千发布者,12000条/小时===>12000/5000=2.4条/小时,每个发布者2.4条/小时(算3条,就是20分钟一条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=20000 -receivers=20000 -publishers=5000 -cid=cccccccccccc -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1200000 -clear=true >run.log 2>&1 & + +第3台,APP端模拟:5千链接,5千订阅,5千topic,5千发布者,6000条/小时===>6000/5000=1.2条每小时。每个发布者1.2条/小时(算2条,30分钟1条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=5000 -receivers=5000 -publishers=5000 -cid=app-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-aaaaaaaaa -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1800000 -clear=true >run.log 2>&1 & + +未开始时,`smqtt`服务截图 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625001229161.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +顺便说一下,如果用我的代码测试,要仔细看最后那个日志出现了才表明完整启动。如图: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625133500953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +下面2张图是跑了2.5小时的监控图,可以看出来,还是非常稳定的。 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625130725948.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70)服务器Finallshell看 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625130812475.png) + +> 照比例看,4核8G,最高400%,400/20*2.5万,不说50万,因为还要考虑,消息集中某个时间点的量很大,20-30万,应该是还可以,这个是大致预估。 + +#### 再模拟QOS1 +更改后的测试代码 +设备端: +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=20000 -receivers=20000 -publishers=5000 -cid=cccccccccccc -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=1 -keepalive=120s -s=44 -I=1200000 -clear=true >run.log 2>&1 & + +App端: +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=5000 -receivers=5000 -publishers=5000 -cid=app-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-aaaaaaaaa -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=1 -keepalive=120s -s=44 -I=1800000 -clear=true >run.log 2>&1 & + +运行将近2个小时的情况入下图: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625152133963.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +再看Finashell,如图: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625152311998.png) + +cpu11%-18%之间缓慢变化,从表现看很稳定。 + + +### 单机翻倍测试(模拟集群只剩一台杠压力) +设备端模拟:4万链接,4万订阅者,4万topic,1万发布者,(app操作的2倍)24000条/小时===>每个发布者2.4条/小时(算3条,就是20分钟一条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=40000 -receivers=40000 -publishers=10000 -cid=cccccccccccc -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1200000 -clear=true >run.log 2>&1 & + +APP端模拟:1万链接,1万订阅,1万topic,1万发布者,(假设1/5的用户多按了1次)12000条/小时===>每个发布者1.2条/小时(算2条,30分钟1条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=10000 -receivers=10000 -publishers=10000 -cid=app-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-aaaaaaaaa -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1800000 -clear=true >run.log 2>&1 & + +合计:5万链接,5万订阅者,4万topic,2万发布者,消息中转处理,36000条/小时=600条/分钟,平均每个发布者1.8条/每小时(就是说4万设备,4万用户,高峰期25%的1万用户,一个小时内这些用户都去一次单车的量。) + +运行了9个多小时的情况截图: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625093547261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +按1天再截图一个: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625100918185.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +我们再看看Finalshell +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625094153841.png) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625094229807.png) +依然非常稳定。 + +>cpu在后台观察,是20%-55%之间缓慢变化,内存稳定在1G。由于消息我按20分钟去平铺,总是不能够完全平铺均匀,所以cpu会变化,另外内存方面,作者说用了PooledByteBuf,性能非常高,可以参考:[https://blog.csdn.net/zero__007/article/details/73294783](https://blog.csdn.net/zero__007/article/details/73294783) + +### 集群测试 +#### 环境 +第1台,4核cpu8G内存8M带宽,部署`nginx`与`smqtt`服务`A` +第2台,4核cpu8G内存5M带宽,部署`smqtt`服务`B` +其它2台,部署cp7执行测试 +nginx的stream配置smqtt,weight都为1 +#### 打开全部压力 +设备端模拟:4万链接,4万订阅者,4万topic,1万发布者,(app操作的2倍)24000条/小时===>每个发布者2.4条/小时(算3条,就是20分钟一条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=40000 -receivers=40000 -publishers=10000 -cid=cccccccccccc -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1200000 -clear=true >run.log 2>&1 & + +APP端模拟:1万链接,1万订阅,1万topic,1万发布者,(假设1/5的用户多按了1次)12000条/小时===>每个发布者1.2条/小时(算2条,30分钟1条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=10000 -receivers=10000 -publishers=10000 -cid=app-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-aaaaaaaaa -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1800000 -clear=true >run.log 2>&1 & + +合计:5万链接,5万订阅者,4万topic,2万发布者,消息中转处理,36000条/小时=600条/分钟,平均每个发布者1.8条/每小时(就是说高峰期,25%的人,每个人一个小时去开1次锁。) + +跑了4个小时的截图,`smqtt`服务`A`如下: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625215658295.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625221305429.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625221441385.png) + +分析: +>1、可以看到服务A,连接数达到75k,为什么会这么高呢?因为首先nginx接收了5万的连接,然后分流weight都为1(所以辅的要设置高一些)即这台服务器还连接着分流给服务B的2.5万,总的就是7.5万,而服务B连接只有2.5万; +>2、内存占用了 4.4G,nginx占了2G左右,smqtt占了1G; +>3、带宽这里2.2M/s,说明8M不够了,主要是所有的进出都走这里了,等到了这个承载量,起码要16M。 + + +我们再看服务B,如图 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625221123285.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625221711689.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210625221732522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +分析: +>1、服务B入我们上面所说,2.5万连接; +>2、带宽比较低,不超过450kb/s,主要是它少了nginx的大量承载,其实这里无所谓,因为是内网带宽。服务A不一样,是要接受外网客户端流量,而服务B只接受服务A那台服务器Nginx的分流。 + +### 集群2-优化分流比为主辅1:2,启用qos1 +第1台,Nginx+服务A,weight=1 +第2台,服务B,weight=2 +这里设置1:2,是因为,nginx基本不怎么耗cpu,主要还是连接比不要差距太大,或者可以考虑,单独部署nginx来分流,就不用这么麻烦。另外,也可以考虑去买阿里的负载均衡。 +另外,这里Nginx需要配置连接的最大数量,`worker_rlimit_nofile`与`worker_connections`,我都设置了60万,自行百度了。 + +设备端模拟: +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=40000 -receivers=40000 -publishers=10000 -cid=cccccccccccc -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=1 -keepalive=120s -s=44 -I=1200000 -clear=true >run.log 2>&1 & + +APP端模拟: +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=10000 -receivers=10000 -publishers=10000 -cid=app-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-aaaaaaaaa -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=1 -keepalive=120s -s=44 -I=1800000 -clear=true >run.log 2>&1 & + +开始测试,我们可以预见,服务A连接数为5万+3.4万(分流服务B)=8.4万,服务B连接数为3.4万。我们一会再看下结果。 + +服务A +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627112954915.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627114113449.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627114210631.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +分析: +>1 连接数8.33万,内存950M,cpu20%左右,符合我们的预估,但是连接数比第一种情况更高,感觉weight还是1:1好。 +>2 内存和带宽感消费比较多,等设备数增长后,可以提升主服务器带宽。 + +服务B +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627113556739.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627113653238.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627113618990.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) +分析: +>1 连接数3.3万多,符合预期,但是cpu增多了,在45%左右(消息多的时候,也不都是在这里,还是消息不均,30%-45%,但总的是升高了),主服务器才20%,我觉得在内存都ok的情况下,cpu应该为主。故第一种方案weight为1:1还是更好一些。 + +#### 提高压力,高峰期支撑50%用户/小时 +当前来说cpu400%,可以说是还有非常大的余地,以下测试就当是验证一下是否真有这么多余地。 + +这种情况我们再分析一下: +>1 我们还是假定有40000个设备,同一时间,设备通常只有1个人在操作,可以不考虑授权的用户,同样假定40000个用户。 +2 这样子设备端就有40000个链接,订阅40000个topic,设备被操作一次,会反馈2条消息(运行中,成功),20000个发布者,针对高峰期App端25000条/小时,这里应该是50000条/小时(833条/分钟,14条/秒) +3 而APP高峰期假设1小时有`50%`的用户,就是20000个链接。 +4 那App就是20000个链接,订阅20000个topic(设备的topic),20000个发布者,部分用户可能操作多次,所以消息可能有多,假设25%用户重复操作,那总是就有25000条/小时,每秒7条左右。 + +综合:高峰期总链接数是,6万个,topic:4万个,消息是75000条/小时(1250条/每分钟,21条/秒) + +**环境相同**,weight改1:1,使用qos0,因为我们业务是qos0,各端情况如下: + + +- 设备端模拟:4万链接,4万订阅者,4万topic,2万发布者,50000条/小时===>50000/20000=2.5条/小时,每个发布者2.5条/小时(算3条,就是20分钟一条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=40000 -receivers=40000 -publishers=20000 -cid=cccccccccccc -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1200000 -clear=true >run.log 2>&1 & + +- APP端模拟:2万链接,2万订阅,2万topic,2万发布者,25000条/小时===>25000/20000=1.25条每小时。每个发布者1.25条/小时(算2条,30分钟1条消息),每条消息44字节 +>nohup ./cp7_bench_group_all -url=tcp://smqtt:smqtt@172.18.x.x:1883 -workers=20000 -receivers=20000 -publishers=20000 -cid=app-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-aaaaaaaaa -topic=aaaaaaaaaaaaaaaaaaaaaaaaaaa -qos=0 -keepalive=120s -s=44 -I=1800000 -clear=true >run.log 2>&1 & + +开始测试,跑2个多小时后。 + +服务A截图: +![在这里插入图片描述](https://img-blog.csdnimg.cn/2021062719170852.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/2021062719112639.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627191150197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +分析: +>1 服务A总内存4.6G相比之前4.4G没增加多少。 +>2 cpu为50%左右变化,相比之前32%,压力有增加了一些。 +>3 带宽达到2.9M/s,相比2.2M/s增加了不少,主要消息变多了。 +>4 总连接数据达到9万,因为接收6万还有分流服务B的3万。 + +服务B截图: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627191916605.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627191350426.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20210627191410228.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0ppbmdsZVll,size_16,color_FFFFFF,t_70) + +分析: +>1 一样cpu达到了50%左右,压力稍微增大。 +>2 内存1G跟之前持平。 + +综合来说,cpu稍微压力增加了一些,带宽增加比较多,内存还好。另外,大概咱们估计下,这里2台都占50%来算,我们cpu4核,当cpu达到400%不考虑带宽的话,另外内存又变化不大,可以支撑48万连接,再打个折扣,40万应该可以! + + diff --git a/docs/test/README.md b/docs/test/README.md new file mode 100644 index 00000000..cf514289 --- /dev/null +++ b/docs/test/README.md @@ -0,0 +1,6 @@ +--- +sort: 3 +--- +# 压测报告 + +{% include list.liquid %} \ No newline at end of file -- Gitee From 0b0dc546b9b652eba32eef0c8ac668f5d9d6798e Mon Sep 17 00:00:00 2001 From: "1091927336@qq.com" <1091927336@qq.com> Date: Mon, 28 Jun 2021 10:26:52 +0800 Subject: [PATCH 006/482] =?UTF-8?q?=E6=A0=B9=E6=8D=AEhttpEnable=E8=AE=BE?= =?UTF-8?q?=E7=BD=AEConsumer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/io/github/quickmsg/AbstractStarter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java index 9d4c9339..22933538 100644 --- a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java +++ b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java @@ -177,7 +177,7 @@ public abstract class AbstractStarter { } Bootstrap bootstrap = builder.build(); - bootstrap.doOnStarted(bt -> printUIUrl(bootstrap.getHttpOptions().getHttpPort())).startAwait(); + bootstrap.doOnStarted(httpEnable ? bt -> printUIUrl(bt.getHttpOptions().getHttpPort()) : bt -> log.info("")).startAwait(); } /** -- Gitee From ab437b8da4586eeb9d2494a271a92f8c934a8351 Mon Sep 17 00:00:00 2001 From: luxurong Date: Mon, 28 Jun 2021 11:04:42 +0800 Subject: [PATCH 007/482] =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=8F=B0=E6=89=93?= =?UTF-8?q?=E5=8D=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/github/quickmsg/AbstractStarter.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java index 22933538..013d66ac 100644 --- a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java +++ b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java @@ -177,21 +177,24 @@ public abstract class AbstractStarter { } Bootstrap bootstrap = builder.build(); - bootstrap.doOnStarted(httpEnable ? bt -> printUIUrl(bt.getHttpOptions().getHttpPort()) : bt -> log.info("")).startAwait(); + bootstrap.doOnStarted(AbstractStarter::printUIUrl).startAwait(); } /** * 打印前端访问地址 * - * @param httpPort http端口 + * @param bootstrap 启动类 */ - public static void printUIUrl(Integer httpPort) { - log.info("\n-------------------------------------------------------------\n\t" + - "Application UI is running AccessURLs:\n\t" + - "Http Local url: http://localhost:{}/smqtt/admin" + "\n\t" + - "Http External url: http://{}:{}/smqtt/admin" + "\n" + - "-------------------------------------------------------------" - , httpPort, IPUtils.getIP(), httpPort); + public static void printUIUrl(Bootstrap bootstrap) { + if (bootstrap.getHttpOptions().getEnableAdmin()) { + Integer port = bootstrap.getHttpOptions().getHttpPort(); + log.info("\n-------------------------------------------------------------\n\t" + + "Application UI is running AccessURLs:\n\t" + + "Http Local url: http://localhost:{}/smqtt/admin" + "\n\t" + + "Http External url: http://{}:{}/smqtt/admin" + "\n" + + "-------------------------------------------------------------" + , port, IPUtils.getIP(), port); + } } } -- Gitee From 3243fc29d502c3cfbcfda70ae68bba9b0d26bc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=86=E9=99=86?= <1074455781@qq.com> Date: Thu, 1 Jul 2021 19:26:21 +0800 Subject: [PATCH 008/482] Create SECURITY.md --- SECURITY.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..034e8480 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. -- Gitee From e7a691eb4e11528957e1e78e00301351fd196218 Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Thu, 1 Jul 2021 22:09:02 +0800 Subject: [PATCH 009/482] akka --- smqtt-registry/pom.xml | 1 + smqtt-registry/smqtt-registry-akka/pom.xml | 51 +++++++++++++++++++ .../test/java/io/github/quickmsg/AppTest.java | 20 ++++++++ 3 files changed, 72 insertions(+) create mode 100644 smqtt-registry/smqtt-registry-akka/pom.xml create mode 100644 smqtt-registry/smqtt-registry-akka/src/test/java/io/github/quickmsg/AppTest.java diff --git a/smqtt-registry/pom.xml b/smqtt-registry/pom.xml index e98fd517..b34f878e 100644 --- a/smqtt-registry/pom.xml +++ b/smqtt-registry/pom.xml @@ -11,6 +11,7 @@ pom smqtt-registry-scube + smqtt-registry-akka smqtt-registry smqtt-registry diff --git a/smqtt-registry/smqtt-registry-akka/pom.xml b/smqtt-registry/smqtt-registry-akka/pom.xml new file mode 100644 index 00000000..6dec9dcf --- /dev/null +++ b/smqtt-registry/smqtt-registry-akka/pom.xml @@ -0,0 +1,51 @@ + + + + 4.0.0 + + + smqtt-registry + io.github.quickmsg + 1.0.5 + + smqtt-registry-akka + 1.0.5 + + + UTF-8 + 2.6.14 + + + smqtt-registry-akka + + + junit + junit + 4.11 + test + + + com.typesafe.akka + akka-actor-typed_2.13 + ${akka.version} + + + com.typesafe.akka + akka-cluster-typed_2.13 + ${akka.version} + + + com.typesafe.akka + akka-serialization-jackson_2.13 + ${akka.version} + + + com.typesafe.akka + akka-actor-testkit-typed_2.13 + ${akka.version} + test + + + + diff --git a/smqtt-registry/smqtt-registry-akka/src/test/java/io/github/quickmsg/AppTest.java b/smqtt-registry/smqtt-registry-akka/src/test/java/io/github/quickmsg/AppTest.java new file mode 100644 index 00000000..32a5272f --- /dev/null +++ b/smqtt-registry/smqtt-registry-akka/src/test/java/io/github/quickmsg/AppTest.java @@ -0,0 +1,20 @@ +package io.github.quickmsg; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} -- Gitee From 115d771d303522669b4f2e9c932e4352c8903c3c Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Thu, 1 Jul 2021 23:39:03 +0800 Subject: [PATCH 010/482] add username and clientID --- smqtt-ui/src/pages/dashboard/connections/Connections.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/smqtt-ui/src/pages/dashboard/connections/Connections.vue b/smqtt-ui/src/pages/dashboard/connections/Connections.vue index cc0bba68..75ab3b7a 100644 --- a/smqtt-ui/src/pages/dashboard/connections/Connections.vue +++ b/smqtt-ui/src/pages/dashboard/connections/Connections.vue @@ -28,6 +28,10 @@ const columns = [ width:'100px', customRender: (text, record, index) => index + 1 }, + { + title: '设备id', + dataIndex: 'clientIdentifier' + }, { title: '连接', dataIndex: 'connection', @@ -53,6 +57,10 @@ const columns = [ dataIndex: 'sessionPersistent', customRender: (text, record) => record.sessionPersistent ? "是" : "否" }, + { + title: '用戶名', + dataIndex: 'username' + }, { title: '遗嘱消息', dataIndex: 'will', -- Gitee From 95f824c7ac43aa84432a3c44019705ca41a31d97 Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Fri, 2 Jul 2021 22:36:57 +0800 Subject: [PATCH 011/482] BUG fix --- .../registry/ScubeClusterRegistry.java | 5 +- .../dashboard/connections/Connections.vue | 201 +++++++++--------- 2 files changed, 110 insertions(+), 96 deletions(-) diff --git a/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java b/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java index 0b9ef763..9a785c8c 100644 --- a/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java +++ b/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java @@ -18,6 +18,7 @@ import reactor.core.publisher.Mono; import reactor.core.publisher.Sinks; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -56,7 +57,9 @@ public class ScubeClusterRegistry implements ClusterRegistry { @Override public List getClusterNode() { - return cluster.members().stream().map(this::clusterNode).collect(Collectors.toList()); + return Optional.ofNullable(cluster) + .map(cs -> cs.members().stream().map(this::clusterNode).collect(Collectors.toList())) + .orElse(Collections.emptyList()); } private ClusterNode clusterNode(Member member) { diff --git a/smqtt-ui/src/pages/dashboard/connections/Connections.vue b/smqtt-ui/src/pages/dashboard/connections/Connections.vue index 75ab3b7a..3eb32e51 100644 --- a/smqtt-ui/src/pages/dashboard/connections/Connections.vue +++ b/smqtt-ui/src/pages/dashboard/connections/Connections.vue @@ -1,102 +1,113 @@ \ No newline at end of file -- Gitee From 9500e9285ce13cae3068a9a5567599060dbada44 Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Sat, 3 Jul 2021 14:45:04 +0800 Subject: [PATCH 012/482] BUG fix --- smqtt-registry/smqtt-registry-scube/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smqtt-registry/smqtt-registry-scube/pom.xml b/smqtt-registry/smqtt-registry-scube/pom.xml index 4ef11f2a..40bfdd43 100644 --- a/smqtt-registry/smqtt-registry-scube/pom.xml +++ b/smqtt-registry/smqtt-registry-scube/pom.xml @@ -14,7 +14,7 @@ - 2.6.7 + 2.6.9 2.13.2 1.7.30 -- Gitee From 99408316a5dfa8c42e631c55a668d568eb30c85e Mon Sep 17 00:00:00 2001 From: "1091927336@qq.com" <1091927336@qq.com> Date: Sat, 3 Jul 2021 15:36:43 +0800 Subject: [PATCH 013/482] =?UTF-8?q?=E5=BC=95=E5=85=A5hazelcast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smqtt-bootstrap/pom.xml | 7 +- .../common/bootstrap/BootstrapKey.java | 15 +++ smqtt-registry/pom.xml | 1 + .../smqtt-registry-hazelcast/pom.xml | 33 +++++ .../registry/HazelcastClusterRegistry.java | 127 ++++++++++++++++++ ...ub.quickmsg.common.cluster.ClusterRegistry | 1 + .../src/main/resources/log4j.xml | 27 ++++ smqtt-registry/smqtt-registry-scube/pom.xml | 1 - 8 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 smqtt-registry/smqtt-registry-hazelcast/pom.xml create mode 100644 smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java create mode 100644 smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry create mode 100644 smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml diff --git a/smqtt-bootstrap/pom.xml b/smqtt-bootstrap/pom.xml index b52179e0..e4fd3839 100644 --- a/smqtt-bootstrap/pom.xml +++ b/smqtt-bootstrap/pom.xml @@ -32,10 +32,15 @@ smqtt-core 1.0.5 - + + + smqtt-registry-hazelcast + io.github.quickmsg + 1.0.5 smqtt-ui diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java index 24fb33e5..675b902b 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java @@ -126,4 +126,19 @@ public class BootstrapKey { public static final String MASTER_SLAVE = "MASTER_SLAVE"; } + + /*分割符号*/ + public static class SplitSymbol { + /*逗号*/ + public static final String COMMA = ","; + /*冒号*/ + public static final String COLON = ":"; + } + + /*hazelcast参数*/ + public static class HazelcastParameter{ + /*端口*/ + public static final int PORT = 8000; + } + } \ No newline at end of file diff --git a/smqtt-registry/pom.xml b/smqtt-registry/pom.xml index b34f878e..498bf88b 100644 --- a/smqtt-registry/pom.xml +++ b/smqtt-registry/pom.xml @@ -12,6 +12,7 @@ smqtt-registry-scube smqtt-registry-akka + smqtt-registry-hazelcast smqtt-registry smqtt-registry diff --git a/smqtt-registry/smqtt-registry-hazelcast/pom.xml b/smqtt-registry/smqtt-registry-hazelcast/pom.xml new file mode 100644 index 00000000..8d8307ec --- /dev/null +++ b/smqtt-registry/smqtt-registry-hazelcast/pom.xml @@ -0,0 +1,33 @@ + + + + smqtt-registry + io.github.quickmsg + 1.0.5 + + 4.0.0 + + io.github.quickmsg + smqtt-registry-hazelcast + 1.0.5 + + + 4.2 + + + + + com.hazelcast + hazelcast-all + ${hazelcast.version} + + + io.github.quickmsg + smqtt-common + 1.0.5 + + + + \ No newline at end of file diff --git a/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java b/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java new file mode 100644 index 00000000..b27dd9cc --- /dev/null +++ b/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java @@ -0,0 +1,127 @@ +package io.github.quickmsg.registry; + +import com.hazelcast.cluster.Member; +import com.hazelcast.cluster.MembershipEvent; +import com.hazelcast.cluster.MembershipListener; +import com.hazelcast.config.Config; +import com.hazelcast.config.MapConfig; +import com.hazelcast.config.TcpIpConfig; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import io.github.quickmsg.common.bootstrap.BootstrapKey; +import io.github.quickmsg.common.cluster.ClusterConfig; +import io.github.quickmsg.common.cluster.ClusterMessage; +import io.github.quickmsg.common.cluster.ClusterNode; +import io.github.quickmsg.common.cluster.ClusterRegistry; +import io.github.quickmsg.common.enums.ClusterEvent; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Sinks; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + + +/** + * hazelcast集群注册 + * + * @author zhaopeng + * @date 2021/07/02 + */ +@Slf4j +public class HazelcastClusterRegistry implements ClusterRegistry { + + private Sinks.Many messageMany = Sinks.many().multicast().onBackpressureBuffer(); + + private Sinks.Many eventMany = Sinks.many().multicast().onBackpressureBuffer(); + + /** + * hazelcast实例 + */ + private HazelcastInstance hazelcastInstance; + + @Override + public void registry(ClusterConfig clusterConfig) { + Config cfg = new Config() + .setInstanceName(clusterConfig.getNodeName()) + .addMapConfig(new MapConfig() + .setName(clusterConfig.getNodeName()) + .setBackupCount(1) + .setTimeToLiveSeconds(300) + ); + + TcpIpConfig tcpIpConfig = cfg.getNetworkConfig() + .setPort(BootstrapKey.HazelcastParameter.PORT) + .setPortAutoIncrement(false) + .getJoin() + .getTcpIpConfig() + .setEnabled(false); + + // 循环注册集群IP地址 + Arrays.stream(clusterConfig.getClusterUrl().split(BootstrapKey.SplitSymbol.COMMA)).forEach(record->{ + String ip = record.split(BootstrapKey.SplitSymbol.COLON)[0]; + tcpIpConfig.addMember(ip); + }); + + // 初始化hazelcast实例 + this.hazelcastInstance = Hazelcast.newHazelcastInstance(cfg); + // 注册监听事件 + this.hazelcastInstance.getCluster().addMembershipListener(new ClusterHandler()); + } + + @Override + public Flux handlerClusterMessage() { + return messageMany.asFlux(); + } + + @Override + public List getClusterNode() { + Set hazelcastInstanceSet = Hazelcast.getAllHazelcastInstances(); + return hazelcastInstanceSet.stream().map(this::clusterNode).collect(Collectors.toList()); + } + + private ClusterNode clusterNode(HazelcastInstance hazelcastInstance) { + // TODO host namespace + return ClusterNode.builder() + .alias(hazelcastInstance.getName()) + .host(null) + .port(BootstrapKey.HazelcastParameter.PORT) + .namespace(null) + .build(); + } + + @Override + public Mono spreadMessage(ClusterMessage clusterMessage) { + log.info("cluster send message {} ", clusterMessage); + hazelcastInstance.getMap("customers").put(1, clusterMessage); + return Mono.empty(); + } + + @Override + public Mono shutdown() { + return Mono.fromRunnable(() -> Optional.ofNullable(hazelcastInstance) + .ifPresent(HazelcastInstance::shutdown)); + } + + @Override + public Flux clusterEvent() { + return eventMany.asFlux(); + } + + class ClusterHandler implements MembershipListener { + + @Override + public void memberAdded(MembershipEvent membershipEvent) { + eventMany.tryEmitNext(ClusterEvent.ADDED); + } + + @Override + public void memberRemoved(MembershipEvent membershipEvent) { + eventMany.tryEmitNext(ClusterEvent.REMOVED); + } + } +} diff --git a/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry b/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry new file mode 100644 index 00000000..537aa005 --- /dev/null +++ b/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry @@ -0,0 +1 @@ +io.github.quickmsg.registry.HazelcastClusterRegistry \ No newline at end of file diff --git a/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml b/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml new file mode 100644 index 00000000..28c361a7 --- /dev/null +++ b/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml @@ -0,0 +1,27 @@ + + + + + + %level{length=1} %date{MMdd-HHmm:ss,SSS} %logger{1.} %message [%thread]%n + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/smqtt-registry/smqtt-registry-scube/pom.xml b/smqtt-registry/smqtt-registry-scube/pom.xml index 4ef11f2a..84f7dda1 100644 --- a/smqtt-registry/smqtt-registry-scube/pom.xml +++ b/smqtt-registry/smqtt-registry-scube/pom.xml @@ -51,7 +51,6 @@ junit test - -- Gitee From 076bb1eafacbf394bc88a11e0c1a8bba967df615 Mon Sep 17 00:00:00 2001 From: luxurong <1074455781@qq.com> Date: Sat, 3 Jul 2021 23:34:43 +0800 Subject: [PATCH 014/482] hazelcast --- .../common/cluster/ClusterConfig.java | 7 +++- .../common/cluster/ClusterMessage.java | 2 + .../common/cluster/ClusterRegistry.java | 6 +-- .../{ClusterEvent.java => ClusterStatus.java} | 2 +- .../core/cluster/InJvmClusterRegistry.java | 4 +- .../registry/HazelcastClusterRegistry.java | 39 ++++++++++--------- .../registry/ScubeClusterRegistry.java | 16 ++++---- 7 files changed, 41 insertions(+), 35 deletions(-) rename smqtt-common/src/main/java/io/github/quickmsg/common/enums/{ClusterEvent.java => ClusterStatus.java} (89%) diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java index dce3f868..59cf54f6 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java @@ -3,8 +3,6 @@ package io.github.quickmsg.common.cluster; import lombok.Builder; import lombok.Data; -import java.util.List; - /** * @author luxurong */ @@ -16,13 +14,18 @@ public class ClusterConfig { private Integer port; + private String host; + private String nodeName; private String clusterUrl; + private final String namespace = "smqtt"; + public static ClusterConfig defaultClusterConfig() { return ClusterConfig.builder() .clustered(false) + .host("0.0.0.0") .port(0) .build(); } diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterMessage.java b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterMessage.java index a4a13fba..adcc1796 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterMessage.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterMessage.java @@ -18,4 +18,6 @@ public class ClusterMessage { private boolean retain; private byte[] message; + + } diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterRegistry.java b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterRegistry.java index ea3a9a60..c0490e18 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterRegistry.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterRegistry.java @@ -1,6 +1,6 @@ package io.github.quickmsg.common.cluster; -import io.github.quickmsg.common.enums.ClusterEvent; +import io.github.quickmsg.common.enums.ClusterStatus; import io.github.quickmsg.common.spi.DynamicLoader; import io.github.quickmsg.common.utils.MessageUtils; import io.netty.handler.codec.mqtt.MqttFixedHeader; @@ -39,9 +39,9 @@ public interface ClusterRegistry { /** * 开始订阅Node事件 * - * @return {@link Flux} + * @return {@link Flux< ClusterStatus >} */ - Flux clusterEvent(); + Flux clusterEvent(); /** diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/enums/ClusterEvent.java b/smqtt-common/src/main/java/io/github/quickmsg/common/enums/ClusterStatus.java similarity index 89% rename from smqtt-common/src/main/java/io/github/quickmsg/common/enums/ClusterEvent.java rename to smqtt-common/src/main/java/io/github/quickmsg/common/enums/ClusterStatus.java index a9253d0b..a1997151 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/enums/ClusterEvent.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/enums/ClusterStatus.java @@ -3,7 +3,7 @@ package io.github.quickmsg.common.enums; /** * @author luxurong */ -public enum ClusterEvent { +public enum ClusterStatus { /** * 添加 diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/InJvmClusterRegistry.java b/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/InJvmClusterRegistry.java index f52e3486..d6ae3898 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/InJvmClusterRegistry.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/InJvmClusterRegistry.java @@ -4,7 +4,7 @@ import io.github.quickmsg.common.cluster.ClusterConfig; import io.github.quickmsg.common.cluster.ClusterMessage; import io.github.quickmsg.common.cluster.ClusterNode; import io.github.quickmsg.common.cluster.ClusterRegistry; -import io.github.quickmsg.common.enums.ClusterEvent; +import io.github.quickmsg.common.enums.ClusterStatus; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -28,7 +28,7 @@ public class InJvmClusterRegistry implements ClusterRegistry { } @Override - public Flux clusterEvent() { + public Flux clusterEvent() { return Flux.empty(); } diff --git a/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java b/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java index b27dd9cc..de5e8144 100644 --- a/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java +++ b/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java @@ -1,6 +1,5 @@ package io.github.quickmsg.registry; -import com.hazelcast.cluster.Member; import com.hazelcast.cluster.MembershipEvent; import com.hazelcast.cluster.MembershipListener; import com.hazelcast.config.Config; @@ -8,12 +7,13 @@ import com.hazelcast.config.MapConfig; import com.hazelcast.config.TcpIpConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.topic.ITopic; import io.github.quickmsg.common.bootstrap.BootstrapKey; import io.github.quickmsg.common.cluster.ClusterConfig; import io.github.quickmsg.common.cluster.ClusterMessage; import io.github.quickmsg.common.cluster.ClusterNode; import io.github.quickmsg.common.cluster.ClusterRegistry; -import io.github.quickmsg.common.enums.ClusterEvent; +import io.github.quickmsg.common.enums.ClusterStatus; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -37,7 +37,9 @@ public class HazelcastClusterRegistry implements ClusterRegistry { private Sinks.Many messageMany = Sinks.many().multicast().onBackpressureBuffer(); - private Sinks.Many eventMany = Sinks.many().multicast().onBackpressureBuffer(); + private Sinks.Many eventMany = Sinks.many().multicast().onBackpressureBuffer(); + + private final static String DEFAULT_TOPIC = "cluster"; /** * hazelcast实例 @@ -47,30 +49,28 @@ public class HazelcastClusterRegistry implements ClusterRegistry { @Override public void registry(ClusterConfig clusterConfig) { Config cfg = new Config() - .setInstanceName(clusterConfig.getNodeName()) + .setInstanceName(clusterConfig.getNamespace()) .addMapConfig(new MapConfig() - .setName(clusterConfig.getNodeName()) - .setBackupCount(1) - .setTimeToLiveSeconds(300) + .setName(clusterConfig.getNodeName()) + .setBackupCount(1) + .setTimeToLiveSeconds(300) ); - TcpIpConfig tcpIpConfig = cfg.getNetworkConfig() - .setPort(BootstrapKey.HazelcastParameter.PORT) + .setPublicAddress(clusterConfig.getHost() + ":" + clusterConfig.getPort()) .setPortAutoIncrement(false) .getJoin() .getTcpIpConfig() .setEnabled(false); // 循环注册集群IP地址 - Arrays.stream(clusterConfig.getClusterUrl().split(BootstrapKey.SplitSymbol.COMMA)).forEach(record->{ - String ip = record.split(BootstrapKey.SplitSymbol.COLON)[0]; - tcpIpConfig.addMember(ip); - }); + Arrays.stream(clusterConfig.getClusterUrl().split(BootstrapKey.SplitSymbol.COMMA)).forEach(tcpIpConfig::addMember); // 初始化hazelcast实例 this.hazelcastInstance = Hazelcast.newHazelcastInstance(cfg); // 注册监听事件 this.hazelcastInstance.getCluster().addMembershipListener(new ClusterHandler()); + ITopic topic = hazelcastInstance.getTopic(DEFAULT_TOPIC); + topic.addMessageListener(message -> messageMany.tryEmitNext(message.getMessageObject())); } @Override @@ -96,9 +96,10 @@ public class HazelcastClusterRegistry implements ClusterRegistry { @Override public Mono spreadMessage(ClusterMessage clusterMessage) { - log.info("cluster send message {} ", clusterMessage); - hazelcastInstance.getMap("customers").put(1, clusterMessage); - return Mono.empty(); + return Mono.fromRunnable(() -> { + ITopic topic = hazelcastInstance.getTopic(DEFAULT_TOPIC); + topic.publish(clusterMessage); + }); } @Override @@ -108,7 +109,7 @@ public class HazelcastClusterRegistry implements ClusterRegistry { } @Override - public Flux clusterEvent() { + public Flux clusterEvent() { return eventMany.asFlux(); } @@ -116,12 +117,12 @@ public class HazelcastClusterRegistry implements ClusterRegistry { @Override public void memberAdded(MembershipEvent membershipEvent) { - eventMany.tryEmitNext(ClusterEvent.ADDED); + eventMany.tryEmitNext(ClusterStatus.ADDED); } @Override public void memberRemoved(MembershipEvent membershipEvent) { - eventMany.tryEmitNext(ClusterEvent.REMOVED); + eventMany.tryEmitNext(ClusterStatus.REMOVED); } } } diff --git a/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java b/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java index 9a785c8c..c79ffddd 100644 --- a/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java +++ b/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java @@ -4,7 +4,7 @@ import io.github.quickmsg.common.cluster.ClusterConfig; import io.github.quickmsg.common.cluster.ClusterMessage; import io.github.quickmsg.common.cluster.ClusterNode; import io.github.quickmsg.common.cluster.ClusterRegistry; -import io.github.quickmsg.common.enums.ClusterEvent; +import io.github.quickmsg.common.enums.ClusterStatus; import io.scalecube.cluster.Cluster; import io.scalecube.cluster.ClusterImpl; import io.scalecube.cluster.ClusterMessageHandler; @@ -31,7 +31,7 @@ public class ScubeClusterRegistry implements ClusterRegistry { private Sinks.Many messageMany = Sinks.many().multicast().onBackpressureBuffer(); - private Sinks.Many eventMany = Sinks.many().multicast().onBackpressureBuffer(); + private Sinks.Many eventMany = Sinks.many().multicast().onBackpressureBuffer(); private Cluster cluster; @@ -45,7 +45,7 @@ public class ScubeClusterRegistry implements ClusterRegistry { .getClusterUrl() .split(",")) .map(Address::from) - .collect(Collectors.toList()))) + .collect(Collectors.toList())).namespace(clusterConfig.getNamespace())) .handler(cluster -> new ClusterHandler()) .startAwait(); } @@ -85,7 +85,7 @@ public class ScubeClusterRegistry implements ClusterRegistry { } @Override - public Flux clusterEvent() { + public Flux clusterEvent() { return eventMany.asFlux(); } @@ -110,16 +110,16 @@ public class ScubeClusterRegistry implements ClusterRegistry { log.info("cluster onMembershipEvent {} {}", member, event); switch (event.type()) { case ADDED: - eventMany.tryEmitNext(ClusterEvent.ADDED); + eventMany.tryEmitNext(ClusterStatus.ADDED); break; case LEAVING: - eventMany.tryEmitNext(ClusterEvent.LEAVING); + eventMany.tryEmitNext(ClusterStatus.LEAVING); break; case REMOVED: - eventMany.tryEmitNext(ClusterEvent.REMOVED); + eventMany.tryEmitNext(ClusterStatus.REMOVED); break; case UPDATED: - eventMany.tryEmitNext(ClusterEvent.UPDATED); + eventMany.tryEmitNext(ClusterStatus.UPDATED); break; default: break; -- Gitee From a91a49048cb0dc14573e97a9a333a0e99d07a376 Mon Sep 17 00:00:00 2001 From: "1091927336@qq.com" <1091927336@qq.com> Date: Mon, 5 Jul 2021 11:57:40 +0800 Subject: [PATCH 015/482] =?UTF-8?q?=E7=A7=BB=E9=99=A4hazelcast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smqtt-bootstrap/pom.xml | 7 +- .../common/bootstrap/BootstrapKey.java | 9 +- smqtt-registry/pom.xml | 1 - .../smqtt-registry-hazelcast/pom.xml | 33 ----- .../registry/HazelcastClusterRegistry.java | 128 ------------------ ...ub.quickmsg.common.cluster.ClusterRegistry | 1 - .../src/main/resources/log4j.xml | 27 ---- 7 files changed, 2 insertions(+), 204 deletions(-) delete mode 100644 smqtt-registry/smqtt-registry-hazelcast/pom.xml delete mode 100644 smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java delete mode 100644 smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry delete mode 100644 smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml diff --git a/smqtt-bootstrap/pom.xml b/smqtt-bootstrap/pom.xml index e4fd3839..b52179e0 100644 --- a/smqtt-bootstrap/pom.xml +++ b/smqtt-bootstrap/pom.xml @@ -32,13 +32,8 @@ smqtt-core 1.0.5 - - smqtt-registry-hazelcast + smqtt-registry-scube io.github.quickmsg 1.0.5 diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java index 675b902b..c9aec99c 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java @@ -134,11 +134,4 @@ public class BootstrapKey { /*冒号*/ public static final String COLON = ":"; } - - /*hazelcast参数*/ - public static class HazelcastParameter{ - /*端口*/ - public static final int PORT = 8000; - } - -} \ No newline at end of file + } \ No newline at end of file diff --git a/smqtt-registry/pom.xml b/smqtt-registry/pom.xml index 498bf88b..b34f878e 100644 --- a/smqtt-registry/pom.xml +++ b/smqtt-registry/pom.xml @@ -12,7 +12,6 @@ smqtt-registry-scube smqtt-registry-akka - smqtt-registry-hazelcast smqtt-registry smqtt-registry diff --git a/smqtt-registry/smqtt-registry-hazelcast/pom.xml b/smqtt-registry/smqtt-registry-hazelcast/pom.xml deleted file mode 100644 index 8d8307ec..00000000 --- a/smqtt-registry/smqtt-registry-hazelcast/pom.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - smqtt-registry - io.github.quickmsg - 1.0.5 - - 4.0.0 - - io.github.quickmsg - smqtt-registry-hazelcast - 1.0.5 - - - 4.2 - - - - - com.hazelcast - hazelcast-all - ${hazelcast.version} - - - io.github.quickmsg - smqtt-common - 1.0.5 - - - - \ No newline at end of file diff --git a/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java b/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java deleted file mode 100644 index de5e8144..00000000 --- a/smqtt-registry/smqtt-registry-hazelcast/src/main/java/io/github/quickmsg/registry/HazelcastClusterRegistry.java +++ /dev/null @@ -1,128 +0,0 @@ -package io.github.quickmsg.registry; - -import com.hazelcast.cluster.MembershipEvent; -import com.hazelcast.cluster.MembershipListener; -import com.hazelcast.config.Config; -import com.hazelcast.config.MapConfig; -import com.hazelcast.config.TcpIpConfig; -import com.hazelcast.core.Hazelcast; -import com.hazelcast.core.HazelcastInstance; -import com.hazelcast.topic.ITopic; -import io.github.quickmsg.common.bootstrap.BootstrapKey; -import io.github.quickmsg.common.cluster.ClusterConfig; -import io.github.quickmsg.common.cluster.ClusterMessage; -import io.github.quickmsg.common.cluster.ClusterNode; -import io.github.quickmsg.common.cluster.ClusterRegistry; -import io.github.quickmsg.common.enums.ClusterStatus; -import lombok.extern.slf4j.Slf4j; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - - -/** - * hazelcast集群注册 - * - * @author zhaopeng - * @date 2021/07/02 - */ -@Slf4j -public class HazelcastClusterRegistry implements ClusterRegistry { - - private Sinks.Many messageMany = Sinks.many().multicast().onBackpressureBuffer(); - - private Sinks.Many eventMany = Sinks.many().multicast().onBackpressureBuffer(); - - private final static String DEFAULT_TOPIC = "cluster"; - - /** - * hazelcast实例 - */ - private HazelcastInstance hazelcastInstance; - - @Override - public void registry(ClusterConfig clusterConfig) { - Config cfg = new Config() - .setInstanceName(clusterConfig.getNamespace()) - .addMapConfig(new MapConfig() - .setName(clusterConfig.getNodeName()) - .setBackupCount(1) - .setTimeToLiveSeconds(300) - ); - TcpIpConfig tcpIpConfig = cfg.getNetworkConfig() - .setPublicAddress(clusterConfig.getHost() + ":" + clusterConfig.getPort()) - .setPortAutoIncrement(false) - .getJoin() - .getTcpIpConfig() - .setEnabled(false); - - // 循环注册集群IP地址 - Arrays.stream(clusterConfig.getClusterUrl().split(BootstrapKey.SplitSymbol.COMMA)).forEach(tcpIpConfig::addMember); - - // 初始化hazelcast实例 - this.hazelcastInstance = Hazelcast.newHazelcastInstance(cfg); - // 注册监听事件 - this.hazelcastInstance.getCluster().addMembershipListener(new ClusterHandler()); - ITopic topic = hazelcastInstance.getTopic(DEFAULT_TOPIC); - topic.addMessageListener(message -> messageMany.tryEmitNext(message.getMessageObject())); - } - - @Override - public Flux handlerClusterMessage() { - return messageMany.asFlux(); - } - - @Override - public List getClusterNode() { - Set hazelcastInstanceSet = Hazelcast.getAllHazelcastInstances(); - return hazelcastInstanceSet.stream().map(this::clusterNode).collect(Collectors.toList()); - } - - private ClusterNode clusterNode(HazelcastInstance hazelcastInstance) { - // TODO host namespace - return ClusterNode.builder() - .alias(hazelcastInstance.getName()) - .host(null) - .port(BootstrapKey.HazelcastParameter.PORT) - .namespace(null) - .build(); - } - - @Override - public Mono spreadMessage(ClusterMessage clusterMessage) { - return Mono.fromRunnable(() -> { - ITopic topic = hazelcastInstance.getTopic(DEFAULT_TOPIC); - topic.publish(clusterMessage); - }); - } - - @Override - public Mono shutdown() { - return Mono.fromRunnable(() -> Optional.ofNullable(hazelcastInstance) - .ifPresent(HazelcastInstance::shutdown)); - } - - @Override - public Flux clusterEvent() { - return eventMany.asFlux(); - } - - class ClusterHandler implements MembershipListener { - - @Override - public void memberAdded(MembershipEvent membershipEvent) { - eventMany.tryEmitNext(ClusterStatus.ADDED); - } - - @Override - public void memberRemoved(MembershipEvent membershipEvent) { - eventMany.tryEmitNext(ClusterStatus.REMOVED); - } - } -} diff --git a/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry b/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry deleted file mode 100644 index 537aa005..00000000 --- a/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/META-INF/services/io.github.quickmsg.common.cluster.ClusterRegistry +++ /dev/null @@ -1 +0,0 @@ -io.github.quickmsg.registry.HazelcastClusterRegistry \ No newline at end of file diff --git a/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml b/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml deleted file mode 100644 index 28c361a7..00000000 --- a/smqtt-registry/smqtt-registry-hazelcast/src/main/resources/log4j.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - %level{length=1} %date{MMdd-HHmm:ss,SSS} %logger{1.} %message [%thread]%n - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file -- Gitee From cb503b2ad166b26f37b32c6c5c25632602964212 Mon Sep 17 00:00:00 2001 From: luxurong Date: Mon, 5 Jul 2021 16:50:15 +0800 Subject: [PATCH 016/482] counter --- .../core/counter/SideWindowCounter.java | 34 ++++++++++++---- .../quickmsg/core/counter/WindowCounter.java | 11 +++++- .../core/inteceptor/CounterInterceptor.java | 31 --------------- .../quickmsg/core/metric/MetricManager.java | 31 +++++++++++++-- .../core/mqtt/MetricChannelHandler.java | 39 +++++++++++++++++++ .../quickmsg/core/mqtt/MqttReceiver.java | 2 +- .../core/protocol/ConnectProtocol.java | 6 +++ ...ub.quickmsg.common.interceptor.Interceptor | 1 - 8 files changed, 108 insertions(+), 47 deletions(-) delete mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/inteceptor/CounterInterceptor.java create mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/SideWindowCounter.java b/smqtt-core/src/main/java/io/github/quickmsg/core/counter/SideWindowCounter.java index e1834e26..0c9d0c13 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/SideWindowCounter.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/counter/SideWindowCounter.java @@ -6,6 +6,7 @@ import reactor.core.publisher.Sinks; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; +import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; @@ -15,36 +16,53 @@ import java.util.concurrent.atomic.LongAdder; @Slf4j public class SideWindowCounter implements WindowCounter, Runnable { - private LongAdder longAdder = new LongAdder(); + private LongAdder allAdder = new LongAdder(); + + private LongAdder windowAdder = new LongAdder(); private Sinks.Many sinks = Sinks.many().multicast().onBackpressureBuffer(); + private String windowName; + public SideWindowCounter(Integer time, TimeUnit timeUnit, String threadName) { - this(+time, timeUnit, Schedulers.newSingle(threadName)); + this(time, timeUnit, Schedulers.newSingle(threadName)); + windowName = threadName; } public SideWindowCounter(Integer time, TimeUnit timeUnit, Scheduler scheduler) { scheduler.schedulePeriodically(this, 0L, time, timeUnit); scheduler.start(); + if (log.isDebugEnabled()) { + Flux.interval(Duration.ofSeconds(10)) + .subscribe(interval -> { + log.debug("request window {} size {}", windowName,windowAdder.sum()); + log.debug("request window {} size {}",windowName, allAdder.sum()); + }); + } } @Override public void run() { - long sum = longAdder.sum(); - log.info("request buffer size {}", sum); + long sum = windowAdder.sum(); sinks.tryEmitNext(sum); - longAdder = new LongAdder(); + allAdder.add(sum); + windowAdder = new LongAdder(); } @Override - public Long count() { - return longAdder.sum(); + public Long intervalCount() { + return windowAdder.sum(); } @Override public void apply(Integer request) { - longAdder.add(request); + windowAdder.add(request); + } + + @Override + public Long allCount() { + return allAdder.sum(); } @Override diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/WindowCounter.java b/smqtt-core/src/main/java/io/github/quickmsg/core/counter/WindowCounter.java index 4c69b2d6..ce152e06 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/WindowCounter.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/counter/WindowCounter.java @@ -7,12 +7,19 @@ import reactor.core.publisher.Flux; */ public interface WindowCounter { + /** + * 周期汇总 + * + * @return count + */ + Long intervalCount(); + /** * 汇总 * - * @return {@link Flux } + * @return count */ - Long count(); + Long allCount(); /** diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/inteceptor/CounterInterceptor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/inteceptor/CounterInterceptor.java deleted file mode 100644 index ddd57939..00000000 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/inteceptor/CounterInterceptor.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.github.quickmsg.core.inteceptor; - -import io.github.quickmsg.common.interceptor.Interceptor; -import io.github.quickmsg.common.interceptor.Invocation; -import io.github.quickmsg.core.metric.MetricManager; -import io.netty.handler.codec.mqtt.MqttMessage; -import io.netty.handler.codec.mqtt.MqttPublishMessage; -import lombok.extern.slf4j.Slf4j; - -/** - * @author luxurong - */ -@Slf4j -public class CounterInterceptor implements Interceptor { - - - @Override - public Object intercept(Invocation invocation) { - Object[] args = invocation.getArgs(); - MqttMessage mqttMessage = (MqttMessage) args[1]; - if (mqttMessage instanceof MqttPublishMessage) { - MetricManager.registryMetric((MqttPublishMessage) mqttMessage); - } - return invocation.proceed(); - } - - @Override - public int sort() { - return 999; - } -} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java b/smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java index 03ce43ac..21c9ad2e 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java @@ -2,10 +2,11 @@ package io.github.quickmsg.core.metric; import io.github.quickmsg.core.counter.SideWindowCounter; import io.github.quickmsg.core.counter.WindowCounter; -import io.netty.handler.codec.mqtt.MqttPublishMessage; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** @@ -15,10 +16,32 @@ import java.util.concurrent.TimeUnit; @Slf4j public class MetricManager { - private static WindowCounter transportBufferSize = new SideWindowCounter(1, TimeUnit.MINUTES, "TRANSPORT-BUFFER-SIZE"); - public static void registryMetric(MqttPublishMessage mqttPublishMessage) { - transportBufferSize.apply(mqttPublishMessage.payload().readableBytes()); + private static WindowCounter readAllBufferSize = new SideWindowCounter(1, TimeUnit.HOURS, "TRANSPORT-READ-BUFFER-SIZE"); + + private static WindowCounter writeAllBufferSize = new SideWindowCounter(1, TimeUnit.HOURS, "TRANSPORT-WRITE-BUFFER-SIZE"); + + private static WindowCounter connectCounter = new SideWindowCounter(1, TimeUnit.HOURS, "CONNECT-SIZE"); + + + private static Map transportBufferSize = new ConcurrentHashMap<>(); + + public static void recordDataSend(Integer bufferSize) { + writeAllBufferSize.apply(bufferSize); + } + + public static void recordDataReceived(Integer bufferSize) { + readAllBufferSize.apply(bufferSize); + } + + public static void recordConnect(Integer size) { + connectCounter.apply(size); } + public static void acceptMetric(String topic, Integer bufferSize) { + WindowCounter windowCounter = transportBufferSize.computeIfAbsent(topic, tc -> new SideWindowCounter(1, TimeUnit.MINUTES, tc)); + windowCounter.apply(bufferSize); + } + + } diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java new file mode 100644 index 00000000..7d1d7f40 --- /dev/null +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java @@ -0,0 +1,39 @@ +package io.github.quickmsg.core.mqtt; + +import io.github.quickmsg.core.metric.MetricManager; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; + +/** + * @author luxurong + */ +public class MetricChannelHandler extends ChannelDuplexHandler { + + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (msg instanceof ByteBuf) { + ByteBuf buffer = (ByteBuf) msg; + if (buffer.readableBytes() > 0) { + MetricManager.recordDataSend(buffer.readableBytes()); + } + } + super.write(ctx, msg, promise); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof ByteBuf) { + ByteBuf buffer = (ByteBuf) msg; + if (buffer.readableBytes() > 0) { + MetricManager.recordDataReceived(buffer.readableBytes()); + } + } + super.channelRead(ctx, msg); + } + + + +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiver.java b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiver.java index e5b210f9..6fe3e86f 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiver.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiver.java @@ -41,7 +41,7 @@ public class MqttReceiver extends AbstractSslHandler implements Receiver { .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) .runOn(receiveContext.getLoopResources()) .doOnConnection(connection -> { - connection.addHandler(new MqttDecoder()).addHandler(MqttEncoder.INSTANCE); + connection.addHandler(MqttEncoder.INSTANCE).addHandler(new MetricChannelHandler()).addHandler(new MqttDecoder()); receiveContext.apply(MqttChannel.init(connection)); }); } diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java b/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java index 68349816..2ce73a5b 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java @@ -9,6 +9,7 @@ import io.github.quickmsg.common.message.MessageRegistry; import io.github.quickmsg.common.message.MqttMessageBuilder; import io.github.quickmsg.common.protocol.Protocol; import io.github.quickmsg.common.topic.TopicRegistry; +import io.github.quickmsg.core.metric.MetricManager; import io.github.quickmsg.core.mqtt.MqttReceiveContext; import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.*; @@ -107,6 +108,11 @@ public class ConnectProtocol implements Protocol { channelRegistry.registry(clientIdentifier, mqttChannel); + MetricManager.recordConnect(1); + + mqttChannel.registryClose(mqttChannel1 -> { + MetricManager.recordConnect(-1); + }); return mqttChannel.write(MqttMessageBuilder.buildConnectAck(MqttConnectReturnCode.CONNECTION_ACCEPTED), false); } else { diff --git a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.interceptor.Interceptor b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.interceptor.Interceptor index a8201e8a..e69de29b 100644 --- a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.interceptor.Interceptor +++ b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.interceptor.Interceptor @@ -1 +0,0 @@ -io.github.quickmsg.core.inteceptor.CounterInterceptor \ No newline at end of file -- Gitee From e92f32631e2ff9ed66867ffa6a6ba6e35efa8b28 Mon Sep 17 00:00:00 2001 From: luxurong Date: Mon, 5 Jul 2021 16:50:45 +0800 Subject: [PATCH 017/482] counter --- smqtt-registry/smqtt-registry-scube/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smqtt-registry/smqtt-registry-scube/pom.xml b/smqtt-registry/smqtt-registry-scube/pom.xml index 637c971b..84f7dda1 100644 --- a/smqtt-registry/smqtt-registry-scube/pom.xml +++ b/smqtt-registry/smqtt-registry-scube/pom.xml @@ -14,7 +14,7 @@ - 2.6.9 + 2.6.7 2.13.2 1.7.30 -- Gitee From 0f8b8b3beb357755db3433e9451b46e5156c36bd Mon Sep 17 00:00:00 2001 From: luxurong Date: Mon, 5 Jul 2021 19:51:19 +0800 Subject: [PATCH 018/482] exclude node --- smqtt-bootstrap/src/test/java/ClusterNode1.java | 2 +- .../github/quickmsg/common/cluster/ClusterConfig.java | 3 ++- .../quickmsg/registry/ScubeClusterRegistry.java | 11 +++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/smqtt-bootstrap/src/test/java/ClusterNode1.java b/smqtt-bootstrap/src/test/java/ClusterNode1.java index 2a7206a0..75dbd3ee 100644 --- a/smqtt-bootstrap/src/test/java/ClusterNode1.java +++ b/smqtt-bootstrap/src/test/java/ClusterNode1.java @@ -25,7 +25,7 @@ public class ClusterNode1 { .httpOptions(Bootstrap.HttpOptions.builder().ssl(false).accessLog(true).build()) .clusterConfig( ClusterConfig.builder() - .clustered(false) + .clustered(true) .port(7773) .nodeName("node-2") .clusterUrl("127.0.0.1:7771,127.0.0.1:7772") diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java index 59cf54f6..6adba4c6 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/cluster/ClusterConfig.java @@ -20,7 +20,8 @@ public class ClusterConfig { private String clusterUrl; - private final String namespace = "smqtt"; + @Builder.Default + private String namespace = "smqtt"; public static ClusterConfig defaultClusterConfig() { return ClusterConfig.builder() diff --git a/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java b/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java index c79ffddd..7a8db8f5 100644 --- a/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java +++ b/smqtt-registry/smqtt-registry-scube/src/main/java/io/github/quickmsg/registry/ScubeClusterRegistry.java @@ -74,8 +74,15 @@ public class ScubeClusterRegistry implements ClusterRegistry { @Override public Mono spreadMessage(ClusterMessage clusterMessage) { log.info("cluster send message {} ", clusterMessage); - return Optional.ofNullable(cluster) - .map(cs -> cs.spreadGossip(Message.withData(clusterMessage).build()).then()).orElse(Mono.empty()); + return Mono.when( + cluster.otherMembers() + .stream() + .map(member -> + Optional.ofNullable(cluster) + .map(cs -> + cs.send(member, Message.withData(clusterMessage).build()).then() + ).orElse(Mono.empty())) + .collect(Collectors.toList())); } @Override -- Gitee From d59063eb68718e6de69b5e5286519537f1bd67bd Mon Sep 17 00:00:00 2001 From: luxurong Date: Mon, 5 Jul 2021 20:40:10 +0800 Subject: [PATCH 019/482] message outer transport --- .../common/message/RecipientRegistry.java | 22 +++++++++++++++++++ .../quickmsg/core/cluster/ClusterSender.java | 11 ++++++++-- .../core/mqtt/AbstractReceiveContext.java | 16 ++++++++++---- .../core/mqtt/MqttReceiveContext.java | 6 +---- .../{ => spi}/DefaultChannelRegistry.java | 2 +- .../{ => spi}/DefaultMessageRegistry.java | 2 +- .../{ => spi}/DefaultProtocolAdaptor.java | 2 +- .../core/{ => spi}/DefaultTopicRegistry.java | 2 +- .../core/spi/EmptyRecipientRegistry.java | 16 ++++++++++++++ 9 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java rename smqtt-core/src/main/java/io/github/quickmsg/core/{ => spi}/DefaultChannelRegistry.java (97%) rename smqtt-core/src/main/java/io/github/quickmsg/core/{ => spi}/DefaultMessageRegistry.java (97%) rename smqtt-core/src/main/java/io/github/quickmsg/core/{ => spi}/DefaultProtocolAdaptor.java (98%) rename smqtt-core/src/main/java/io/github/quickmsg/core/{ => spi}/DefaultTopicRegistry.java (98%) create mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java b/smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java new file mode 100644 index 00000000..305e462a --- /dev/null +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java @@ -0,0 +1,22 @@ +package io.github.quickmsg.common.message; + +import io.github.quickmsg.common.spi.DynamicLoader; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttPublishMessage; + +/** + * @author luxurong + */ +public interface RecipientRegistry { + + RecipientRegistry INSTANCE = DynamicLoader.findFirst(RecipientRegistry.class).orElse(null); + + + /** + * message + * + * @param publishMessage {@link MqttPublishMessage} + */ + void accept(MqttPublishMessage publishMessage); + +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java b/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java index 544a55f5..90aaceb4 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java @@ -1,5 +1,6 @@ package io.github.quickmsg.core.cluster; +import io.github.quickmsg.common.message.RecipientRegistry; import io.github.quickmsg.core.mqtt.MqttReceiveContext; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage; @@ -16,15 +17,21 @@ public class ClusterSender implements Function { private final Scheduler scheduler; - public ClusterSender(Scheduler scheduler, MqttReceiveContext mqttReceiveContext) { + private final RecipientRegistry recipientRegistry; + + public ClusterSender(Scheduler scheduler, MqttReceiveContext mqttReceiveContext, RecipientRegistry recipientRegistry) { this.scheduler = scheduler; this.mqttReceiveContext = mqttReceiveContext; + this.recipientRegistry = recipientRegistry; } @Override public MqttMessage apply(MqttMessage mqttMessage) { if (mqttMessage instanceof MqttPublishMessage) { - ((MqttPublishMessage) mqttMessage).retain(); + MqttPublishMessage publishMessage = (MqttPublishMessage) mqttMessage; + publishMessage.payload().resetReaderIndex(); + recipientRegistry.accept(publishMessage); + publishMessage.retain(); if (mqttReceiveContext.getConfiguration().getClusterConfig().getClustered()) { mqttReceiveContext.getClusterRegistry().spreadPublishMessage(((MqttPublishMessage) mqttMessage).copy()).subscribeOn(scheduler).subscribe(); } diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java index cad0f458..cee01a54 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java @@ -7,13 +7,11 @@ import io.github.quickmsg.common.config.AbstractConfiguration; import io.github.quickmsg.common.config.Configuration; import io.github.quickmsg.common.context.ReceiveContext; import io.github.quickmsg.common.message.MessageRegistry; +import io.github.quickmsg.common.message.RecipientRegistry; import io.github.quickmsg.common.protocol.ProtocolAdaptor; import io.github.quickmsg.common.topic.TopicRegistry; import io.github.quickmsg.common.transport.Transport; -import io.github.quickmsg.core.DefaultChannelRegistry; -import io.github.quickmsg.core.DefaultMessageRegistry; -import io.github.quickmsg.core.DefaultProtocolAdaptor; -import io.github.quickmsg.core.DefaultTopicRegistry; +import io.github.quickmsg.core.spi.*; import io.github.quickmsg.core.cluster.InJvmClusterRegistry; import lombok.Getter; import lombok.Setter; @@ -48,9 +46,12 @@ public abstract class AbstractReceiveContext implements private final ClusterRegistry clusterRegistry; + private final RecipientRegistry recipientRegistry; + public AbstractReceiveContext(T configuration, Transport transport) { this.configuration = configuration; this.transport = transport; + this.recipientRegistry = recipientRegistry(); this.protocolAdaptor = protocolAdaptor(); this.channelRegistry = channelRegistry(); this.topicRegistry = topicRegistry(); @@ -58,11 +59,18 @@ public abstract class AbstractReceiveContext implements this.messageRegistry = messageRegistry(); this.clusterRegistry = clusterRegistry(); this.passwordAuthentication = basicAuthentication(); + AbstractConfiguration abstractConfiguration = castConfiguration(configuration); this.channelRegistry.startUp(abstractConfiguration.getEnvContext()); this.messageRegistry.startUp(abstractConfiguration.getEnvContext()); } + + private RecipientRegistry recipientRegistry() { + return Optional.ofNullable(RecipientRegistry.INSTANCE) + .orElse(new EmptyRecipientRegistry()); + } + private MessageRegistry messageRegistry() { return Optional.ofNullable(MessageRegistry.INSTANCE) .orElse(new DefaultMessageRegistry()); diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java index 6ceb69b1..87dbeb9b 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java @@ -7,11 +7,7 @@ import io.github.quickmsg.core.cluster.ClusterSender; import io.netty.handler.codec.mqtt.MqttMessage; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import reactor.netty.Connection; - -import java.time.Duration; /** * @author luxurong @@ -27,7 +23,7 @@ public class MqttReceiveContext extends AbstractReceiveContext transport) { super(configuration, transport); - this.clusterSender = new ClusterSender(Schedulers.newParallel("cluster-transport"), this); + this.clusterSender = new ClusterSender(Schedulers.newParallel("cluster-transport"), this,this.getRecipientRegistry()); this.clusterReceiver = new ClusterReceiver(this); clusterReceiver.registry(); } diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultChannelRegistry.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultChannelRegistry.java similarity index 97% rename from smqtt-core/src/main/java/io/github/quickmsg/core/DefaultChannelRegistry.java rename to smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultChannelRegistry.java index f9a9e154..57bd349d 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultChannelRegistry.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultChannelRegistry.java @@ -1,4 +1,4 @@ -package io.github.quickmsg.core; +package io.github.quickmsg.core.spi; import io.github.quickmsg.common.channel.ChannelRegistry; import io.github.quickmsg.common.channel.MqttChannel; diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultMessageRegistry.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultMessageRegistry.java similarity index 97% rename from smqtt-core/src/main/java/io/github/quickmsg/core/DefaultMessageRegistry.java rename to smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultMessageRegistry.java index 2d0c4cb1..256d8170 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultMessageRegistry.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultMessageRegistry.java @@ -1,4 +1,4 @@ -package io.github.quickmsg.core; +package io.github.quickmsg.core.spi; import io.github.quickmsg.common.message.MessageRegistry; import io.github.quickmsg.common.message.RetainMessage; diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultProtocolAdaptor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java similarity index 98% rename from smqtt-core/src/main/java/io/github/quickmsg/core/DefaultProtocolAdaptor.java rename to smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java index 59057f0e..9f5e38ba 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultProtocolAdaptor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java @@ -1,4 +1,4 @@ -package io.github.quickmsg.core; +package io.github.quickmsg.core.spi; import io.github.quickmsg.common.channel.MqttChannel; import io.github.quickmsg.common.config.Configuration; diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultTopicRegistry.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultTopicRegistry.java similarity index 98% rename from smqtt-core/src/main/java/io/github/quickmsg/core/DefaultTopicRegistry.java rename to smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultTopicRegistry.java index b373fb62..d8a0cee8 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/DefaultTopicRegistry.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultTopicRegistry.java @@ -1,4 +1,4 @@ -package io.github.quickmsg.core; +package io.github.quickmsg.core.spi; import io.github.quickmsg.common.channel.MqttChannel; import io.github.quickmsg.common.message.SubscribeChannelContext; diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java new file mode 100644 index 00000000..b73e01ef --- /dev/null +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java @@ -0,0 +1,16 @@ +package io.github.quickmsg.core.spi; + +import io.github.quickmsg.common.message.RecipientRegistry; +import io.netty.handler.codec.mqtt.MqttPublishMessage; + +/** + * @author luxurong + */ +public class EmptyRecipientRegistry implements RecipientRegistry { + + + @Override + public void accept(MqttPublishMessage message) { + + } +} -- Gitee From 46345ba958ec86a4f6e647350a396c3ca5ee8f8e Mon Sep 17 00:00:00 2001 From: luxurong Date: Mon, 5 Jul 2021 21:01:16 +0800 Subject: [PATCH 020/482] message outer transport --- .../quickmsg/common/context/ReceiveContext.java | 10 ++++++++++ .../common/message/RecipientRegistry.java | 12 +++++++++++- .../quickmsg/core/protocol/ConnectProtocol.java | 15 ++++++++++++--- .../quickmsg/core/spi/EmptyRecipientRegistry.java | 9 +++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/context/ReceiveContext.java b/smqtt-common/src/main/java/io/github/quickmsg/common/context/ReceiveContext.java index be52abb1..2d4ceab1 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/context/ReceiveContext.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/context/ReceiveContext.java @@ -5,6 +5,7 @@ import io.github.quickmsg.common.channel.MqttChannel; import io.github.quickmsg.common.cluster.ClusterRegistry; import io.github.quickmsg.common.config.Configuration; import io.github.quickmsg.common.message.MessageRegistry; +import io.github.quickmsg.common.message.RecipientRegistry; import io.github.quickmsg.common.protocol.ProtocolAdaptor; import io.github.quickmsg.common.topic.TopicRegistry; import io.netty.handler.codec.mqtt.MqttMessage; @@ -57,6 +58,15 @@ public interface ReceiveContext extends BiConsumer { MESSAGE_TYPE_LIST.add(MqttMessageType.CONNECT); } + private static void accept(MqttChannel mqttChannel1) { + MetricManager.recordConnect(-1); + } + @Override public Mono parseProtocol(MqttConnectMessage message, MqttChannel mqttChannel, ContextView contextView) { MqttReceiveContext mqttReceiveContext = (MqttReceiveContext) contextView.get(ReceiveContext.class); + RecipientRegistry recipientRegistry = mqttReceiveContext.getRecipientRegistry(); MqttConnectVariableHeader mqttConnectVariableHeader = message.variableHeader(); MqttConnectPayload mqttConnectPayload = message.payload(); String clientIdentifier = mqttConnectPayload.clientIdentifier(); @@ -110,9 +116,12 @@ public class ConnectProtocol implements Protocol { MetricManager.recordConnect(1); - mqttChannel.registryClose(mqttChannel1 -> { - MetricManager.recordConnect(-1); - }); + mqttChannel.registryClose(ConnectProtocol::accept); + + recipientRegistry.channelStatus(mqttChannel, mqttChannel.getStatus()); + + mqttChannel.registryClose(mqttChannel1 -> recipientRegistry.channelStatus(mqttChannel1, ChannelStatus.OFFLINE)); + return mqttChannel.write(MqttMessageBuilder.buildConnectAck(MqttConnectReturnCode.CONNECTION_ACCEPTED), false); } else { diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java index b73e01ef..c4d4eb57 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java @@ -1,5 +1,7 @@ package io.github.quickmsg.core.spi; +import io.github.quickmsg.common.channel.MqttChannel; +import io.github.quickmsg.common.enums.ChannelStatus; import io.github.quickmsg.common.message.RecipientRegistry; import io.netty.handler.codec.mqtt.MqttPublishMessage; @@ -13,4 +15,11 @@ public class EmptyRecipientRegistry implements RecipientRegistry { public void accept(MqttPublishMessage message) { } + + @Override + public void channelStatus(MqttChannel mqttChannel, ChannelStatus channelStatus) { + + } + + } -- Gitee From 2988aad477a1ac4a44af606e5a90ccb734c59f9d Mon Sep 17 00:00:00 2001 From: luxurong Date: Tue, 6 Jul 2021 11:31:54 +0800 Subject: [PATCH 021/482] message outer transport --- .../common/message/RecipientRegistry.java | 5 +++-- .../quickmsg/core/cluster/ClusterSender.java | 17 +++++++++-------- .../core/mqtt/AbstractReceiveContext.java | 2 ++ .../quickmsg/core/mqtt/MqttReceiveContext.java | 4 +++- .../core/spi/EmptyRecipientRegistry.java | 4 +--- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java b/smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java index 412d2dfe..32a0e09f 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/message/RecipientRegistry.java @@ -14,11 +14,12 @@ public interface RecipientRegistry { /** - * message + * 全局消息处理 * + * @param mqttChannel {@link MqttChannel} * @param publishMessage {@link MqttPublishMessage} */ - void accept(MqttPublishMessage publishMessage); + void accept(MqttChannel mqttChannel, MqttPublishMessage publishMessage); /** diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java b/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java index 90aaceb4..0376c8d1 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/cluster/ClusterSender.java @@ -1,17 +1,18 @@ package io.github.quickmsg.core.cluster; +import io.github.quickmsg.common.channel.MqttChannel; import io.github.quickmsg.common.message.RecipientRegistry; import io.github.quickmsg.core.mqtt.MqttReceiveContext; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage; import reactor.core.scheduler.Scheduler; -import java.util.function.Function; +import java.util.function.BiFunction; /** * @author luxurong */ -public class ClusterSender implements Function { +public class ClusterSender implements BiFunction { private final MqttReceiveContext mqttReceiveContext; @@ -26,17 +27,17 @@ public class ClusterSender implements Function { } @Override - public MqttMessage apply(MqttMessage mqttMessage) { - if (mqttMessage instanceof MqttPublishMessage) { - MqttPublishMessage publishMessage = (MqttPublishMessage) mqttMessage; + public MqttMessage apply(MqttChannel mqttChannel, MqttMessage message) { + if (message instanceof MqttPublishMessage) { + MqttPublishMessage publishMessage = (MqttPublishMessage) message; publishMessage.payload().resetReaderIndex(); - recipientRegistry.accept(publishMessage); + recipientRegistry.accept(mqttChannel, publishMessage); publishMessage.retain(); if (mqttReceiveContext.getConfiguration().getClusterConfig().getClustered()) { - mqttReceiveContext.getClusterRegistry().spreadPublishMessage(((MqttPublishMessage) mqttMessage).copy()).subscribeOn(scheduler).subscribe(); + mqttReceiveContext.getClusterRegistry().spreadPublishMessage(publishMessage.copy()).subscribeOn(scheduler).subscribe(); } } - return mqttMessage; + return message; } } diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java index cee01a54..ae75cb51 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/AbstractReceiveContext.java @@ -2,6 +2,7 @@ package io.github.quickmsg.core.mqtt; import io.github.quickmsg.common.auth.PasswordAuthentication; import io.github.quickmsg.common.channel.ChannelRegistry; +import io.github.quickmsg.common.channel.MqttChannel; import io.github.quickmsg.common.cluster.ClusterRegistry; import io.github.quickmsg.common.config.AbstractConfiguration; import io.github.quickmsg.common.config.Configuration; @@ -13,6 +14,7 @@ import io.github.quickmsg.common.topic.TopicRegistry; import io.github.quickmsg.common.transport.Transport; import io.github.quickmsg.core.spi.*; import io.github.quickmsg.core.cluster.InJvmClusterRegistry; +import io.netty.handler.codec.mqtt.MqttMessage; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java index 87dbeb9b..bcadafcb 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MqttReceiveContext.java @@ -34,12 +34,14 @@ public class MqttReceiveContext extends AbstractReceiveContext clusterSender.apply(mqttChannel,message)) .subscribe(mqttMessage -> this.accept(mqttChannel, mqttMessage)); } + + @Override public void accept(MqttChannel mqttChannel, MqttMessage mqttMessage) { log.info("accept channel] {} message {}", mqttChannel.getConnection(), mqttMessage); diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java index c4d4eb57..f372b841 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/EmptyRecipientRegistry.java @@ -12,7 +12,7 @@ public class EmptyRecipientRegistry implements RecipientRegistry { @Override - public void accept(MqttPublishMessage message) { + public void accept(MqttChannel mqttChannel, MqttPublishMessage publishMessage) { } @@ -20,6 +20,4 @@ public class EmptyRecipientRegistry implements RecipientRegistry { public void channelStatus(MqttChannel mqttChannel, ChannelStatus channelStatus) { } - - } -- Gitee From c8d5bb83adadd490c8d406e90a07e7cf44383066 Mon Sep 17 00:00:00 2001 From: luxurong Date: Tue, 6 Jul 2021 13:55:24 +0800 Subject: [PATCH 022/482] message outer transport --- .../io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java index 9f5e38ba..b6e3f4dd 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/spi/DefaultProtocolAdaptor.java @@ -37,6 +37,9 @@ public class DefaultProtocolAdaptor implements ProtocolAdaptor { @Override public void chooseProtocol(MqttChannel mqttChannel, MqttMessage mqttMessage, ReceiveContext receiveContext) { + if(log.isDebugEnabled()){ + log.debug("chooseProtocol channel {} chooseProtocol:", mqttChannel); + } Optional.ofNullable(types.get(mqttMessage.fixedHeader().messageType())) .ifPresent(protocol -> protocol .doParseProtocol(mqttMessage, mqttChannel) -- Gitee From d737f32a9780b3e0274ce22e90ace013bd5991f7 Mon Sep 17 00:00:00 2001 From: luxurong Date: Wed, 7 Jul 2021 15:02:45 +0800 Subject: [PATCH 023/482] http --- "docs/\345\205\245\351\227\250/1.jar.md" | 10 +-- pom.xml | 1 + .../quickmsg/common/annotation/Spi.java | 17 +++++ .../quickmsg/common/spi/CacheLoader.java | 42 ++++++++++++ smqtt-core/pom.xml | 5 ++ .../core/http/actors/CounterHttpActor.java | 32 ++++++++++ .../core/http/actors/CpuHttpActor.java | 33 ++++++++++ .../core/http/actors/JvmHttpActor.java | 32 ++++++++++ .../quickmsg/core/metric/MetricManager.java | 47 -------------- .../core/mqtt/MetricChannelHandler.java | 6 +- .../core/protocol/ConnectProtocol.java | 8 +-- .../core/websocket/WebSocketMqttReceiver.java | 6 +- smqtt-metric/pom.xml | 35 ++++++++++ .../github/quickmsg/metric/MetricsGetter.java | 17 +++++ .../quickmsg/metric/MetricsManager.java | 12 ++++ .../quickmsg/metric/category/CpuMetric.java | 64 +++++++++++++++++++ .../quickmsg/metric/category/JvmMetric.java | 45 +++++++++++++ .../metric}/counter/SideWindowCounter.java | 2 +- .../metric/counter/SimpleWindowCounter.java | 36 +++++++++++ .../metric}/counter/WindowCounter.java | 2 +- .../quickmsg/metric/counter/WindowMetric.java | 56 ++++++++++++++++ .../quickmsg/metric/utils/FormatUtils.java | 33 ++++++++++ .../test/java/io/github/quickmsg/AppTest.java | 20 ++++++ 23 files changed, 498 insertions(+), 63 deletions(-) create mode 100644 smqtt-common/src/main/java/io/github/quickmsg/common/annotation/Spi.java create mode 100644 smqtt-common/src/main/java/io/github/quickmsg/common/spi/CacheLoader.java create mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java create mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java create mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java delete mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java create mode 100644 smqtt-metric/pom.xml create mode 100644 smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsGetter.java create mode 100644 smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsManager.java create mode 100644 smqtt-metric/src/main/java/io/github/quickmsg/metric/category/CpuMetric.java create mode 100644 smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java rename {smqtt-core/src/main/java/io/github/quickmsg/core => smqtt-metric/src/main/java/io/github/quickmsg/metric}/counter/SideWindowCounter.java (97%) create mode 100644 smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/SimpleWindowCounter.java rename {smqtt-core/src/main/java/io/github/quickmsg/core => smqtt-metric/src/main/java/io/github/quickmsg/metric}/counter/WindowCounter.java (92%) create mode 100644 smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java create mode 100644 smqtt-metric/src/main/java/io/github/quickmsg/metric/utils/FormatUtils.java create mode 100644 smqtt-metric/src/test/java/io/github/quickmsg/AppTest.java diff --git "a/docs/\345\205\245\351\227\250/1.jar.md" "b/docs/\345\205\245\351\227\250/1.jar.md" index f836b589..d28ad55e 100644 --- "a/docs/\345\205\245\351\227\250/1.jar.md" +++ "b/docs/\345\205\245\351\227\250/1.jar.md" @@ -115,15 +115,17 @@ redis.sentinel.nodes=127.0.0.1:26379,127.0.0.1:26379,127.0.0.1:26379 ``` +## 启动服务 + +```markdown + java -jar smqtt-bootstrap-1.0.1-SNAPSHOT.jar +``` + ## 不使用配置文件 使用jvm启动参数: ```markdown java -jar -Dsmqtt.tcp.port=1883 -Dsmqtt.http.enable=true smqtt-bootstrap-1.0.5.jar ``` -## 启动服务 -```markdown - java -jar smqtt-bootstrap-1.0.1-SNAPSHOT.jar -``` diff --git a/pom.xml b/pom.xml index f091eff2..5a479884 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ smqtt-persistent smqtt-rule smqtt-ui + smqtt-metric pom diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/annotation/Spi.java b/smqtt-common/src/main/java/io/github/quickmsg/common/annotation/Spi.java new file mode 100644 index 00000000..b44e61e8 --- /dev/null +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/annotation/Spi.java @@ -0,0 +1,17 @@ +package io.github.quickmsg.common.annotation; + +import java.lang.annotation.*; + +/** + * @author luxurong + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface Spi { + + String type(); + + +} diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/spi/CacheLoader.java b/smqtt-common/src/main/java/io/github/quickmsg/common/spi/CacheLoader.java new file mode 100644 index 00000000..09a37755 --- /dev/null +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/spi/CacheLoader.java @@ -0,0 +1,42 @@ +package io.github.quickmsg.common.spi; + +import io.github.quickmsg.common.annotation.Spi; +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.StreamSupport; + +/** + * @author luxurong + */ +@Slf4j +public class CacheLoader { + + public static Map, Map> cacheBean = new ConcurrentHashMap<>(); + + @SuppressWarnings("unchecked") + public static T loadSpiByType(String type, Class tClass) { + Map beans = cacheBean.computeIfAbsent(tClass, CacheLoader::loadAll); + return (T) beans.get(type); + } + + private static Map loadAll(Class aClass) { + ServiceLoader load = ServiceLoader.load(aClass); + Map map = new HashMap<>(16); + StreamSupport.stream(load.spliterator(), false) + .forEach(b -> { + Spi spi = b.getClass().getAnnotation(Spi.class); + if (spi == null) { + log.warn("class {} not contain spi annotation ", b); + } else { + map.put(spi.type(), b); + } + }); + return map; + } + + +} diff --git a/smqtt-core/pom.xml b/smqtt-core/pom.xml index a72cb08f..cff07980 100644 --- a/smqtt-core/pom.xml +++ b/smqtt-core/pom.xml @@ -16,6 +16,11 @@ smqtt-common 1.0.5 + + io.github.quickmsg + smqtt-metric + 1.0.5 + \ No newline at end of file diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java new file mode 100644 index 00000000..8600431a --- /dev/null +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java @@ -0,0 +1,32 @@ +package io.github.quickmsg.core.http.actors; + +import io.github.quickmsg.common.annotation.Header; +import io.github.quickmsg.common.annotation.Router; +import io.github.quickmsg.common.config.Configuration; +import io.github.quickmsg.common.enums.HttpType; +import io.github.quickmsg.common.http.HttpActor; +import io.github.quickmsg.metric.counter.WindowMetric; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; + +/** + * @author luxurong + */ + +@Router(value = "/smqtt/monitor/counter", type = HttpType.GET) +@Slf4j +@Header(key = "Content-Type", value = "application/json") +public class CounterHttpActor implements HttpActor { + + @Override + public Publisher doRequest(HttpServerRequest request, HttpServerResponse response, Configuration configuration) { + return request + .receive() + .then(response + .sendString(Mono.just(WindowMetric.WINDOW_METRIC_INSTANCE.metrics().toJSONString())) + .then()); + } +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java new file mode 100644 index 00000000..9cd7f9f6 --- /dev/null +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java @@ -0,0 +1,33 @@ +package io.github.quickmsg.core.http.actors; + +import io.github.quickmsg.common.annotation.Header; +import io.github.quickmsg.common.annotation.Router; +import io.github.quickmsg.common.config.Configuration; +import io.github.quickmsg.common.enums.HttpType; +import io.github.quickmsg.common.http.HttpActor; +import io.github.quickmsg.metric.category.CpuMetric; +import io.github.quickmsg.metric.category.JvmMetric; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; + +/** + * @author luxurong + */ + +@Router(value = "/smqtt/monitor/cpu", type = HttpType.GET) +@Slf4j +@Header(key = "Content-Type", value = "application/json") +public class CpuHttpActor implements HttpActor { + + @Override + public Publisher doRequest(HttpServerRequest request, HttpServerResponse response, Configuration configuration) { + return request + .receive() + .then(response + .sendString(Mono.just(CpuMetric.CPU_METRIC_INSTANCE.metrics().toJSONString())) + .then()); + } +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java new file mode 100644 index 00000000..9fd3b4c8 --- /dev/null +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java @@ -0,0 +1,32 @@ +package io.github.quickmsg.core.http.actors; + +import io.github.quickmsg.common.annotation.Header; +import io.github.quickmsg.common.annotation.Router; +import io.github.quickmsg.common.config.Configuration; +import io.github.quickmsg.common.enums.HttpType; +import io.github.quickmsg.common.http.HttpActor; +import io.github.quickmsg.metric.category.JvmMetric; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; + +/** + * @author luxurong + */ + +@Router(value = "/smqtt/monitor/jvm", type = HttpType.GET) +@Slf4j +@Header(key = "Content-Type", value = "application/json") +public class JvmHttpActor implements HttpActor { + + @Override + public Publisher doRequest(HttpServerRequest request, HttpServerResponse response, Configuration configuration) { + return request + .receive() + .then(response + .sendString(Mono.just(JvmMetric.JVM_METRIC_INSTANCE.metrics().toJSONString())) + .then()); + } +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java b/smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java deleted file mode 100644 index 21c9ad2e..00000000 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/metric/MetricManager.java +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.quickmsg.core.metric; - -import io.github.quickmsg.core.counter.SideWindowCounter; -import io.github.quickmsg.core.counter.WindowCounter; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -/** - * @author luxurong - */ -@Getter -@Slf4j -public class MetricManager { - - - private static WindowCounter readAllBufferSize = new SideWindowCounter(1, TimeUnit.HOURS, "TRANSPORT-READ-BUFFER-SIZE"); - - private static WindowCounter writeAllBufferSize = new SideWindowCounter(1, TimeUnit.HOURS, "TRANSPORT-WRITE-BUFFER-SIZE"); - - private static WindowCounter connectCounter = new SideWindowCounter(1, TimeUnit.HOURS, "CONNECT-SIZE"); - - - private static Map transportBufferSize = new ConcurrentHashMap<>(); - - public static void recordDataSend(Integer bufferSize) { - writeAllBufferSize.apply(bufferSize); - } - - public static void recordDataReceived(Integer bufferSize) { - readAllBufferSize.apply(bufferSize); - } - - public static void recordConnect(Integer size) { - connectCounter.apply(size); - } - - public static void acceptMetric(String topic, Integer bufferSize) { - WindowCounter windowCounter = transportBufferSize.computeIfAbsent(topic, tc -> new SideWindowCounter(1, TimeUnit.MINUTES, tc)); - windowCounter.apply(bufferSize); - } - - -} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java index 7d1d7f40..55168a45 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/mqtt/MetricChannelHandler.java @@ -1,6 +1,6 @@ package io.github.quickmsg.core.mqtt; -import io.github.quickmsg.core.metric.MetricManager; +import io.github.quickmsg.metric.counter.WindowMetric; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; @@ -17,7 +17,7 @@ public class MetricChannelHandler extends ChannelDuplexHandler { if (msg instanceof ByteBuf) { ByteBuf buffer = (ByteBuf) msg; if (buffer.readableBytes() > 0) { - MetricManager.recordDataSend(buffer.readableBytes()); + WindowMetric.WINDOW_METRIC_INSTANCE.recordDataSend(buffer.readableBytes()); } } super.write(ctx, msg, promise); @@ -28,7 +28,7 @@ public class MetricChannelHandler extends ChannelDuplexHandler { if (msg instanceof ByteBuf) { ByteBuf buffer = (ByteBuf) msg; if (buffer.readableBytes() > 0) { - MetricManager.recordDataReceived(buffer.readableBytes()); + WindowMetric.WINDOW_METRIC_INSTANCE.recordDataReceived(buffer.readableBytes()); } } super.channelRead(ctx, msg); diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java b/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java index b74bc4cd..dd5878d4 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/protocol/ConnectProtocol.java @@ -10,7 +10,7 @@ import io.github.quickmsg.common.message.MqttMessageBuilder; import io.github.quickmsg.common.message.RecipientRegistry; import io.github.quickmsg.common.protocol.Protocol; import io.github.quickmsg.common.topic.TopicRegistry; -import io.github.quickmsg.core.metric.MetricManager; +import io.github.quickmsg.metric.counter.WindowMetric; import io.github.quickmsg.core.mqtt.MqttReceiveContext; import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.*; @@ -40,7 +40,7 @@ public class ConnectProtocol implements Protocol { } private static void accept(MqttChannel mqttChannel1) { - MetricManager.recordConnect(-1); + WindowMetric.WINDOW_METRIC_INSTANCE.recordConnect(-1); } @Override @@ -114,16 +114,14 @@ public class ConnectProtocol implements Protocol { channelRegistry.registry(clientIdentifier, mqttChannel); - MetricManager.recordConnect(1); + WindowMetric.WINDOW_METRIC_INSTANCE.recordConnect(1); mqttChannel.registryClose(ConnectProtocol::accept); recipientRegistry.channelStatus(mqttChannel, mqttChannel.getStatus()); - mqttChannel.registryClose(mqttChannel1 -> recipientRegistry.channelStatus(mqttChannel1, ChannelStatus.OFFLINE)); return mqttChannel.write(MqttMessageBuilder.buildConnectAck(MqttConnectReturnCode.CONNECTION_ACCEPTED), false); - } else { return mqttChannel.write( MqttMessageBuilder.buildConnectAck(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD), diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/websocket/WebSocketMqttReceiver.java b/smqtt-core/src/main/java/io/github/quickmsg/core/websocket/WebSocketMqttReceiver.java index c9a5e3b7..d9a4ea53 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/websocket/WebSocketMqttReceiver.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/websocket/WebSocketMqttReceiver.java @@ -2,6 +2,7 @@ package io.github.quickmsg.core.websocket; import io.github.quickmsg.common.Receiver; import io.github.quickmsg.common.channel.MqttChannel; +import io.github.quickmsg.core.mqtt.MetricChannelHandler; import io.github.quickmsg.core.mqtt.MqttConfiguration; import io.github.quickmsg.core.mqtt.MqttReceiveContext; import io.github.quickmsg.core.ssl.AbstractSslHandler; @@ -54,8 +55,9 @@ public class WebSocketMqttReceiver extends AbstractSslHandler implements Receive .addHandler(new WebSocketServerProtocolHandler("/", "mqtt, mqttv3.1, mqttv3.1.1")) .addHandler(new WebSocketFrameToByteBufDecoder()) .addHandler(new ByteBufToWebSocketFrameEncoder()) - .addHandler(new MqttDecoder()) - .addHandler(MqttEncoder.INSTANCE); + .addHandler(MqttEncoder.INSTANCE) + .addHandler(new MetricChannelHandler()) + .addHandler(new MqttDecoder()); receiveContext.apply(MqttChannel.init(connection)); }); } diff --git a/smqtt-metric/pom.xml b/smqtt-metric/pom.xml new file mode 100644 index 00000000..23b07d33 --- /dev/null +++ b/smqtt-metric/pom.xml @@ -0,0 +1,35 @@ + + + + 4.0.0 + + + + smqtt + io.github.quickmsg + 1.0.5 + + + smqtt-metric + + + + io.github.quickmsg + smqtt-common + 1.0.5 + + + com.github.oshi + oshi-core + 5.7.5 + + + junit + junit + 4.11 + test + + + + diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsGetter.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsGetter.java new file mode 100644 index 00000000..e6f7c9ae --- /dev/null +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsGetter.java @@ -0,0 +1,17 @@ +package io.github.quickmsg.metric; + +import com.alibaba.fastjson.JSONObject; + +/** + * @author luxurong + */ +public interface MetricsGetter { + + /** + * 获取统计信息 + * + * @return {@link JSONObject} + */ + JSONObject metrics(); + +} diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsManager.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsManager.java new file mode 100644 index 00000000..79506fc8 --- /dev/null +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/MetricsManager.java @@ -0,0 +1,12 @@ +package io.github.quickmsg.metric; + +/** + * @author luxurong + */ +public class MetricsManager { + + + + + +} diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/CpuMetric.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/CpuMetric.java new file mode 100644 index 00000000..40e469c9 --- /dev/null +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/CpuMetric.java @@ -0,0 +1,64 @@ +package io.github.quickmsg.metric.category; + +import com.alibaba.fastjson.JSONObject; +import io.github.quickmsg.metric.MetricsGetter; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.util.Util; + +import java.text.DecimalFormat; + +/** + * cpu数据 + * + * @author luxurong + */ +public class CpuMetric implements MetricsGetter { + + + private SystemInfo systemInfo = new SystemInfo(); + + private HardwareAbstractionLayer hardware = systemInfo.getHardware(); + + private final int OSHI_WAIT_SECOND = 500; + + public final static CpuMetric CPU_METRIC_INSTANCE = new CpuMetric(); + + private CpuMetric() { + + } + + + @Override + public JSONObject metrics() { + JSONObject cpuInfo = new JSONObject(); + CentralProcessor processor = hardware.getProcessor(); + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; + long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; + long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; + long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; + long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; + long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; + long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; + long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + //cpu核数 + cpuInfo.put("cpuNum", processor.getLogicalProcessorCount()); + //cpu系统使用率 + cpuInfo.put("cSys", new DecimalFormat("#.##%").format(cSys * 1.0 / totalCpu)); + //cpu用户使用率 + cpuInfo.put("user", new DecimalFormat("#.##%").format(user * 1.0 / totalCpu)); + //cpu当前等待率 + cpuInfo.put("iowait", new DecimalFormat("#.##%").format(iowait * 1.0 / totalCpu)); + //cpu当前使用率 + cpuInfo.put("idle", new DecimalFormat("#.##%").format(1.0 - (idle * 1.0 / totalCpu))); + return cpuInfo; + } + + +} diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java new file mode 100644 index 00000000..73279c87 --- /dev/null +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java @@ -0,0 +1,45 @@ +package io.github.quickmsg.metric.category; + +import com.alibaba.fastjson.JSONObject; +import io.github.quickmsg.metric.MetricsGetter; +import io.github.quickmsg.metric.utils.FormatUtils; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.ThreadMXBean; +import java.util.Properties; + +/** + * jvm数据 + * + * @author luxurong + */ +public class JvmMetric implements MetricsGetter { + + public final static JvmMetric JVM_METRIC_INSTANCE = new JvmMetric(); + + private JvmMetric() { + + } + + + @Override + public JSONObject metrics() { + Properties props = System.getProperties(); + JSONObject jvm = new JSONObject(); + MemoryMXBean mxb = ManagementFactory.getMemoryMXBean(); + ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + jvm.put("jdk_home", props.getProperty("java.home")); + jvm.put("jdk_version", props.getProperty("java.version")); + jvm.put("thread.count", threadBean.getThreadCount()); + jvm.put("heap-max", FormatUtils.formatByte(mxb.getHeapMemoryUsage().getMax())); + jvm.put("heap-init", FormatUtils.formatByte(mxb.getHeapMemoryUsage().getInit())); + jvm.put("heap-commit", FormatUtils.formatByte(mxb.getHeapMemoryUsage().getCommitted())); + jvm.put("heap-used", FormatUtils.formatByte(mxb.getHeapMemoryUsage().getUsed())); + jvm.put("no_heap-max", FormatUtils.formatByte(mxb.getNonHeapMemoryUsage().getMax())); + jvm.put("no_heap-init", FormatUtils.formatByte(mxb.getNonHeapMemoryUsage().getInit())); + jvm.put("no_heap-commit", FormatUtils.formatByte(mxb.getNonHeapMemoryUsage().getCommitted())); + jvm.put("no_heap-used", FormatUtils.formatByte(mxb.getNonHeapMemoryUsage().getUsed())); + return jvm; + } +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/SideWindowCounter.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/SideWindowCounter.java similarity index 97% rename from smqtt-core/src/main/java/io/github/quickmsg/core/counter/SideWindowCounter.java rename to smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/SideWindowCounter.java index 0c9d0c13..164d9da8 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/SideWindowCounter.java +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/SideWindowCounter.java @@ -1,4 +1,4 @@ -package io.github.quickmsg.core.counter; +package io.github.quickmsg.metric.counter; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/SimpleWindowCounter.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/SimpleWindowCounter.java new file mode 100644 index 00000000..05160f50 --- /dev/null +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/SimpleWindowCounter.java @@ -0,0 +1,36 @@ +package io.github.quickmsg.metric.counter; + +import reactor.core.publisher.Flux; + +import java.util.concurrent.atomic.LongAdder; + +/** + * @author luxurong + * @date 2021/7/7 11:41 + * @description + */ +public class SimpleWindowCounter implements WindowCounter { + + + private LongAdder windowAdder = new LongAdder(); + + @Override + public Long intervalCount() { + return windowAdder.sum(); + } + + @Override + public Long allCount() { + return windowAdder.sum(); + } + + @Override + public void apply(Integer request) { + windowAdder.add(request); + } + + @Override + public Flux interval() { + return Flux.empty(); + } +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/WindowCounter.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowCounter.java similarity index 92% rename from smqtt-core/src/main/java/io/github/quickmsg/core/counter/WindowCounter.java rename to smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowCounter.java index ce152e06..47865444 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/counter/WindowCounter.java +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowCounter.java @@ -1,4 +1,4 @@ -package io.github.quickmsg.core.counter; +package io.github.quickmsg.metric.counter; import reactor.core.publisher.Flux; diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java new file mode 100644 index 00000000..1af8f49a --- /dev/null +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java @@ -0,0 +1,56 @@ +package io.github.quickmsg.metric.counter; + +import com.alibaba.fastjson.JSONObject; +import io.github.quickmsg.metric.MetricsGetter; +import io.github.quickmsg.metric.utils.FormatUtils; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.TimeUnit; + +/** + * @author luxurong + */ +@Getter +@Slf4j +public class WindowMetric implements MetricsGetter { + + + public final static WindowMetric WINDOW_METRIC_INSTANCE = new WindowMetric(); + + private WindowMetric() { + + } + + private WindowCounter readAllBufferSize = new SideWindowCounter(1, TimeUnit.HOURS, "TRANSPORT-READ-BUFFER-SIZE"); + + private WindowCounter writeAllBufferSize = new SideWindowCounter(1, TimeUnit.HOURS, "TRANSPORT-WRITE-BUFFER-SIZE"); + + private WindowCounter connectCounter = new SimpleWindowCounter(); + + public void recordDataSend(Integer bufferSize) { + writeAllBufferSize.apply(bufferSize); + } + + public void recordDataReceived(Integer bufferSize) { + readAllBufferSize.apply(bufferSize); + } + + public void recordConnect(Integer size) { + connectCounter.apply(size); + } + + + @Override + public JSONObject metrics() { + JSONObject window = new JSONObject(); + window.put("read_size", FormatUtils.formatByte(readAllBufferSize.allCount())); + window.put("read_hour_size", FormatUtils.formatByte(readAllBufferSize.intervalCount())); + window.put("write_size", FormatUtils.formatByte(writeAllBufferSize.allCount())); + window.put("write_hour_size", FormatUtils.formatByte(writeAllBufferSize.intervalCount())); + window.put("connect_size", FormatUtils.formatByte(connectCounter.allCount())); + return window; + } + + +} diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/utils/FormatUtils.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/utils/FormatUtils.java new file mode 100644 index 00000000..425bab4e --- /dev/null +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/utils/FormatUtils.java @@ -0,0 +1,33 @@ +package io.github.quickmsg.metric.utils; + +import java.text.DecimalFormat; + +/** + * @author luxurong + */ +public class FormatUtils { + + /** + * 单位转换 + * + * @param byteNumber number + * @return desc + */ + public static String formatByte(long byteNumber) { + double format = 1024.0; + double kbNumber = byteNumber / format; + if (kbNumber < format) { + return new DecimalFormat("#.##KB").format(kbNumber); + } + double mbNumber = kbNumber / format; + if (mbNumber < format) { + return new DecimalFormat("#.##MB").format(mbNumber); + } + double gbNumber = mbNumber / format; + if (gbNumber < format) { + return new DecimalFormat("#.##GB").format(gbNumber); + } + double tbNumber = gbNumber / format; + return new DecimalFormat("#.##TB").format(tbNumber); + } +} diff --git a/smqtt-metric/src/test/java/io/github/quickmsg/AppTest.java b/smqtt-metric/src/test/java/io/github/quickmsg/AppTest.java new file mode 100644 index 00000000..32a5272f --- /dev/null +++ b/smqtt-metric/src/test/java/io/github/quickmsg/AppTest.java @@ -0,0 +1,20 @@ +package io.github.quickmsg; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Unit test for simple App. + */ +public class AppTest +{ + /** + * Rigorous Test :-) + */ + @Test + public void shouldAnswerWithTrue() + { + assertTrue( true ); + } +} -- Gitee From 16c769dd672e8ea1e1e206d67ea27ffb59ecdba7 Mon Sep 17 00:00:00 2001 From: luxurong Date: Thu, 8 Jul 2021 10:49:10 +0800 Subject: [PATCH 024/482] http --- .../java/io/github/quickmsg/metric/category/JvmMetric.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java index 73279c87..68171a95 100644 --- a/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/category/JvmMetric.java @@ -6,7 +6,9 @@ import io.github.quickmsg.metric.utils.FormatUtils; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; +import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; +import java.util.Date; import java.util.Properties; /** @@ -16,10 +18,10 @@ import java.util.Properties; */ public class JvmMetric implements MetricsGetter { + public final static JvmMetric JVM_METRIC_INSTANCE = new JvmMetric(); private JvmMetric() { - } @@ -29,6 +31,9 @@ public class JvmMetric implements MetricsGetter { JSONObject jvm = new JSONObject(); MemoryMXBean mxb = ManagementFactory.getMemoryMXBean(); ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); + jvm.put("smqtt", "1.0.5"); + jvm.put("start_time",new Date(runtimeBean.getStartTime()).toString()); jvm.put("jdk_home", props.getProperty("java.home")); jvm.put("jdk_version", props.getProperty("java.version")); jvm.put("thread.count", threadBean.getThreadCount()); -- Gitee From 8600452d4db373e82612de3bd9f417802cc3b02f Mon Sep 17 00:00:00 2001 From: luxurong Date: Thu, 8 Jul 2021 14:17:57 +0800 Subject: [PATCH 025/482] http --- .../services/io.github.quickmsg.common.http.HttpActor | 5 ++++- .../java/io/github/quickmsg/metric/counter/WindowMetric.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor index 81d75834..cf3bac29 100644 --- a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor +++ b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor @@ -4,4 +4,7 @@ io.github.quickmsg.core.http.actors.ClusterActor io.github.quickmsg.core.http.actors.SubscribeActor io.github.quickmsg.core.http.actors.resource.IndexResourceActor io.github.quickmsg.core.http.actors.resource.StaticResourceActor -io.github.quickmsg.core.http.actors.resource.LoginResourceActor \ No newline at end of file +io.github.quickmsg.core.http.actors.resource.LoginResourceActor +io.github.quickmsg.core.http.actors.JvmHttpActor +io.github.quickmsg.core.http.actors.CounterHttpActor +io.github.quickmsg.core.http.actors.CpuHttpActor \ No newline at end of file diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java index 1af8f49a..6457c2f0 100644 --- a/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java @@ -48,7 +48,7 @@ public class WindowMetric implements MetricsGetter { window.put("read_hour_size", FormatUtils.formatByte(readAllBufferSize.intervalCount())); window.put("write_size", FormatUtils.formatByte(writeAllBufferSize.allCount())); window.put("write_hour_size", FormatUtils.formatByte(writeAllBufferSize.intervalCount())); - window.put("connect_size", FormatUtils.formatByte(connectCounter.allCount())); + window.put("connect_size", connectCounter.allCount()); return window; } -- Gitee From 440ff3cb37eb65565d917430fc12dd462cd8b397 Mon Sep 17 00:00:00 2001 From: luxurong Date: Thu, 8 Jul 2021 14:24:41 +0800 Subject: [PATCH 026/482] http --- .../github/quickmsg/metric/counter/WindowMetric.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java index 6457c2f0..e216be56 100644 --- a/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java +++ b/smqtt-metric/src/main/java/io/github/quickmsg/metric/counter/WindowMetric.java @@ -44,10 +44,12 @@ public class WindowMetric implements MetricsGetter { @Override public JSONObject metrics() { JSONObject window = new JSONObject(); - window.put("read_size", FormatUtils.formatByte(readAllBufferSize.allCount())); - window.put("read_hour_size", FormatUtils.formatByte(readAllBufferSize.intervalCount())); - window.put("write_size", FormatUtils.formatByte(writeAllBufferSize.allCount())); - window.put("write_hour_size", FormatUtils.formatByte(writeAllBufferSize.intervalCount())); + long readHour = readAllBufferSize.intervalCount(); + long writeHour = writeAllBufferSize.intervalCount(); + window.put("read_size", FormatUtils.formatByte(readAllBufferSize.allCount() + readHour)); + window.put("read_hour_size", FormatUtils.formatByte(readHour)); + window.put("write_size", FormatUtils.formatByte(writeAllBufferSize.allCount() + writeHour)); + window.put("write_hour_size", FormatUtils.formatByte(writeHour)); window.put("connect_size", connectCounter.allCount()); return window; } -- Gitee From 36cb416ad26ae8afbf248417d3fed828a07f96ca Mon Sep 17 00:00:00 2001 From: luxurong Date: Thu, 8 Jul 2021 15:51:49 +0800 Subject: [PATCH 027/482] http --- smqtt-bootstrap/src/test/java/ClusterNode1.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smqtt-bootstrap/src/test/java/ClusterNode1.java b/smqtt-bootstrap/src/test/java/ClusterNode1.java index 75dbd3ee..50c88de7 100644 --- a/smqtt-bootstrap/src/test/java/ClusterNode1.java +++ b/smqtt-bootstrap/src/test/java/ClusterNode1.java @@ -22,7 +22,7 @@ public class ClusterNode1 { .sslContext(new SslContext("crt", "key")) .isWebsocket(true) .wiretap(false) - .httpOptions(Bootstrap.HttpOptions.builder().ssl(false).accessLog(true).build()) + .httpOptions(Bootstrap.HttpOptions.builder().enableAdmin(true).ssl(false).accessLog(true).build()) .clusterConfig( ClusterConfig.builder() .clustered(true) -- Gitee From a45ecddffb5f6f87284d8c17ad2762c80b5f7c50 Mon Sep 17 00:00:00 2001 From: "yiming.tang" Date: Thu, 8 Jul 2021 22:35:42 +0800 Subject: [PATCH 028/482] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0=E9=A1=B5=E9=9D=A2=EF=BC=9B=E6=9B=B4=E5=A5=BD=E4=B8=B4?= =?UTF-8?q?=E6=97=B6logo=EF=BC=9B=E4=BF=AE=E5=A4=8D=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E9=A1=B5=E9=A6=96=E9=A1=B5=E8=B0=83=E6=95=B4=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smqtt-ui/public/favicon.ico | Bin 2913 -> 41662 bytes smqtt-ui/src/assets/img/cpu.png | Bin 0 -> 8334 bytes smqtt-ui/src/assets/img/jvm.png | Bin 0 -> 10214 bytes smqtt-ui/src/assets/img/logo.png | Bin 26705 -> 12702 bytes smqtt-ui/src/components/menu/SideMenu.vue | 2 +- .../src/pages/dashboard/console/Console.vue | 244 ++++++++++++++++++ smqtt-ui/src/pages/dashboard/console/index.js | 2 + smqtt-ui/src/pages/exception/403.vue | 2 +- smqtt-ui/src/pages/exception/404.vue | 2 +- smqtt-ui/src/pages/exception/500.vue | 2 +- smqtt-ui/src/pages/login/Login.vue | 4 +- smqtt-ui/src/router/config.js | 15 +- 12 files changed, 262 insertions(+), 11 deletions(-) create mode 100644 smqtt-ui/src/assets/img/cpu.png create mode 100644 smqtt-ui/src/assets/img/jvm.png create mode 100644 smqtt-ui/src/pages/dashboard/console/Console.vue create mode 100644 smqtt-ui/src/pages/dashboard/console/index.js diff --git a/smqtt-ui/public/favicon.ico b/smqtt-ui/public/favicon.ico index 47bf70b9def4bdda7fd3cb9fdbba9e794c2bfb7f..bd1a5761c470114fa41b4e397e9942a7413156fb 100644 GIT binary patch literal 41662 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

)1;nk5Ci^Kl#ys7j4ZH?5nvMsb9 zeKsU(j57oNd+yPd!Z?Qd&UVwkOg%JD27+hQkJm;u(L4zIO8L@ffDgp=R){vk%DGU! zUru^i?H;ijwtK8%@6`82->y?VN#VRv3YN* z+>U>Dy2^6^+jSl|G;-qMinIL&{r3@nENHWPtnMcXv)T#uT6KWA4)Dg571IXO-d9Fm zsUBpNx=cBveVBzjMzJvm$9UfH9`!wLYJ&2g@lzmvN5qzsh*l(cvpKghpswx4*{Nv;Fm(*?PKl%kjr?K5L3b1cI6m^w>4 z)Eluh+e}$2hL6J@P@WpXt`PTbD|&0Z;N-7&KMk1touwbHIE*nkPBF)NKDxmelR6NM zt*ryz=dmufg|>p@*a2IH$~feUXiF*YwvTtjyp%zUXfOJ19H_p-!dCeQzOn9V$OYH! z@v^iU+(LRz>@K6`kCd5fXUKxo1+r}GQdzTOwQSn6QMP8jB|CF>$c~(Jc`IX!Y}~y; zR&QS=OW#@|3pX#28EcT=Oc^R&#&(jXNsZ;D?zN;c@=nCU!bp8N%5?|UQ+=@7=)bk_ zVJuI(&Ab`$VLxK0w$^+mV{2PZE#1MEa!lQ%Zh3LAwgo7AKx(hJ1w9Sg0+{3H=8p(jE{bV6{p8a9Ae0Ab0 zId%4wT)1>Wu3f(-rTrcG=5zV*vk&F`#dDzZa`N;^$v>JeyASM=S!-u1&h>gS?*p6b zIHK|ZdASWJ)C|?h9z4Gm~ z(M>e(#C#v)Su+RWJLU`2=0L}YA9afQ!1gvxZX(OxS}I4sJt|kPUM(%#i(P;9+ErQk z_6lj(r@qwcQbQ&z9WUoDoGW(S#2wV+rQlm}_T0HK)p)3JLPXEyqrb3&;!J!x%(m3z$237Hr?b&5V`I znAYu$iJfg{8?8FXf7+$$J*r7l#3r1N;hdv6|7GSsEI+{ZG528Qewe3WOvyQ8#^-k2 ziTa(-3$RZN(m}}}59rCZ$njzeESN_(^@BIdlq$rsvQ=3Ur#${zDi#EEv2@g(JtdETJpryH~v zoNFiV6X$f$vFV6?;(?r&=1a_RNM+o|+;B*qKWscD}X>ob2caO-f@JL;Z4F}Fg!WNdD= zi_hhljAI^?R%y-5A7DQV&m`|wgE)6r9`utx z6^*$$hoNNgKG+tzfm~yRQM+9gV#oNG{B}Oaf1fxoPdN%S1N08)6sSZ#$f-lzC;vDe zp&VPeMPkLA6?KIYS=DuD= zu7NQlxGqEc2?JP6)VV=NR_frWr&|3FHT_fol zAJ}zXkQ1<@UKyKN=RyX#!Yf8iOF^I&c8KYkE%W3YAZ zpK=Z!_bIs&%YP5`QEq+-z4c(*3N#Evyz@ZcfT-(NK>l^!?lSpa2%_C5&*y;#K(@GM z;E$l+fO>n%x&!j{Ru4wD_>&RoUE_l`#DMZ|(iUes$!Vy>S7-KwaAA{d|+8yWmZ{SxI<>nyveFBJB?FJnN9Ro3TI1M@nqMY(Mf$Rs@ zTm;fT;(ZYGHfSMeG^i_xe2)hGD`e?O#LADj5r?8pY~MeCCWE*p!P!Uu{iC=yf<8E= zan!A{J>-3Oz7gHVWjx&Jss1v9qXdH-g?`$8BJq3CKbv^p$ z&c4sY@uD`gaeWWv3ZPw}lEf#h`(cn#u5-DNeElvbqY}(Ex?&?J=d>@y7vN z61c|61MYtfqKy4HDn3$iq3lI~IQQcGMPVIGOyZv*n_PcaWbbfq5wAk*N5y&`hK&9J z*Utx0Pn>NTVATUW3=jNV%qx{)2T(WKq33jb=6(kI$spQL=l#L<523!_>iwwqcK^sb zwjcBao=My4(=K8W|9TwqL9gg>jamGz@!n(M@;q?(A>%)*4-u`(V+~~JwJ)BQ9>dcD zg!vllKCIC*qGDZti}F8$>VuYmXxn_+=q(S8viRk z72J0mbO=NoPk@}~?S1BFnd{-2D&|W3t|N9$L3bPrV(W%4CYg3Uq5;~$afIDQV$yz8 zJ2`gpnC8B>W4@v1q#jbKeFep(gySyCIPErhT?jf4BL9e;{j8Atl;_YgebaiV^W;DC zIa~+8bqoRQ@Os!f+VH!A2QkrS)1Smf&}Jut$Sb>lZ1ae@!*qU?lRuo3TamF!nvHEC z6|vTb=L7W1zTa6_Y4)vpxn=%?xyRDnZ(VD(jrqf#Yx?Q>1+M!Gpc^NFMP>TH=iA+W zA5Pu}`=RZsBZ$#|g#DRTU%2JkZuPi*ChSqKF!;4?G*DaSeujBP&dYEv zVA2P(bp4KhU8r-qJ2B#sNW`Bm(B1x_J(jNg0{h#oK6A^qbN-dlb9(*}XFV z#A=y{J~H3O_i(BK8|D#tkCj{U**ezc@)}-C+2QnYZJJBl50}^u%Nsh0R0LSmfUT)fn=;j zpN26S^9wyemq50jus=g)50-OS|HR)7{QL0YVOd(RLHSMog`0xuhmFTo;86fPf;^j8 zbDl-l;rp&lXYA6N?+3WToWBhxE@-2fZ-~=%!OV-;u_^sKedz3Uv#@^Wiehs0#x==2 z_ij*r1mjalkAD|Dhy~YIa*eFh-g#y0ZEQJ)4}1PLoOrMQsF>$SCSnO75@hBSmRrc2n=8o_9+=i2n z;oNt}y`a7X{seR5x@N(~g#Nt&zT=knah{FInQQ0dt<$@|GbW|NBlOtsuFY+P69=oV zNL>%cHDNkuQW2PZ;nWji5$be0op*I^ z!{TAMw_MBR!93OraagZnuERR#j|tz0I&=Pv1&Mri`3qSN|E@We5Prgc++meVj0bc> z$9{OXJiyrRuFq|R8w;yGT~nfCMc4O%#R!nkoL+Q-N2acPQ~0hDf6S?TaPgB{&Z(3L z59F_?8~lb(fU(~_Gq+*!Fx*>?-IdI>luMjCLjTV9JJzJHwspkJsl0PbF>p!nSXG!N z(>|TA@6`r~Z||wOjc{XO)fbUa4lyu&iTgLeBg2UaeF@($%fUOG*6%#$OwQ~rwcWZY z9@or#VrP7x0WqM{XK??XoZGN?7|w0|`t^+%u5~ik#Cg#7e1A-0hqRPWzx>pKL<+8* zE_F`jW_T1_FO+E?%roD!hHg6AaL>+dgcAp6U0k2S@uJI|$7&2-_^f%Pp7g=m6uuj1 z{l2<#tW-IbV0bXc%-@O2C#6$LSO2jFak;1GHk^D6=e{|9=NfQb6EO&U@LBUn|2}2K zWVv+ZlJbxK{iBP2K~803iTugU@Hp_7ccezQVtJAKVs0axI5_Ky^u9NTvB{5t2iJDm zc7y)4626hV9Ph&3xN$=A)m67>DrsBarmu_5^oo6#|>feLm!8I?;2k<>zznJR2o7)H{4$iuw z+i~r*&cBxj9-sPS!f%&+a^w>m6V92Wo!%7^lR$WIeaWO{6D4_ilDRg|p^tdxeK)t^ zyZK%If+ndr0b>$wdl zAH%ut%{g+umuntplYbVef5$h5`TeBp*RQLZaL(kt3m?mplf~Dm1jECs&#`?V<~G8K zgR?I0IFaj`_>S0C2jBEvpO?DVmi_PTv&@!Uxp7tYpF2=21}-5U57gX-laJxtkM2-i z*O>u}KYT=P_rp$H%{y5RV6HZX=u+IP2n^3F9sAdvdjb$G1M1 zz`u7J*F{d8I$>jS66@PG7H+pODFGf2zqou<*Bo-rqy%_8uyY&X#KBpY`5rFECGH;pkLgYwvHbhuv_rivfzpvlw34zC7 z#oR_Xad6fZ_1g2^c`Wz;0*{0Kn6$tc@8i!uvN5@M{gUiFpQ+yq3NQ!zS2MTaY{GYUI^#R8#|nY~ z_Yn43m$}{;+p=HjH~^m0w=))g41DA}4K|Go1&s!A zeeu(-4){KU7aQxl2c>J-U+>kHf5&_l-@#Z7vU$z#CGtDFJ92lZiUh&Kz8?2-UNRFj z98?MPl;NpXgbn5|`7U1BFEj`EeFm;W1t$Lq9=;2*dCWNzev{zPkwdcg-93Ty!@kye zANC7D{LUS}H4uaPf7A%W;fjIyks-8f0{}uZs`OYKp_#xJ= z;=6PD{UEM4;(aBtVatF&;_dK$8!>Rt?%$(P@%WaRx#qDl`1&=-U#^{H`U8Fgi$0ri zmGfM%ea>BFgJL4v;k#q4!u##e`IC2Zzp#0aW8bIN(pg&+-$vDf#z}o-rEhwwO$pP@1Jgx=u8MmW64&#o;i`H_(-*A5A9{Q%3vmX!2 zeh560c+L~cF=UxPxKnt0@nEje-(GPo>YZvv*>k)c@Z!N-$Oj>?Eb|+8Nj!*$`Ttqi z^CV?h+$AjT_s5!RWFFQ{Av%MdYwXiQ*fGmD%r4>*vz%|14Uf0%EgFxSWxbon9x}_k z$^0eh{rOhW@GpD(&YR_Q`+Bo%7mX))z5O`Dc$DQ!Mdu%4nPmOApKleg?F349lz)%? z6j`MVqCWJd!!Vc$`HxDe%ZkoWg2%lTFbP_s-je-CT9h=2ZN6W=0!n`KHz zzE<#r-Y?jHHk}8aHOtA|=g(VC*8=`{<;TCAZ=Fvv@3$N1E!zzg^Ssp{2rMr(r58I^ z*(<(=Ex+FS?Q*ht;8E}Wk{-DHgmpgOJa3o14Sd!*?`@#RDtjB~Urw^ldmCt%$wBBzL7A7^ Q{3?C<(zCwzvt-uy{~t|0LI3~& literal 2913 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 literal 26705 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Ú)iw@EROzI;@MB?O80_sB- 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
- +

{{systemName}}

diff --git a/smqtt-ui/src/pages/dashboard/console/Console.vue b/smqtt-ui/src/pages/dashboard/console/Console.vue new file mode 100644 index 00000000..a4f3bf33 --- /dev/null +++ b/smqtt-ui/src/pages/dashboard/console/Console.vue @@ -0,0 +1,244 @@ + + + \ No newline at end of file diff --git a/smqtt-ui/src/pages/dashboard/console/index.js b/smqtt-ui/src/pages/dashboard/console/index.js new file mode 100644 index 00000000..8386d595 --- /dev/null +++ b/smqtt-ui/src/pages/dashboard/console/index.js @@ -0,0 +1,2 @@ +import Console from "./Console"; +export default Console \ No newline at end of file diff --git a/smqtt-ui/src/pages/exception/403.vue b/smqtt-ui/src/pages/exception/403.vue index 08a3517f..c0fe7bdc 100644 --- a/smqtt-ui/src/pages/exception/403.vue +++ b/smqtt-ui/src/pages/exception/403.vue @@ -1,5 +1,5 @@ \ No newline at end of file -- Gitee From 44030c639da3227d3ce28c1619ebc058ffaea59c Mon Sep 17 00:00:00 2001 From: tangyiming Date: Fri, 9 Jul 2021 12:01:02 +0800 Subject: [PATCH 030/482] =?UTF-8?q?=E4=BC=98=E5=8C=96jvm=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 单列改双列,信息顺序调整,删除mock数据注释代码 --- smqtt-ui/package.json | 1 + smqtt-ui/src/main.js | 3 + .../src/pages/dashboard/console/Console.vue | 94 ++++++++++--------- 3 files changed, 55 insertions(+), 43 deletions(-) diff --git a/smqtt-ui/package.json b/smqtt-ui/package.json index 2ec8c6fa..f4a377d2 100644 --- a/smqtt-ui/package.json +++ b/smqtt-ui/package.json @@ -15,6 +15,7 @@ "clipboard": "^2.0.6", "core-js": "^3.6.5", "date-fns": "^2.14.0", + "echarts": "^4.9.0", "enquire.js": "^2.1.6", "highlight.js": "^10.2.1", "js-cookie": "^2.2.1", diff --git a/smqtt-ui/src/main.js b/smqtt-ui/src/main.js index 9c8b12b6..51029fd0 100644 --- a/smqtt-ui/src/main.js +++ b/smqtt-ui/src/main.js @@ -11,6 +11,7 @@ import Plugins from '@/plugins' import {initI18n} from '@/utils/i18n' import bootstrap from '@/bootstrap' import 'moment/locale/zh-cn' +import echarts from 'echarts' const router = initRouter(store.state.setting.asyncRoutes) const i18n = initI18n('CN', 'US') @@ -20,6 +21,8 @@ Vue.config.productionTip = false Vue.use(Viser) Vue.use(Plugins) +Vue.prototype.$echarts = echarts + bootstrap({router, store, i18n, message: Vue.prototype.$message}) new Vue({ diff --git a/smqtt-ui/src/pages/dashboard/console/Console.vue b/smqtt-ui/src/pages/dashboard/console/Console.vue index a4f3bf33..a66b63e8 100644 --- a/smqtt-ui/src/pages/dashboard/console/Console.vue +++ b/smqtt-ui/src/pages/dashboard/console/Console.vue @@ -23,8 +23,52 @@
系统信息
- -

{{ k }} : {{ v }}

+ + + +

+ smqtt: {{ jvmInfo.smqtt }} +

+

+ jdk_home: {{ jvmInfo.jdk_home }} +

+

+ jdk_version: {{ jvmInfo.jdk_version }} +

+

+ start_time: {{ jvmInfo.start_time }} +

+

+ thread.count: {{ jvmInfo["thread.count"] }} +

+
+ +

+ heap-max: {{ jvmInfo["heap-max"] }} +

+

+ heap-init: {{ jvmInfo["heap-init"] }} +

+

+ heap-used: {{ jvmInfo["heap-used"] }} +

+

+ heap-commit: {{ jvmInfo["heap-commit"]}} +

+

+ no_heap-max: {{ jvmInfo["no_heap-max"] }} +

+

+ no_heap-init: {{ jvmInfo["no_heap-init"] }} +

+

+ no_heap-used: {{ jvmInfo["no_heap-used"] }} +

+

+ no_heap-commit: {{ jvmInfo["no_heap-commit"] }} +

+
+
@@ -156,11 +200,6 @@ export default { this.dataSource = res.data // 设置默认的展示节点数据 this.optionsList =[...res.data] - - // - // this.dataSource = [...res.data,{"alias":"aaaa","host":"localhost"},{"alias":"bbbb","host":"1.1.2.1"}] - // this.optionsList =[...res.data,{"alias":"aaaa","host":"localhost"},{"alias":"bbbb","host":"1.1.2.1"}] - this.defaultNode = this.optionsList.length===0 ? undefined : this.optionsList[0]['host'] this.nodeInfo = res.data.slice(0,1) || [] @@ -186,50 +225,21 @@ export default { }, getConsoleInfo(host){ // console.log(host) - let jvm =`http://${host}:60000/smqtt/monitor/jvm` - let cpu =`http://${host}:60000/smqtt/monitor/cpu` - let counter =`http://${host}:60000/smqtt/monitor/counter` + let jvm = `http://${host}:60000/smqtt/monitor/jvm` + let cpu = `http://${host}:60000/smqtt/monitor/cpu` + let counter = `http://${host}:60000/smqtt/monitor/counter` let options = { - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, } - axios.get(jvm,options).then(res=>{ + axios.get(jvm, options).then(res => { this.jvmInfo = res.data }) - // this.jvmInfo = { - // "jdk_home": "C:\\Program Files\\Java\\jdk1.8.0_261\\jre", - // "thread.count": 19, - // "jdk_version": "1.8.0_261", - // "heap-used": "47.72MB", - // "no_heap-commit": "35.44MB", - // "no_heap-used": "33.81MB", - // "smqtt": "1.0.5", - // "start_time": "Thu Jul 08 20:01:04 CST 2021", - // "heap-commit": "197MB", - // "heap-max": "3.47GB", - // "no_heap-max": "-0KB", - // "no_heap-init": "2.44MB", - // "heap-init": "250MB" - // } axios.get(cpu,options).then(res=>{ this.cpuInfo = res.data }) - // this.cpuInfo = { - // "cSys": "1.83%", - // "idle": "80.1%", - // "iowait": "0%", - // "user": "78.27%", - // "cpuNum": 8 - // } axios.get(counter,options).then(res=>{ this.counterInfo = new Array(res.data) }) - // this.counterInfo = new Array({ - // "connect_size": 0, - // "write_size": "0KB", - // "write_hour_size": "0KB", - // "read_size": "0KB", - // "read_hour_size": "0KB" - // }) if (this.timer) { clearTimeout(this.timer) } @@ -237,8 +247,6 @@ export default { this.getConsoleInfo(host) }, 3000) } - - } } \ No newline at end of file -- Gitee From be156f2f3caeb6f382b95b693328b34e575f00bb Mon Sep 17 00:00:00 2001 From: luxurong Date: Fri, 9 Jul 2021 14:13:45 +0800 Subject: [PATCH 031/482] http --- .../src/test/java/ClusterNode1.java | 1 + .../core/http/actors/CpuHttpActor.java | 1 - .../core/http/actors/IsClusterActor.java | 28 +++++++++++++++++++ .../io.github.quickmsg.common.http.HttpActor | 3 +- 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java diff --git a/smqtt-bootstrap/src/test/java/ClusterNode1.java b/smqtt-bootstrap/src/test/java/ClusterNode1.java index 50c88de7..1d0082ba 100644 --- a/smqtt-bootstrap/src/test/java/ClusterNode1.java +++ b/smqtt-bootstrap/src/test/java/ClusterNode1.java @@ -17,6 +17,7 @@ public class ClusterNode1 { }) //netty childOptions设置 .highWaterMark(1000000) .reactivePasswordAuth((U, P) -> true) + .lowWaterMark(1000) .ssl(false) .sslContext(new SslContext("crt", "key")) diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java index 9cd7f9f6..cd30c630 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java @@ -6,7 +6,6 @@ import io.github.quickmsg.common.config.Configuration; import io.github.quickmsg.common.enums.HttpType; import io.github.quickmsg.common.http.HttpActor; import io.github.quickmsg.metric.category.CpuMetric; -import io.github.quickmsg.metric.category.JvmMetric; import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java new file mode 100644 index 00000000..b74df959 --- /dev/null +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java @@ -0,0 +1,28 @@ +package io.github.quickmsg.core.http.actors; + +import io.github.quickmsg.common.annotation.Router; +import io.github.quickmsg.common.config.Configuration; +import io.github.quickmsg.common.enums.HttpType; +import io.github.quickmsg.common.http.HttpActor; +import io.github.quickmsg.core.DefaultTransport; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; + +/** + * @author luxurong + */ +@Router(value = "/smqtt/is/cluster", type = HttpType.GET) +@Slf4j +public class IsClusterActor implements HttpActor { + + + @Override + public Publisher doRequest(HttpServerRequest request, HttpServerResponse response, Configuration httpConfiguration) { + return request + .receive() + .then(response.sendString(Mono.just(DefaultTransport.receiveContext.getConfiguration().getClusterConfig().getClustered().toString())).then()); + } +} diff --git a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor index cf3bac29..4463448f 100644 --- a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor +++ b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor @@ -7,4 +7,5 @@ io.github.quickmsg.core.http.actors.resource.StaticResourceActor io.github.quickmsg.core.http.actors.resource.LoginResourceActor io.github.quickmsg.core.http.actors.JvmHttpActor io.github.quickmsg.core.http.actors.CounterHttpActor -io.github.quickmsg.core.http.actors.CpuHttpActor \ No newline at end of file +io.github.quickmsg.core.http.actors.CpuHttpActor +io.github.quickmsg.core.http.actors.IsClusterActor \ No newline at end of file -- Gitee From c53c78ad188df1402c550d00f33e5d162309e426 Mon Sep 17 00:00:00 2001 From: luxurong Date: Fri, 9 Jul 2021 15:30:41 +0800 Subject: [PATCH 032/482] allow cors --- config.properties | 4 ++-- .../quickmsg/common/annotation/AllowCors.java | 13 ++++++++++ .../common/bootstrap/BootstrapKey.java | 11 --------- .../quickmsg/common/enums/HttpType.java | 5 ++++ .../core/http/HttpRouterAcceptor.java | 16 +++++++++++-- .../core/http/actors/AllowCorsHttpActor.java | 24 +++++++++++++++++++ .../core/http/actors/ClusterActor.java | 2 ++ .../core/http/actors/ConnectionActor.java | 2 ++ .../core/http/actors/CounterHttpActor.java | 2 ++ .../core/http/actors/CpuHttpActor.java | 2 ++ .../core/http/actors/IsClusterActor.java | 2 ++ .../core/http/actors/JvmHttpActor.java | 2 ++ .../core/http/actors/PublishActor.java | 2 ++ .../core/http/actors/SubscribeActor.java | 2 ++ .../actors/resource/LoginResourceActor.java | 2 ++ .../io.github.quickmsg.common.http.HttpActor | 3 ++- 16 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 smqtt-common/src/main/java/io/github/quickmsg/common/annotation/AllowCors.java create mode 100644 smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/AllowCorsHttpActor.java diff --git a/config.properties b/config.properties index 319da376..fd260b35 100644 --- a/config.properties +++ b/config.properties @@ -26,12 +26,12 @@ smqtt.tcp.username=smqtt smqtt.tcp.password=smqtt # 开启http smqtt.http.enable=true -# 开启http端口 -smqtt.http.port=60000 + # 开启http日志 smqtt.http.accesslog=true # 开启ssl smqtt.http.ssl.enable=false + # smqtt.http.ssl.crt = # smqtt.http.ssl.key = # 开启管理后台(必须开启http) diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/annotation/AllowCors.java b/smqtt-common/src/main/java/io/github/quickmsg/common/annotation/AllowCors.java new file mode 100644 index 00000000..c758da62 --- /dev/null +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/annotation/AllowCors.java @@ -0,0 +1,13 @@ +package io.github.quickmsg.common.annotation; + +import java.lang.annotation.*; + +/** + * @author luxurong + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface AllowCors { +} diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java index c9aec99c..fdbe639d 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/bootstrap/BootstrapKey.java @@ -36,8 +36,6 @@ public class BootstrapKey { public final static String BOOTSTRAP_HTTP_ENABLE = "smqtt.http.enable"; - public final static String BOOTSTRAP_HTTP_PORT = "smqtt.http.port"; - public final static String BOOTSTRAP_HTTP_ACCESS_LOG = "smqtt.http.accesslog"; public final static String BOOTSTRAP_HTTP_SSL_ENABLE = "smqtt.http.ssl.enable"; @@ -125,13 +123,4 @@ public class BootstrapKey { public static final String MASTER_SLAVE = "MASTER_SLAVE"; } - - - /*分割符号*/ - public static class SplitSymbol { - /*逗号*/ - public static final String COMMA = ","; - /*冒号*/ - public static final String COLON = ":"; - } } \ No newline at end of file diff --git a/smqtt-common/src/main/java/io/github/quickmsg/common/enums/HttpType.java b/smqtt-common/src/main/java/io/github/quickmsg/common/enums/HttpType.java index aeef3897..0fb428cd 100644 --- a/smqtt-common/src/main/java/io/github/quickmsg/common/enums/HttpType.java +++ b/smqtt-common/src/main/java/io/github/quickmsg/common/enums/HttpType.java @@ -22,6 +22,11 @@ public enum HttpType { */ PUT, + /** + * OPTIONS请求 + */ + OPTIONS, + /** * 文件流 */ diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/HttpRouterAcceptor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/HttpRouterAcceptor.java index f526005c..bf234031 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/HttpRouterAcceptor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/HttpRouterAcceptor.java @@ -1,9 +1,11 @@ package io.github.quickmsg.core.http; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Headers; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.http.HttpActor; +import io.netty.handler.codec.http.HttpHeaderNames; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import reactor.netty.http.server.HttpServerRequest; @@ -47,6 +49,10 @@ public class HttpRouterAcceptor implements Consumer { httpServerRoutes .delete(router.value(), handler); break; + case OPTIONS: + httpServerRoutes + .options(router.value(), handler); + break; case GET: default: httpServerRoutes @@ -59,14 +65,20 @@ public class HttpRouterAcceptor implements Consumer { private Publisher doRequest(HttpServerRequest httpServerRequest, HttpServerResponse httpServerResponse, HttpActor httpActor, Router router) { Header header = httpActor.getClass().getAnnotation(Header.class); Headers headers = httpActor.getClass().getAnnotation(Headers.class); + AllowCors allowCors = httpActor.getClass().getAnnotation(AllowCors.class); if (router.resource() && !httpConfiguration.getEnableAdmin()) { return Mono.empty(); - } - else{ + } else { Optional.ofNullable(header) .ifPresent(hd -> httpServerResponse.addHeader(hd.key(), hd.value())); Optional.ofNullable(headers) .ifPresent(hds -> Arrays.stream(hds.headers()).forEach(hd -> httpServerResponse.addHeader(hd.key(), hd.value()))); + Optional.ofNullable(allowCors) + .ifPresent(cors -> { + httpServerResponse.addHeader(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "*"); + httpServerResponse.addHeader(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "*"); + httpServerResponse.addHeader(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + }); return httpActor.doRequest(httpServerRequest, httpServerResponse, httpConfiguration); } } diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/AllowCorsHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/AllowCorsHttpActor.java new file mode 100644 index 00000000..b5609012 --- /dev/null +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/AllowCorsHttpActor.java @@ -0,0 +1,24 @@ +package io.github.quickmsg.core.http.actors; + +import io.github.quickmsg.common.annotation.AllowCors; +import io.github.quickmsg.common.annotation.Router; +import io.github.quickmsg.common.config.Configuration; +import io.github.quickmsg.common.enums.HttpType; +import io.github.quickmsg.common.http.HttpActor; +import org.reactivestreams.Publisher; +import reactor.core.publisher.Mono; +import reactor.netty.http.server.HttpServerRequest; +import reactor.netty.http.server.HttpServerResponse; + +/** + * @author luxurong + */ + +@Router(value = "/**", type = HttpType.OPTIONS) +@AllowCors +public class AllowCorsHttpActor implements HttpActor { + @Override + public Publisher doRequest(HttpServerRequest request, HttpServerResponse response, Configuration configuration) { + return Mono.empty(); + } +} diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ClusterActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ClusterActor.java index 1da62550..54ce13a4 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ClusterActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ClusterActor.java @@ -1,6 +1,7 @@ package io.github.quickmsg.core.http.actors; import com.alibaba.fastjson.JSON; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; @@ -20,6 +21,7 @@ import reactor.netty.http.server.HttpServerResponse; @Router(value = "/smqtt/cluster", type = HttpType.POST) @Slf4j @Header(key = "Content-Type", value = "application/json") +@AllowCors public class ClusterActor implements HttpActor { diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ConnectionActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ConnectionActor.java index 8186476b..2403a57b 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ConnectionActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/ConnectionActor.java @@ -1,6 +1,7 @@ package io.github.quickmsg.core.http.actors; import com.alibaba.fastjson.JSON; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; @@ -20,6 +21,7 @@ import reactor.netty.http.server.HttpServerResponse; @Router(value = "/smqtt/connection", type = HttpType.POST) @Slf4j @Header(key = "Content-Type", value = "application/json") +@AllowCors public class ConnectionActor extends AbstractHttpActor { @Override diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java index 8600431a..f6de1e55 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CounterHttpActor.java @@ -1,5 +1,6 @@ package io.github.quickmsg.core.http.actors; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; @@ -19,6 +20,7 @@ import reactor.netty.http.server.HttpServerResponse; @Router(value = "/smqtt/monitor/counter", type = HttpType.GET) @Slf4j @Header(key = "Content-Type", value = "application/json") +@AllowCors public class CounterHttpActor implements HttpActor { @Override diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java index cd30c630..84b132af 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/CpuHttpActor.java @@ -1,5 +1,6 @@ package io.github.quickmsg.core.http.actors; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; @@ -19,6 +20,7 @@ import reactor.netty.http.server.HttpServerResponse; @Router(value = "/smqtt/monitor/cpu", type = HttpType.GET) @Slf4j @Header(key = "Content-Type", value = "application/json") +@AllowCors public class CpuHttpActor implements HttpActor { @Override diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java index b74df959..e5120333 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/IsClusterActor.java @@ -1,5 +1,6 @@ package io.github.quickmsg.core.http.actors; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; import io.github.quickmsg.common.enums.HttpType; @@ -16,6 +17,7 @@ import reactor.netty.http.server.HttpServerResponse; */ @Router(value = "/smqtt/is/cluster", type = HttpType.GET) @Slf4j +@AllowCors public class IsClusterActor implements HttpActor { diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java index 9fd3b4c8..81752ba4 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/JvmHttpActor.java @@ -1,5 +1,6 @@ package io.github.quickmsg.core.http.actors; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; @@ -19,6 +20,7 @@ import reactor.netty.http.server.HttpServerResponse; @Router(value = "/smqtt/monitor/jvm", type = HttpType.GET) @Slf4j @Header(key = "Content-Type", value = "application/json") +@AllowCors public class JvmHttpActor implements HttpActor { @Override diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/PublishActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/PublishActor.java index 2a93ffe7..ac610504 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/PublishActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/PublishActor.java @@ -1,5 +1,6 @@ package io.github.quickmsg.core.http.actors; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; import io.github.quickmsg.common.enums.HttpType; @@ -17,6 +18,7 @@ import reactor.netty.http.server.HttpServerResponse; */ @Router(value = "/smqtt/publish", type = HttpType.POST) @Slf4j +@AllowCors public class PublishActor extends AbstractHttpActor { diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/SubscribeActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/SubscribeActor.java index 72e11f8e..545a2d63 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/SubscribeActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/SubscribeActor.java @@ -1,6 +1,7 @@ package io.github.quickmsg.core.http.actors; import com.alibaba.fastjson.JSON; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; @@ -20,6 +21,7 @@ import reactor.netty.http.server.HttpServerResponse; @Router(value = "/smqtt/subscribe", type = HttpType.POST) @Slf4j @Header(key = "Content-Type", value = "application/json") +@AllowCors public class SubscribeActor implements HttpActor { @Override diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/resource/LoginResourceActor.java b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/resource/LoginResourceActor.java index ce4d18d8..72f3d4de 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/resource/LoginResourceActor.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/http/actors/resource/LoginResourceActor.java @@ -1,6 +1,7 @@ package io.github.quickmsg.core.http.actors.resource; import com.alibaba.fastjson.JSON; +import io.github.quickmsg.common.annotation.AllowCors; import io.github.quickmsg.common.annotation.Header; import io.github.quickmsg.common.annotation.Router; import io.github.quickmsg.common.config.Configuration; @@ -26,6 +27,7 @@ import java.util.Optional; @Router(value = "/auth/login", type = HttpType.POST,resource = true) @Slf4j @Header(key = "Content-Type", value = "application/json") +@AllowCors public class LoginResourceActor implements HttpActor { @Override diff --git a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor index 4463448f..8af187ee 100644 --- a/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor +++ b/smqtt-core/src/main/resources/META-INF/services/io.github.quickmsg.common.http.HttpActor @@ -8,4 +8,5 @@ io.github.quickmsg.core.http.actors.resource.LoginResourceActor io.github.quickmsg.core.http.actors.JvmHttpActor io.github.quickmsg.core.http.actors.CounterHttpActor io.github.quickmsg.core.http.actors.CpuHttpActor -io.github.quickmsg.core.http.actors.IsClusterActor \ No newline at end of file +io.github.quickmsg.core.http.actors.IsClusterActor +io.github.quickmsg.core.http.actors.AllowCorsHttpActor \ No newline at end of file -- Gitee From 3edd1037c38aa8f30606e6b4b4444768ed5a1255 Mon Sep 17 00:00:00 2001 From: luxurong Date: Fri, 9 Jul 2021 16:15:48 +0800 Subject: [PATCH 033/482] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.properties | 11 ++---- .../io/github/quickmsg/AbstractStarter.java | 21 ++++------- .../src/test/java/ClusterNode1.java | 5 +-- .../io/github/quickmsg/core/Bootstrap.java | 5 +++ smqtt-core/src/main/resources/logback.xml | 36 ++++++++++++++----- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/config.properties b/config.properties index fd260b35..647d9370 100644 --- a/config.properties +++ b/config.properties @@ -1,5 +1,5 @@ # 日志级别 ALL|TRACE|DEBUG|INFO|WARN|ERROR|OFF -smqtt.log.level=info +smqtt.log.level=INFO # 开启tcp端口 smqtt.tcp.port=1883 # 高水位 @@ -26,16 +26,14 @@ smqtt.tcp.username=smqtt smqtt.tcp.password=smqtt # 开启http smqtt.http.enable=true - # 开启http日志 smqtt.http.accesslog=true # 开启ssl smqtt.http.ssl.enable=false - # smqtt.http.ssl.crt = # smqtt.http.ssl.key = # 开启管理后台(必须开启http) -smqtt.http.admin.enable = true +smqtt.http.admin.enable=true # 管理后台登录用户 smqtt.http.admin.username=smqtt # 管理后台登录密码 @@ -48,7 +46,6 @@ smqtt.cluster.url=127.0.0.1:7771,127.0.0.1:7772 smqtt.cluster.port=7771 # 节点名称 smqtt.cluster.node=node-1 - # 数据库配置(选配) db.driverClassName=com.mysql.jdbc.Driver db.url=jdbc:mysql://127.0.0.1:3306/smqtt?characterEncoding=utf-8&useSSL=false&useInformationSchema=true&serverTimezone=UTC @@ -62,7 +59,6 @@ db.maxActive=300 db.maxWait=60000 # 回收空闲连接时,将保证至少有minIdle个连接 db.minIdle=2 - # redis配置(选配) # 单机模式:single 哨兵模式:sentinel 集群模式:cluster redis.mode=single @@ -78,10 +74,8 @@ redis.pool.min.idle=8 redis.pool.conn.timeout=3000 # 连接池大小 redis.pool.size=10 - # 单机配置 redis.single.address=127.0.0.1:6379 - # 集群配置 redis.cluster.scan.interval=1000 redis.cluster.nodes=127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005 @@ -90,7 +84,6 @@ redis.cluster.retry.attempts=3 redis.cluster.slave.connection.pool.size=64 redis.cluster.master.connection.pool.size=64 redis.cluster.retry.interval=1500 - # 哨兵配置 redis.sentinel.master=mymaster redis.sentinel.nodes=127.0.0.1:26379,127.0.0.1:26379,127.0.0.1:26379 \ No newline at end of file diff --git a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java index 013d66ac..e4c02904 100644 --- a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java +++ b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java @@ -6,7 +6,6 @@ import io.github.quickmsg.common.cluster.ClusterConfig; import io.github.quickmsg.common.config.SslContext; import io.github.quickmsg.common.environment.EnvContext; import io.github.quickmsg.common.utils.IPUtils; -import io.github.quickmsg.common.utils.LoggerLevel; import io.github.quickmsg.common.utils.PropertiesLoader; import io.github.quickmsg.core.Bootstrap; import io.netty.channel.WriteBufferWaterMark; @@ -32,10 +31,6 @@ public abstract class AbstractStarter { private static final String DEFAULT_AUTH_USERNAME_PASSWORD = "smqtt"; - public static void start(Function function) { - start(function, null); - } - public static void start(Function function, String path) { path = path == null ? DEFAULT_PROPERTIES_LOAD_CONFIG_PATH : path; @@ -79,12 +74,8 @@ public abstract class AbstractStarter { .map(Boolean::parseBoolean).orElse(false); - String loggerLevel = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_LOGGER_LEVEL, function.apply(BootstrapKey.BOOTSTRAP_LOGGER_LEVEL))) - .map(String::valueOf).orElse(null); - - if (loggerLevel != null) { - LoggerLevel.root(Level.toLevel(loggerLevel)); - } + Level loggerLevel = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_LOGGER_LEVEL, function.apply(BootstrapKey.BOOTSTRAP_LOGGER_LEVEL))) + .map(Level::toLevel).orElse(null); Bootstrap.BootstrapBuilder builder = Bootstrap.builder(); builder.port(port) @@ -96,7 +87,9 @@ public abstract class AbstractStarter { .lowWaterMark(lowWaterMark) .envContext(params) .highWaterMark(highWaterMark); - + if (loggerLevel != null) { + builder.rootLevel(loggerLevel); + } if (ssl) { String sslCrt = Optional.ofNullable(params.obtainKeyOrDefault(BootstrapKey.BOOTSTRAP_SSL_CRT, function.apply(BootstrapKey.BOOTSTRAP_SSL_CRT))) @@ -177,7 +170,7 @@ public abstract class AbstractStarter { } Bootstrap bootstrap = builder.build(); - bootstrap.doOnStarted(AbstractStarter::printUIUrl).startAwait(); + bootstrap.doOnStarted(AbstractStarter::printUiUrl).startAwait(); } /** @@ -185,7 +178,7 @@ public abstract class AbstractStarter { * * @param bootstrap 启动类 */ - public static void printUIUrl(Bootstrap bootstrap) { + public static void printUiUrl(Bootstrap bootstrap) { if (bootstrap.getHttpOptions().getEnableAdmin()) { Integer port = bootstrap.getHttpOptions().getHttpPort(); log.info("\n-------------------------------------------------------------\n\t" + diff --git a/smqtt-bootstrap/src/test/java/ClusterNode1.java b/smqtt-bootstrap/src/test/java/ClusterNode1.java index 1d0082ba..ff21e86d 100644 --- a/smqtt-bootstrap/src/test/java/ClusterNode1.java +++ b/smqtt-bootstrap/src/test/java/ClusterNode1.java @@ -1,3 +1,4 @@ +import ch.qos.logback.classic.Level; import io.github.quickmsg.common.cluster.ClusterConfig; import io.github.quickmsg.common.config.SslContext; import io.github.quickmsg.core.Bootstrap; @@ -9,6 +10,8 @@ public class ClusterNode1 { public static void main(String[] args) throws InterruptedException { Bootstrap bootstrap = Bootstrap.builder() + .rootLevel(Level.INFO) + .wiretap(false) .port(8555) .websocketPort(8999) .options(channelOptionMap -> { @@ -17,12 +20,10 @@ public class ClusterNode1 { }) //netty childOptions设置 .highWaterMark(1000000) .reactivePasswordAuth((U, P) -> true) - .lowWaterMark(1000) .ssl(false) .sslContext(new SslContext("crt", "key")) .isWebsocket(true) - .wiretap(false) .httpOptions(Bootstrap.HttpOptions.builder().enableAdmin(true).ssl(false).accessLog(true).build()) .clusterConfig( ClusterConfig.builder() diff --git a/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java b/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java index 4c51116f..1d9aca61 100644 --- a/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java +++ b/smqtt-core/src/main/java/io/github/quickmsg/core/Bootstrap.java @@ -1,5 +1,6 @@ package io.github.quickmsg.core; +import ch.qos.logback.classic.Level; import io.github.quickmsg.common.auth.PasswordAuthentication; import io.github.quickmsg.common.cluster.ClusterConfig; import io.github.quickmsg.common.config.SslContext; @@ -81,6 +82,9 @@ public class Bootstrap { private Consumer started; + @Builder.Default + private Level rootLevel = Level.INFO; + private MqttConfiguration initMqttConfiguration() { MqttConfiguration mqttConfiguration = defaultConfiguration(); @@ -138,6 +142,7 @@ public class Bootstrap { public Mono start() { MqttConfiguration mqttConfiguration = initMqttConfiguration(); MqttTransportFactory mqttTransportFactory = new MqttTransportFactory(); + LoggerLevel.root(rootLevel); return mqttTransportFactory.createTransport(mqttConfiguration) .start() .doOnError(Throwable::printStackTrace) diff --git a/smqtt-core/src/main/resources/logback.xml b/smqtt-core/src/main/resources/logback.xml index e680a30e..6cdc1e2f 100644 --- a/smqtt-core/src/main/resources/logback.xml +++ b/smqtt-core/src/main/resources/logback.xml @@ -1,3 +1,19 @@ + + @@ -7,17 +23,20 @@ - - testFile.log - false + + + smqtt.%d{yyyy-MM-dd}.log + 30 + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n -` - - - + + + + + @@ -26,8 +45,9 @@ - + + -- Gitee From c1d9d13e1d774b06172699d7e77e8596b1633859 Mon Sep 17 00:00:00 2001 From: luxurong Date: Fri, 9 Jul 2021 16:21:03 +0800 Subject: [PATCH 034/482] =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=BA=A7=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- smqtt-core/src/main/resources/logback.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smqtt-core/src/main/resources/logback.xml b/smqtt-core/src/main/resources/logback.xml index 6cdc1e2f..b819e3e0 100644 --- a/smqtt-core/src/main/resources/logback.xml +++ b/smqtt-core/src/main/resources/logback.xml @@ -25,7 +25,7 @@ - smqtt.%d{yyyy-MM-dd}.log + logs/smqtt.%d{yyyy-MM-dd}.log 30 -- Gitee From 554223c6161828bb7e9fec3a2084c3a7b609090b Mon Sep 17 00:00:00 2001 From: luxurong Date: Fri, 9 Jul 2021 17:00:35 +0800 Subject: [PATCH 035/482] log --- .../java/io/github/quickmsg/AbstractStarter.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java index e4c02904..9629a491 100644 --- a/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java +++ b/smqtt-bootstrap/src/main/java/io/github/quickmsg/AbstractStarter.java @@ -179,15 +179,16 @@ public abstract class AbstractStarter { * @param bootstrap 启动类 */ public static void printUiUrl(Bootstrap bootstrap) { + String start = "\n-------------------------------------------------------------\n\t"; + start += String.format("Smqtt mqtt connect url %s:%s \n\t", IPUtils.getIP(), bootstrap.getPort()); if (bootstrap.getHttpOptions().getEnableAdmin()) { Integer port = bootstrap.getHttpOptions().getHttpPort(); - log.info("\n-------------------------------------------------------------\n\t" + - "Application UI is running AccessURLs:\n\t" + - "Http Local url: http://localhost:{}/smqtt/admin" + "\n\t" + - "Http External url: http://{}:{}/smqtt/admin" + "\n" + - "-------------------------------------------------------------" - , port, IPUtils.getIP(), port); + start += String.format("Smqtt-Admin UI is running AccessURLs:\n\t" + + "Http Local url: http://localhost:%s/smqtt/admin" + "\n\t" + + "Http External url: http://%s:%s/smqtt/admin" + "\n" + + "-------------------------------------------------------------", port, IPUtils.getIP(), port); } + log.info(start); } } -- Gitee From 23285c9f93a87155d47867e5ad13dfbdee7994f1 Mon Sep 17 00:00:00 2001 From: tangyiming Date: Fri, 9 Jul 2021 21:12:31 +0800 Subject: [PATCH 036/482] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 优化console页外观 --- smqtt-ui/src/assets/img/cluster.png | Bin 0 -> 9117 bytes smqtt-ui/src/assets/img/cpu.png | Bin 8334 -> 4137 bytes smqtt-ui/src/assets/img/csys.png | Bin 0 -> 7650 bytes smqtt-ui/src/assets/img/idle.png | Bin 0 -> 7872 bytes smqtt-ui/src/assets/img/iowait.png | Bin 0 -> 9097 bytes smqtt-ui/src/assets/img/jvm.png | Bin 10214 -> 7629 bytes smqtt-ui/src/assets/img/user.png | Bin 0 -> 8809 bytes .../src/pages/dashboard/console/Console.vue | 91 ++++++++++++++---- smqtt-ui/src/services/api.js | 1 + smqtt-ui/src/services/smqtt.js | 9 +- 10 files changed, 79 insertions(+), 22 deletions(-) create mode 100644 smqtt-ui/src/assets/img/cluster.png create mode 100644 smqtt-ui/src/assets/img/csys.png create mode 100644 smqtt-ui/src/assets/img/idle.png create mode 100644 smqtt-ui/src/assets/img/iowait.png create mode 100644 smqtt-ui/src/assets/img/user.png diff --git a/smqtt-ui/src/assets/img/cluster.png b/smqtt-ui/src/assets/img/cluster.png new file mode 100644 index 0000000000000000000000000000000000000000..889e59c20fc11d12d12ffff74c3f27cfb30e3145 GIT binary patch literal 9117 zcmcgShdZ0!_x7q)ifYxUM2k>_n5jJyqqU{A8&S3QRwdLfi4`$QjcR?Aq|_?4N-C&m zZCa!Do>i^>((fPfd+zhT_de%2_rC8L_uMmX@?A4S4z??7G&D3EFe5z+YCk}I`dOH$ zZv$VxA{rV|8knB8RhZ*e5%U)rW&b{I3-b-dc?R&bW@(hny*g$o z1X~EIMhe#2N($baa9{rPhvJSCaMQRivNl4W1A?mEIx>g73&N&$ME}5=6`Y({Ta`T9 zcmmIDidnaYQz~>>s?gl|=I{ZW82bI-<_sm}b^Eo*=NPx|7sZm5SUs_&KkUHjQLAtW z@f@D}bP$4s-1`4pZ{ZE|S8 zSlqn!Yo32xx>%%l5f<4*%Hb`eSPkH#A*YXQ!2pL*cU)vpj_L1fT^izhI8#I$rh+~M z_AVIvS}*BSP2(+f*j$0yI$(Ad7P$4r{r;!NgQmoBzAw%5R@MYXEvYp&F?1CNcbU$^ zPu0Qai`9hj+M?EC90;?l^V+8eKCVI<41e zZYe~g%$)DGge{3X$M-3BWB%mB4I0?V1+hXK72&M|p924TftCZ65~=0JiQL6kbi%ym z&0|@+Biw=4*R&qL!5r!4(hqJ10&{AywWh}WuuadpdOHh89-WDAFy zC;)mF0zT8rkcH)Oh<)dYK@J_r3>R*0;G6`;)Wd`E86eE|*8l#3t7St;{nNPauM_N7DU4icSNuPuuQv*|=vHGQ`hEw=CgjxO@!d*hlwHbDMsaWw`mJbFx$9YWg zuK=n=9{^Lgky$~sCF#aOj4zQe1803Mqjh!S;Z!U(NVNne3LhOWsEbPKPvZezOg;FE z4^;XKfUoJ4>Ns}8a|?%@dcn|sorlcyKhvAHa^F@(HWlcsPQFn2LiSsfNR1rH-<#q| z{#!My@HgB3iRR}(%~u%UyT!g3*?3UL!Ot6J0911M*_ju?Gd1qN)RP*vkYZYuah;cc zgYO;6>n<>Szv-VFR^C0T%d%p8_bv(Z86hoGV8lPvMl{R}ZnNQ0U(CPH#ZO4s3nG^EO>6dV}TA%kFbeCxmPt#V{PQ_l6oir^a?id zrT<>>y&b|nruMRqGjVT3=9j1xn#9(e7xa`{XA|`hXw2%|_gNl5)jXunUd(CYiOZm$ z)u)B`dL2ex4#qKc_ZwElH*S}Q>%xn2Kvxen2JRyFlAKXkydt}zYp*D)YKESL4ymt>hR|ZYCX#$TL6o zaloiBd~D^`YrgqSk?8if@N~X zvcPhaejnx+{ZS)+!2utkaOsMFEp+p1#Xlj##Y6)SufF@OHC`{qz+yUhl_UOv_1DO1G9?c z#%CwFb2@;7yLi}ZKiBSk4rn0qmF%v#z)uHy85gK7_XE6i%WEl<+5xRtZXJByMx+^L z6RdW?Q4KaOLZpyE7viN*(C)4h6Zuq=PlIoAAzcbY~iJfLu)E6Rs=ml^*_!~8<0SC zSfmiwpTdi%-T#JEUxNw{c=-kQ|A>-M+NI=guW`8C?@wGyfweGK=iRGScG|=e2ff~< zkC`i_o`)y3cft-5(wPFb&y22aQJFk%PdgSh^cUP z=X?dH9zQNqA>*!QS2MuG?f~J!1u&+G?)T>|@5OC6YH|3|SZhG=RmxS!hBeH7Hr8YB zDP!cXj%aB2`c_)|h1;&E4hAro8G5?v|`8b?e5u`Q$cdJvN)WQLWPN#qCvz@eaN* zb&ueyi6D$9?c_?yLsZB5dN7Px#iJ#q^7nbL^5D7Zk5aKu@Q7Hvz3r`+f0@lNqn1Gt zP6OnLRr~vhNP%Z;~kZ!UG1o`V1LOCK{78*RFDh9WL~G@)md=>YalOn!z|%j zvgau$d_*!?POz>MnjY2tMnO|rf7t3%tqdY+KIR_PM_qB-ce*|B{gW3TY$my@Li^o@ z6#nPgL9`!x%*l)L3qSe3KZAKau-$vSH@PqE!06s#+)|8X5I9!|Z#@~va}72towa4yg;mZfNrO^MPWIi^4Vg!b6s1f9;bd(PtX8xQzuz7u@{-b>n4@g7@s zc&jegTMpK|%~PnHt5NXO=ZDq6^2h!H7QOE%5MO@Kf`O8ZeQyU~1x{u+!R#n_U>U%6VU(i ztdA4BQRd*r8{@7O5VDencHg~DxV@mZFZRC>q@T?q_)Z4s9N0Gy-2EkQ&HUU5tS+^D z`UKVT*N+!jg)^hn`YitrH#y_^Bt-QA0ohLhe^5(by0Jf>9K<+*TF zofuFjy8tboJ`z6YFjkXA`x$)#i{}Ev0uRp}SrLHQvB7*ELp%4&gO#iIFEPnLf|k2{ zAUT{xYX@poxJgGzHo!$3xnPe+ayU5q&q*U-?0`2L*uH9_IU0>1L0M6{4XRAUZF4uCeB41DO?* zH6Qj3F!v;e_9}}1oqn!x1p#o0$CkY;*PzBO!gDad#f^r?q)$35=pkoHRITwF00it$ zm(5KD0Hl8{|I8sEhdu+kYbootJkD`mSVIym50tfPJ(ETnVGNX=wcLy)dR}nQ)ps zaOPg$0(44FRMYX^T?9vN|qamuLV5t%Gugk`)hP5`bF?S^*I4?X0 zFKASPQ>eC~*1$dk$oNgoY1d4^$D{(YyU#8Aw?BWM)op^zkZ&`}9gTT;^_gYHKq2w` zJ}PxN|M-;Y1}jjQ&+>MuiA~gf>^W_Hcna<)Bwj!)^%m-M-3eIwqBUjz2lDRs5Rfd! zXN^8L-U$d)tAz1*rd4pJBr;v|MZv<`0oSPhW&6^FL~fPEE@sd#Ej#!hdKX^$MBN8_}>8$qKR3owf=! zv=5k{?auC*&I1(}uzB%|3;rxyy!YE+lRWO6i0Vjq#z>T$$kvD-Hl-!+BbtY!9&^}FWjr9t z0t*BwdOKV)ZD-sQyH9BF6&F|1ky(0Pd|(nUT!9*qySnc^vp9-4&J={z3Z4Ew(U=qh zo9=r%wwESNBBr)JB??vwmL9;aCkFzZb?KgAFwlc=N18eq>kLwyw+pEn$kdaVj;fn< zX?2xEXf$v-XKP%1DiG)KKCr%(J^EcV?cyy3L*^sh{2I^>bLkHE`G|WqB(_A@y@;_F1MdsqK^;&-9-c3+wpY`p9$G4YL(k6qUfJetMMsM}TsK{aMtFO5jtAt9Y&CJ%h%VNvy+ zm0PnG-=5-WE^{}F$l13#2aaZ>cqpq5wj5`NJ+3ziBkHwlq%cs}V3dmlS1eYr z{P(;J`Mx-pPxQR>1t*Ffg8$iSeZB17X$shwhF$C&wjhwLaFCnvXwG{Q_OVBO^}!r4 zLgXP%0TvpJ3Bo;jvwJjbbL;sVrPKV`s){U7V`oADN#PPCk^IiUV*j~M*j4+^%yMvc z^jH5XILCfK$4+ssThHdSt@hZD$hck4B@{vYm&z)d^#t=DLO{vPF8#GE-v1 z&sNe4j;(lhBf&!u|C{p~yZR)Vuy1{u6P{T~`f6gffb$q@4xex?K1}bv5(g^r!?cN? zZZr%AxneJQP2FZFOk#Kpk;Cfom;uHV&)#Yzo#S4sh6cVBfK>FyO+TANxAJ3qbU&WD z23MT95Lxa_y~9Y^-Q1i5#Ia4!;CnRdl}B+44Nv4|M*BhC3(D4TOhT|A7`9JSIfB{V zS(XbQm|Chvebm|nwi{G@o8nxbN4e*cl<>s*HDms=Ct{>X%Se*tCb{ycG2MLArrmB@ zV5d4*iG5Y`;06}8)#8DJy4&Bx4A_D^ykaMBa5xm$=wjn*TT!Gb0NdiA8f8+(<1?C5MYlX9zx^gQ@}r4V-9z zBf>mIe)^Hv?{zfz)#szQvP)hDVyTfYL}9j+r0b2ovRcJI(3{|;18*J^v)JSi_evTC z2ttD-Hj&w04g2}%fOe6en3Du3lQ8<_pO?$DYfgtB!p}*-_+cPSD`sf^O3%`IL)fi^ zvKusL#e+~Q*;ksgf(JMW2QWc{|4K{+gS^(SGFd# zAb|+mlEc&4(ACb0EvDFo3F_&bvCA$?cDeU#s%d?+KDy+{*paqUai8F` zi)h>?%@c+gzDmZXDH65L-2{K_1w1jexzsK7;jsI0+W_HccPcX>zoc;&-9YoPTmG`W z7mDNj=t?pu|6{(3X%~Xih&LJ%?N!#q%!N_>VWKilRvT8Yi0Ws;BUUwFrcQOI?TnZC zPq2h^C=9aYJ8+MeF9J)~e;MiT)iQ?d2Y`VjVULf{bw>S`fe;S9cs5V}N` zcWs`6{E?tfbN_EX^=rtywBz+ImokOUavBAGgwWLh+Pb~I`TrzvX_KI2PrA-ZnYNPj z2Qz(MfKWIaBp%zVEk-@BaEfvmKSm<{j^l!a3oW~OS+}nRjUX13!Ev?cJ0(Y3gop#( z9I19yH++Po)r51xI5)!3qKLZWxj3Uv6g6Go`1t_QO9u{9>9ud|H-h=OO}DcpSJGZT ztqEfjeSmeVXIt2-iF!SeEb92%<_ZlV~}zS8iW!;fsDG|MdbXYbc6%kc6PAM2;64z>Yu^NQ0Hb- z=W++kd5VvkYIluK*V;ReUJ^~&WUQc3U?d<52A2elZ|mIF`~;;dBr2QwM5JIDnY;)( z2LgzkxWP{(3@f{8=!+eAkVU{1uE0me1MV#9QK*3VQt9iGu_nnNK@^z2D4Pfi7y6fm zdAQxE(lD$BQ_;0Fv8cnbBNSr8D{&1RzqhWfXElIKrtQni4Hr^#ax8+xaV|2;=9>{r z%vx3`ewd2Qlo{e2wN3x=y`sL`ewbR_ih_YO{k|_vKeIR$&-k%MEl`v+K3v-*$ zpigTI{G&HR$K(;oly^yMEa4x;b0&oKx6Ny?5S6o74uV4+O9&gpQ#+U4xq#(^OiL!< zSM~!|i3%Em4-%$`pKdwG$uJ!|;%`=|a3W*B$weDre#`5=URzUxbmrR(MZ> zF*98+SJf+JMiMu*Bmcv)Zc+}Jyq0gB1EWQXW8AF&2wVEHL2^Ly&?%w`n<%vgrmnuX zcgaf%!K1ERMb-5jkSetBugO20(?3FgkpCbfv-irK|3UOy+8kj2AmTI%w)+1d;?7t) zBj5l_tQ8?`nk3m^O(;JcYjU%!s|F9SOqA1DxaVG^p^lr13N7vfjp4rO_aTgUHDX$< zDVu08N{}{_I{ts2$VUbNVnxkQ;Dms*>59_^ScDeKyvH@$mO`FvX;~Q6O(u4w`?j}^!{EqD&mRutf88Lo;*i2t=;wBYIBs@H|jM8T~`uf9VLS+QNVZ?v(z4QIM*7cM1y3Zis+h)@hTa=Rj5&&CU7bb2y z7VFzP@#;+Uk%wg1!_0#*a*<817PR&Gd`C$Uvg+||<jU|Yx7YdQo+%S`FT^|O6_qdQuspqF9#V`U3bC|V=<88*ixh2T%XT$@c#f;v0ttW} z@AbZ$1hVjP8}9Ve%D!q z9h=(IlQ#+f_Op6#5c(O#}gXoevlT)R@BALOT}s zt{y3jxZM&>PDQhHHp58*>|!<25RqUmWUg|fGGZAe*rqV?tMwAYGX8&M>|n-Vo!v8P zftXdyzTvqegjc?K2=tPeBn5l=lsiMVY*Pccf#wLcyNtQ&N<%|$_wO!1+~9>@huZQ4 za@O+5xl*fFNurF#KpxF#O`ZUZXYYQ#{U$y;TbH~k3FONi1Fk8|5CwI@kS?>9eama8 z5a;AjSu<)noc*F&x0>@E@R`G>0b?~MmU(J`8}i+lP59DrssX|&=V4R%{U%1gGoD|| zK_(pd@m~dd8x?bba)i%XuyN7zmwu~hGm7dmmAn9Nf9&g}*7o5iADA4CvktjW3Ud_o zt719kb!!Bh>swxC3u&cuV1tyEY2k@OQ6r|r3VCacN<|VWxF`;4&_kdG7hgcK+ zSn6j=+1L)$@jEt(sB7J|n@Hn=GZWVoytN%{i%EVdMSJh@vfqmTc}xPMjDfV=|Po*XMGu zR9$1{eQmkkt8fJ9Wpf*=w|NF@d3m2^v*bSuGkGv3uEs<{mp?*_(i`pXsK#CQI$o91 z+DmR9we};P%dUMiE0{IdrspZyY}jl%>fd^JTT(SdcX{I=9b8BQ)OF8&PZc=LNLZpv z&L6qn7T|2(AnN#Y;-w6w?{;e%K2W6ia`2r;C`6Qp=7b)*E$Or1D=|S)^_^QnuU{zN zgJO;kykjYE49-=Bq=#kyYkJcT!@3;@U{shs_{Z`D#NoD_bVg9)V#$9LGzy-{d=aQB z5bT?{AxTZxMwn6fqM*8jEul%5c#xrXG%B3SsHGjdEQ^ailTNRy6)B0&%k zlp-Zm3j)%M6cG>zQm*yo{@#1*-M`??Su?ZN>{;uab@t4e*$L*RMx1PdYybdo;;;sm zjG6XNN5C0lja_X40C4T#4D_r+UAFR59`LAh-y1{iXhL%lLl<310I4Gw=7$1M3rT? z*u{VH>4);`AEEV|c~C!F9W3w-eVdlEM?d+&1r?EtezS3K+<*xR3y30{6g^#Ol zbZK123e(I@Vl|GQE-i@NJbaAoN)o(RNC7^DAs}x>WtDr^BmyiPaJ!wm^07PL3>=9o z+f!T*-iMzL`ZEj^Q$n>O?VfCgNYGsu8_Nf+d0jietcSNzkmjC5%5Q$1-n6qL1~3=T zqk1U}(5Dt>uUglWtZG^XzEOGO@2DG!7Vvzqo=NGt+2&OIFm%`mrRnG&-jwA7MdE+S z#S$yWass3z+N&$;zB%B-x%ESvzkLs#bwq_V)Q=Po;`gW7>?-cKQT7QIOd^*XcrnU4 zop&Q|wKy(S)=(Z_TPnN|z!!GI$QK?q&LsH6q2$Bj=Y z4nC(OCi^bo7c+jJq3yXC)D4^o?638^KV^xR1MP(TKRiUwRB9Tyi41&}T*XUW|NwTUQ z<>G>`bG$!ZdK#;)Y4-~E*h1*g&0i@qrMlpT3icY=r_uJ+spTa7UNBitAVwKZ{ToU6 zw~KNQBnK&CUjnm0AVe87DbnmL4!?TcT1` z7PAS=SUzhN&LhwquTeTzBR_OKUr_9m0H1sRHf84MK}I4XH!>WSCdFHw<>eWMJ4wryunFs3Oa{}XobFt z*5{w7?>`zRnv`k&HHxpfO6>0VdEwr0{No)++S(SIW_4-Poo+m?P2`V`#X-4CsT6ob z`HC;5E9VoIuQZ;Zie0d|&!uM^VpZWh+=jh`*$Kq?zs}Y041xuq1E9vU?L4TBYXdl0 zZ&q4U?4fA3&1}UQR`AD-j1uY2)Y;1^DJ}^#rQR2~u^b@niq~PXiqZRK+o!_r=|~s;sVZXST|Y$;WV&Ce$fm|R=Ak? zUUJo&ndG3xqkp|vvZT5?7{f$KwCis8CMPkr)kuhWH|7B8%(|9cKna`06Bh!^J*N)X)Q7i1EO+!G3f zs=^7ZM9>5|H)Jb!9)dp2?-tkpVn9t=8{KZ7)~WfrPiZh0nN&OhPFhbM7>YpX%tX7(Qy<1vA(b_|`ddLTEC7CVfBYTi zA&&p;=LogDFvvUgd0-WN_}SKc&4e3PTK=5<`4|e!+5t6M`d@@q7YatcI0LDy+={*x z1f#{QU%WEFAHj_8m$6Y;8TYPWM0i=*ln8nSrhb!`+BfF1v~1!lGNs|79# zO32OSCw_OB;(^#xWY#7$;mcZA*V_I*wY?C0c(mnLDe{sOCSP?G*4?tG#H$2BYENGB zIQuA={=31dx#tkvF@KXOdwW|8>SmtPC(BB-K3|+d3~eav^ppUl;dc0SJvfVN?Hxv) z8=9n*M@oY*k5OPKsDOwhYYI(%+hzdXg2a{)ZX{Io+KJrX`3%5>NqHiSQU6xGJx1FZ zK)n~Rs%3^j(DG8ezlSh@vz#4HYpe*!qh}9H{+Kd=a!B@uHbfk}eYSpmo&ij%0k`x2 z@ik(2_SZQEkPCOM-Glv45Mpp7zERRPTj*|{1*yF;Zoc&CSoY`!HxMSG-*I_dLIMl- zubVOb5hK5?@mlb>Nt@?gY0!SScy<^K$jGRv=76pJEuQAh4y{v>N-1{7W`CYMC4(l#dyN^c$E%FVszdpCbYex_N zCvGUHU9uB<>un4N^cX}_g{)oThm3}=wRx_UYS57o!?Hqm26(MV{TtL`UTDF$12 z85_j^|2B+$g(UPZlgD62)D^|U3y32}$D{r&hZN11x)7wKtwxTkcu^}h@5t+XZhzp@ zX|KqFg(I1Y$vIYRG;=5=2f(iT{Pg=ND444%x8&3nnPPVEUf=9c8f;W^ZWiey8tcSo z5#%Loxj`Mtx|3iWXVduBIRHj!Mn0%u9D~q>c83An^?0J>sW(<*ra;tyD1a0m^63w> zb6kU!KJR63oM2pZZA=9jwe!61%v%PdOo0vQouw@69)+vjn+P(1A;0@R(PFB3luy(U zh!vIygwbpP`pMWGWurd!`4}RPjbF`l2#n?k(A8ph>N5k-Q;hTv>{&OSz>Gi;F6bqE z>!BAPWY=SbwLTC^vqk-Vi(v8bb?ZN*JS?CDLupK?u%<{u@v|qF@<~l46XjGXu%KF7 zk8h2*aO!!x0qhxN`wsOWiLyje>#%f_R?6x6J3ez=7=ly&%M8yA{4MwfCI3zY*tIZN zko5U0>BL81LGBXQ#nKmTe46%a4RG|_%PUOcHqKQUU*K8whe<^1XGz7JQb2tYdm>xN z#;O9)`C%)Dnm)$s@i2WX^*Xo$Ij*mf>lWj{R(lMgmAM1cbc1Ct6E3io8Ia1?s8mdbmmWGBv+~A zveTTB5qBDXbUh8ua)i)NMKJkp`R=<8%bxo0w@!X!fzO2RdJcf*y~lwLNruNNd?&&V zUHxmLn&-KwEG}rrfku++8`u5!Xz~ZM@c9H<%ojoTn5r{mbar2Fj!Bgx)GeaqmAbzA zy1mVPK{=iY%r{}p_qbo-ci6H1i|$OrK*DIX@a5BP7dhBwZYjF&7DWl%299ks-1tKC znGUORX@pD0^FIoVmzic)gfyv^ZEy9K&0PD4%EhH!onxY_GfS56Pl~Y{x7HPLo;y8% zO;B=fH5P8YfB%TiRh6FEHrV%d?!%o>h-6TINW!ko?80+{LcQ94*4N@0YW7pJ{G}}{ z2`(^ras`t8jIvi@f9~G&mqRFGyct#JIG^;ky7zs@ndm!Q+bvE2`Xyqnp(`DkMokXC zfZ#j2ctB60zhR#fA z@lLezT=JeyKhk(65ZWp%8fG`3Q)Y8W-0YDGy_xjOCbRUm`y-qAmwI${55-NlkPGU> z&8X-y*Xlsz!&exHz?+x7f_>JLB486H%C^%*5l+{=qL&=CuA=MnL%giQ%s(=d;)pqD zMW;V7Iftv6v?Lrqy&WL&yrW6em+;7t6BSe4n0VBA40`vjw|jotmAcz+GQ{uiTQ3em z`*RoL6xhLRRq;Ut9qJS|DOf*lx>_sN6*Cm1DxMp9kY$@tss7COj}2a1dnamiYRRo> z%b{q(#SCM5Gnptn!3D9ulsmDpu9teBLx<*7RmM;Xi#!{#|EmNX&aZni#6kDXWwM## zKfnCy@opKu3cEZv|3pT4R}or}<+G)dx9+RZQ0M-mEAr|fj2eqTK&ZW+BieE5ho3PQ zO{vX^o{iGAZvMPURd!B2ZsZZXd@ffJ!`c6tCI#}3*0>d_F;abf>unJM7E83-N>D+# zBmXTg$fJZi@)ER-hyRjKHP;O2xq*Wqp*r0!uWJ_F_g58V|oE^-g;QuAwbKJcER={-p zT~cWMuaQdG@^tb_e}ZX9P@rCF=QIz9LJL4s%>nrxSq(#oW++Ody*A%Z)3oVeB`P~R zVo?rDV``yfWDD}r!Fc6>188Et_8Cmg_`5jHUt)GcMblS$%nkuY!(B8rsMU9k{x6W{ BUbz4O literal 8334 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 O0&2?Ih;l`%xBmxhxkj)6 literal 0 HcmV?d00001 diff --git a/smqtt-ui/src/assets/img/idle.png b/smqtt-ui/src/assets/img/idle.png new file mode 100644 index 0000000000000000000000000000000000000000..a83b82bf2295ab9cd8b94380ed4c2451af2c9538 GIT binary patch literal 7872 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 literal 0 HcmV?d00001 diff --git a/smqtt-ui/src/assets/img/iowait.png b/smqtt-ui/src/assets/img/iowait.png new file mode 100644 index 0000000000000000000000000000000000000000..9a55dfedbcda26f736deae83800366868e66fba4 GIT binary patch literal 9097 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 literal 10214 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 a_RAuTc5oTL!bFvj0OX}rq{=0X1OFcwlFmK= literal 0 HcmV?d00001 diff --git a/smqtt-ui/src/pages/dashboard/console/Console.vue b/smqtt-ui/src/pages/dashboard/console/Console.vue index a66b63e8..ffec6cf6 100644 --- a/smqtt-ui/src/pages/dashboard/console/Console.vue +++ b/smqtt-ui/src/pages/dashboard/console/Console.vue @@ -1,8 +1,15 @@ \ No newline at end of file diff --git a/smqtt-ui/src/pages/dashboard/publish/index.js b/smqtt-ui/src/pages/dashboard/publish/index.js new file mode 100644 index 00000000..aadb69cd --- /dev/null +++ b/smqtt-ui/src/pages/dashboard/publish/index.js @@ -0,0 +1,2 @@ +import Console from "./Publish"; +export default Console \ No newline at end of file diff --git a/smqtt-ui/src/router/config.js b/smqtt-ui/src/router/config.js index b13fbfc8..432e69bc 100644 --- a/smqtt-ui/src/router/config.js +++ b/smqtt-ui/src/router/config.js @@ -68,6 +68,11 @@ const options = { path: 'subscribes', name: '订阅信息', component: () => import('@/pages/dashboard/subscribes'), + }, + { + path: 'publish', + name: '推送信息', + component: () => import('@/pages/dashboard/publish'), } ] }, diff --git a/smqtt-ui/src/services/api.js b/smqtt-ui/src/services/api.js index 92e18538..d871804a 100644 --- a/smqtt-ui/src/services/api.js +++ b/smqtt-ui/src/services/api.js @@ -8,6 +8,7 @@ module.exports = { CONNECTIONS: `${BASE_URL}/smqtt/connection`, SUBSCRIBES:`${BASE_URL}/smqtt/subscribe`, ISCLUESTER:`${BASE_URL}/smqtt/is/cluster`, + PUBLISH:`${BASE_URL}/smqtt/publish`, ROUTES: `${BASE_URL}/routes`, GOODS: `${BASE_URL}/goods`, GOODS_COLUMNS: `${BASE_URL}/columns`, diff --git a/smqtt-ui/src/services/smqtt.js b/smqtt-ui/src/services/smqtt.js index 0f45db61..56b89e70 100644 --- a/smqtt-ui/src/services/smqtt.js +++ b/smqtt-ui/src/services/smqtt.js @@ -1,4 +1,4 @@ -import {CLUSTERS,CONNECTIONS,SUBSCRIBES,ISCLUESTER} from '@/services/api' +import {CLUSTERS, CONNECTIONS, SUBSCRIBES, ISCLUESTER, PUBLISH} from '@/services/api' import {request, METHOD} from '@/utils/request' /** @@ -27,4 +27,11 @@ export async function subscribes() { */ export async function isCluster() { return request(ISCLUESTER, METHOD.GET, {}) +} + +/** + * 推送mqtt消息 + */ +export async function publish(){ + return request(PUBLISH,METHOD.POST,{}) } \ No newline at end of file -- Gitee From 35d386c61811bccc725802b0bd541a7723ef6ada Mon Sep 17 00:00:00 2001 From: "yiming.tang" Date: Sat, 10 Jul 2021 22:04:23 +0800 Subject: [PATCH 043/482] =?UTF-8?q?=E8=A1=A8=E5=8D=95=E5=BF=85=E5=A1=AB?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=8C=E4=B8=8E=E4=BC=A0=E5=8F=82=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/dashboard/publish/Publish.vue | 47 ++++++++++++------- smqtt-ui/src/services/smqtt.js | 4 +- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/smqtt-ui/src/pages/dashboard/publish/Publish.vue b/smqtt-ui/src/pages/dashboard/publish/Publish.vue index 93d2dac5..12659769 100644 --- a/smqtt-ui/src/pages/dashboard/publish/Publish.vue +++ b/smqtt-ui/src/pages/dashboard/publish/Publish.vue @@ -1,17 +1,21 @@