diff --git a/.gitignore b/.gitignore index a0043cc4..61f3420f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,15 @@ *.class *.classpath *.project -*/.settings/* -*/target/* -*/bin/* -*/WebContent/* +*/.settings/ +*/target/ +*/bin/ +*/WebContent/ +*/.DS_Store -.idea/* -*.jar -*.war -*.ear -*.iml -./target/* +.idea/ + +target/ # Mobile Tools for Java (J2ME) .mtj.tmp/ @@ -20,6 +18,9 @@ *.jar *.war *.ear +*.iml +.DS_Store # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +mpush-test/src/test/resources/application.conf diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 00000000..fc7e5191 --- /dev/null +++ b/Changelog.md @@ -0,0 +1,126 @@ +#### v0.8.0 + +1. 增加本地ip和外网ip配置项 +2. ConnServer和GatewayServer增加bind ip和register ip 配置项 +3. ConnServer增加权重等扩展属性配置项 +4. 系统模块化重构:SPI增加Plugin接口及其init方法,增加MPushContext对象,方便插件初始化时控制系统内部对象 +5. 广播推送增加RedisBroadcastController存储推送结果到redis, 并通过redis控制推送任务 +6. 启动脚本优化修复不能加载自定义SPI的bug +7. EventBus订阅方法增加@AllowConcurrentEvents注解,提高高并发性能 +8. 代码优化,当GatewayClient启动时从ZK获取的链接为空时,后续会尝试重新获取 +9. 优化ServerLauncher和PushClient代码,方便自定义系统配置和spring方式启动 +10. 依赖类库升级,日志优化,及其他bug fix + + + + +#### v0.7.1 + +1. 修复网关客户端获取连接失败bug +2. 修复ZK临时节点在连接断开未重新注册bug +3. PushClient代码优化,依赖服务启动/停止顺序优化 +4. 增加查询在线用户列表接口,修复Json转换bug +5. 修改http代理request.copy引用计数bug +6. 依赖类库升级,日志优化,及其他bug fix + + + + +#### v0.7.0 + +1. 网关新增udp, sctp协议支持,目前支持的协议有tcp/udp/sctp/udt, 推荐tcp +2. 增加websocket接入层,和原接入层共享线程资源,增加websocket js client +3. 抽象出cache层,不再直接依赖redis模块,支持自定义缓存实现 +4. 抽象出服务注册与发现层,不再直接依赖Zookeeper模块, 支持自定义服务注册发现 +5. 添加测试用的默认缓存实现以及注册发现实现,在不安装redis,zk的情况下也能进行源码测试 +6. 推送中心模块重构,支持不同的消息来源,支持自定义消息来源,编写从MQ订阅消息demo +7. Gateway Client代码重构,支持多线程,多连接数配置 +8. 线程池优化,重新设计各模块线程配置方式,EventBus使用动态线程池,增加Netty线程池监控 +9. PushClient任务超时代码优化, 优化Timer任务线程池,在任务取消后直接从队列里删除 +10. PushSender同步调用直接返回PushResult不再兼容老的返回Boolean类型 +11. 修改TimeLine多线程bug,优化PushRequest多线程下代码 +12. 修复ID_SEQ在高并发下重复的问题,不再使用LongAdder +13. 代码优化,内存优化,修复推送超时问题 +14. 增加推送压测代码,增加推送统计及流控QPS监控等 +15. 增加tcp/udp 发送接收缓冲区配置 +16. 增netty write-buffer-water-mark配置 +17. 代码优化, 内存优化,及时释放无用的对象 +18. 流控调优,默认关闭流量整形 +19. 增加jmx监控统计, 脚本加入JMX设置,jvm设置 +20. 增加PushCenter消息流转时间线, 方便监控消息的各个生命周期的耗时(PushClient -> GatewayClient -> GatewayServer -> PushCenter -> ConnServer -> Client) +21. 服务启动/停止流程优化,boot chain正序启动,逆序停止,启动流程日志优化 + + + + +#### v0.6.1 + +1. 产品版本策略修改,主版本前移一位,最后一位用于小版本bug fix +2. 新增支持单机多实例部署方式 +3. 升级依赖类库,解决由于版本升级引起的jedis和zk兼容性问题 +4. 核心日志打印内容优化,更利于问题排查 +5. 修复connId高并发下可能重复的bug +6. 增加压测代码,优化测试模块 +7. 配置文件优化,增加相应的注释说明 +8. 修复流控发送计数引起的bug +9. 优化内存占用,连接断开后立即释放Connection的引用 +10. 其他bug fix及代码优化 + + + + +#### v0.0.6 + +1. 网关服务增加UDP及组播支持,后续网关部分的TCP部分将逐步淘汰 +2. 新增用户打标,修改标签,取消标签功能 +3. 全网推送增加按标签过滤,按条件过滤,条件表达式目前支持javascript +4. Service模块代码优化,增加同步启动/停止,超时监控 +5. 推送模块增加流控功能, 分为全局流控和广播流控,以及基于Redis实现的实时流控 +6. 由于网关采用了UDP,PushClient模块和踢人模块增加UDP支持 +7. 线程池代码优化,线程命名调整, 支持配置调整Netty boss和work的线程数 +8. 路由模块:客户端定义增加SPI支持, 用户可自定义控制多端在线策略 +9. 日志及配置项优化,增加mp.home配置项 +10. 心跳检测优化,连接一建立就开始检测心跳,防止客户端握手失败或未握手的情况 + + + + +#### v0.0.5 + +1. redis 增加redis3.x集群支持, 配置项不再兼容 +2. 绑定用户调整,校验重复绑定,以及未解绑,就绑定的场景 +3. 新增client push上行功能, 并支持用户以SPI方式集成自己的Handler +4. 全网推送增加按标签过滤推送用户 +5. 增加流量控制,tcp发送缓冲区检测代码优化 +6. 修复ACK超时方法调用错误bug,增加ack超时时间设置 +7. 解码优化, 不再抛出解码异常,取消循环解码 +8. NettyServer 增加IoRate设置,优雅停止流程优化,先关闭main reactor +9. 心跳优化,连接建立后就开始计算心跳 +10. sessionId生成器性能优化,采用jdk8 LongAdder +11. Service模块开始使用java8 CompletableFuture +12. SPI模块优化增加@Spi注解,多个实现可以指定顺序 +13. Profile 性能分析模块优化,增加性能监控开关配置,加入javassist优化性能 +14. zk client 代码优化,修改临时节点重复注册问题,增加DNS ZK Node +15. 脚步修改start-foreground不能加载配置项bug, 修改windows启动命令bug +16. 其他bug fix + + + + +#### v0.0.4 + +1. push client API 调整 +2. push 接口增加了全网推送功能 +3. 用户下线后路由信息不再删除,而是修改为下线状态 +4. 修复ZK Client临时节点断开后,不能恢复注册的bug +5. 其他bug fix + +#### v0.0.3 + +1. 增加了消息ACK功能 +2. 修复脚本换行问题 +3. bug fix + +### v0.0.2 + +1. 增加多端同时在线攻能 diff --git a/README.md b/README.md index f5a812c9..e6bbb603 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,292 @@ -# mpush -mobile push +## [详细教程](http://mpush.mydoc.io) + +* 官网:[https://mpusher.github.io](https://mpusher.github.io) +* 文档:[http://mpush.mydoc.io](http://mpush.mydoc.io) +* QQ群:__114583699__ MPUSH开源消息推送系统 + +## 源码 +* group [https://github.com/mpusher/](https://github.com/mpusher/) 源代码空间 +* server [https://github.com/mpusher/mpush](https://github.com/mpusher/mpush) 服务端源码 +* alloc [https://github.com/mpusher/alloc](https://github.com/mpusher/alloc) 调度器源码 +* mpns [https://github.com/mpusher/mpns](https://github.com/mpusher/mpns) 个性化推送中心源码 +* java-client [https://github.com/mpusher/mpush-client-java](https://github.com/mpusher/mpush-client-java) 纯java客户端源码 +* android sdk&demo [https://github.com/mpusher/mpush-android](https://github.com/mpusher/mpush-android) 安卓SDK和DEMO源码 +* IOS sdk(swift) [https://github.com/mpusher/mpush-client-swift](https://github.com/mpusher/mpush-client-swift) swift版客户端源码 +* IOS sdk(OC) [https://github.com/mpusher/mpush-client-oc](https://github.com/mpusher/mpush-client-oc) Object C 客户端源码 + +ps:由于源码分别在github和码云有两份,最新的代码以github为主 + +## 服务调用关系 +![](https://mpusher.github.io/docs/服务依赖关系.png) + +## 源码测试 +1. ```git clone https://github.com/mpusher/mpush.git``` +2. 导入到eclipse或Intellij IDEA +3. 打开```mpush-test```模块,所有的测试代码都在该模块下 +4. 修改配置文件```src/test/resource/application.conf```文件修改方式参照 服务部署第6点 +5. 运行```com.mpush.test.sever.ServerTestMain.java```启动长链接服务 +6. 运行```com.mpush.test.client.ConnClientTestMain.java``` 模拟一个客户端 +7. 运行```com.mpush.test.push.PushClientTestMain``` 模拟给用户下发消息 +8. 可以在控制台观察日志看服务是否正常运行,消息是否下发成功 + +## 服务部署 + +###### 说明:mpush 服务只依赖于zookeeper和redis,当然还有JDK>=1.8 + +1. 安装```jdk 1.8``` 以上版本并设置```%JAVA_HOME%``` + +2. 安装```zookeeper``` (安装配置步骤略) + +3. 安装```Redis``` (安装配置步骤略) + +4. 下载mpush server 最新的正式包[https://github.com/mpusher/mpush/releases](https://github.com/mpusher/mpush/releases) + +5. 解压下载的tar包`tar -zvxf mpush-release-0.0.2.tar.gz`到 mpush 目录, 结构如下 + + >
+   >drwxrwxr-x 2 shinemo shinemo  4096 Aug 20 09:30 bin —> 启动脚本
+   >drwxrwxr-x 2 shinemo shinemo  4096 Aug 20 09:52 conf —> 配置文件
+   >drwxrwxr-x 2 shinemo shinemo  4096 Aug 20 09:29 lib —> 核心类库
+   >-rw-rw-r-- 1 shinemo shinemo 11357 May 31 11:07 LICENSE
+   >drwxrwxr-x 2 shinemo shinemo  4096 Aug 20 09:32 logs —> 日志目录
+   >-rw-rw-r-- 1 shinemo shinemo    21 May 31 11:07 README.md
+   >drwxrwxr-x 2 shinemo shinemo  4096 Aug 20 09:52 tmp
+   >
+ +6. 修改 conf 目录下的 ```vi mpush.conf```文件, ```mpush.conf```里的配置项会覆盖同目录下的```reference.conf```文件 + ```java + #主要修改以下配置 + mp.net.connect-server-port=3000//长链接服务对外端口, 公网端口 + mp.zk.server-address="127.0.0.1:2181"//zk 机器的地址 + mp.redis={//redis 相关配置 + nodes:["127.0.0.1:6379"] //格式是ip:port + cluster-model:single //single, cluster + } + //还有用于安全加密的RSA mp.security.private-key 和 mp.security.public-key 等... + ``` + 如果要修改其他配置请参照reference.conf文件 + +7. 给bin目录下的脚本增加执行权限```chmod u+x *.sh``` + +8. 执行```./mp.sh start``` 启动服务, 查看帮助```./mp.sh``` 目前支持的命令: + + ```Usage: ./mp.sh {start|start-foreground|stop|restart|status|upgrade|print-cmd}``` + + ```set-env.sh``` 用于增加和修改jvm启动参数,比如堆内存、开启远程调试端口、开启jmx等 + +9. ```cd logs```目录,```cat mpush.out```查看服务是否启动成功 +10. 集成部署,比如集成到现有web工程一起部署到tomcat,可以添加如下依赖 + + ```xml + + com.github.mpusher + mpush-boot + 0.0.2 + + ``` + 启动入口`com.mpush.bootstrap.ServerLauncher.java` + +## 配置文件详解 + ```java +################################################################################################################## +# +# NOTICE: +# +# 系统配置文件,所有列出的项是系统所支持全部配置项 +# 如果要覆盖某项的值可以添加到mpush.conf中。 +# +# 配置文件格式采用HOCON格式。解析库由https://github.com/typesafehub/config提供。 +# 具体可参照器说明文档,比如含有特殊字符的字符串必须用双引号包起来。 +# +############################################################################################################## + +mp { + #日志配置 + log.level=warn + log.dir=${user.dir}/../logs + + #核心配置 + core { + max-packet-size=10k //系统允许传输的最大包的大小 + compress-threshold=10k //数据包启用压缩的临界值,超过该值后对数据进行压缩 + min-heartbeat=3m //最小心跳间隔 + max-heartbeat=3m //最大心跳间隔 + max-hb-timeout-times=2 //允许的心跳连续超时的最大次数 + session-expired-time=1d //用于快速重连的session 过期时间默认1天 + epoll-provider=netty //nio:jdk自带,netty:由netty实现 + } + + #安全配置 + security { + #rsa 私钥, 公钥 key长度为1024;生成方式可以使用open-ssh或者使用工具类com.mpush.tools.crypto.RSAUtils#main + private-key="MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA=" + public-key="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB" + aes-key-length=16 //AES key 长度 + ras-key-length=1024 //RSA key 长度 + } + + #网络配置 + net { + connect-server-port=3000 //长链接服务对外端口, 公网端口 + gateway-server-port=3001 //网关服务端口, 内部端口 + admin-server-port=3002 //控制台服务端口, 内部端口 + public-host-mapping { //本机局域网IP和公网IP的映射关系 + "127.0.0.1":"111.1.32.137" + } + traffic-shaping { //流量整形配置 + gateway-client { + enabled:true + check-interval:100ms + write-global-limit:1k + read-global-limit:0 + write-channel-limit:256b + read-channel-limit:0 + } + + gateway-server { + enabled:true + check-interval:100ms + write-global-limit:0 + read-global-limit:10k + write-channel-limit:0 + read-channel-limit:0.5k + } + + connect-server { + enabled:false + check-interval:100ms + write-global-limit:0 + read-global-limit:100k + write-channel-limit:3k + read-channel-limit:3k + } + } + } + + #Zookeeper配置 + zk { + server-address="127.0.0.1:2181" + namespace=mpush + digest=mpush + local-cache-path=/ + retry { + #initial amount of time to wait between retries + baseSleepTimeMs=3s + #max number of times to retry + maxRetries=3 + #max time in ms to sleep on each retry + maxSleepMs=5s + } + connectionTimeoutMs=5s + sessionTimeoutMs=5s + } + + #Redis集群配置 + redis { + write-to-zk=true + #redis 集群配置,group 是个二维数组,第一层表示有多少组集群,每个集群下面可以有多台机器 + cluster-group:[ + [ + { + host:"127.0.0.1" + port:2181 + password:ShineMoIpo + } + ] + ] + config { + maxTotal:8, + maxIdle:4, + minIdle:1, + lifo:true, + fairness:false, + maxWaitMillis:5000, + minEvictableIdleTimeMillis:300000, + softMinEvictableIdleTimeMillis:1800000, + numTestsPerEvictionRun:3, + testOnCreate:false, + testOnBorrow:false, + testOnReturn:false, + testWhileIdle:false, + timeBetweenEvictionRunsMillis:60000, + blockWhenExhausted:true, + jmxEnabled:true, + jmxNamePrefix:pool, + jmxNameBase:pool + } + } + + #HTTP代理配置 + http { + proxy-enabled=false //启用Http代理 + max-conn-per-host=5 //每个域名的最大链接数, 建议web服务nginx超时时间设长一点, 以便保持长链接 + default-read-timeout=10s //请求超时时间 + max-content-length=5m //response body 最大大小 + dns-mapping { //域名映射外网地址转内部IP + "mpush.com":["127.0.0.1:8080", "127.0.0.1:8081"] + } + } + + #线程池配置 + thread { + pool { + boss { //netty boss + min:4 + max:16 + queue-size:1000 + } + + work { //netty boss + min:8 + max:32 + queue-size:1000 + } + + event-bus { + min:4 + max:4 + queue-size:10000 //大量的online,offline, + } + + http-proxy { + min:8 + max:64 + queue-size:1000 + } + + biz { //其他业务 + min:4 + max:64 + queue-size:10 + } + + mq { //用户上下线消息, 踢人等 + min:2 + max:4 + queue-size:10000 + } + + push-callback { //消息推送 + min:2 + max:2 + queue-size:0 + } + } + } + + #系统监控配置 + monitor { + dump-dir=/tmp/logs/mpush/ + dump-stack=false //是否定时dump堆栈 + dump-period=1m //多久监控一次 + print-log=true //是否打印监控日志 + } + + #SPI扩展配置 + spi { + thread-pool-factory:"com.mpush.tools.thread.pool.DefaultThreadPoolFactory" + dns-mapping-manager:"com.mpush.common.net.HttpProxyDnsMappingManager" + } +} +``` +11. 未完待续... diff --git a/bin/mp-env.cmd b/bin/env-mp.cmd similarity index 96% rename from bin/mp-env.cmd rename to bin/env-mp.cmd index fa39e14c..46f05757 100644 --- a/bin/mp-env.cmd +++ b/bin/env-mp.cmd @@ -15,7 +15,7 @@ REM See the License for the specific language governing permissions and REM limitations under the License. set MPCFGDIR=%~dp0%..\conf -set MP_LOG_DIR=%~dp0%.. +set MP_LOG_DIR=%~dp0%..\logs set MP_LOG4J_PROP=INFO,CONSOLE REM for sanity sake assume Java 1.6 @@ -30,7 +30,7 @@ SET CLASSPATH=%~dp0..\*;%~dp0..\lib\*;%CLASSPATH% REM make it work for developers SET CLASSPATH=%~dp0..\build\classes;%~dp0..\build\lib\*;%CLASSPATH% -set MPCFG=%MPCFGDIR%\zoo.cfg +set MPCFG=%MPCFGDIR%\mpush.conf @REM setup java environment variables diff --git a/bin/mp-env.sh b/bin/env-mp.sh old mode 100644 new mode 100755 similarity index 57% rename from bin/mp-env.sh rename to bin/env-mp.sh index 618dee65..2ee5287f --- a/bin/mp-env.sh +++ b/bin/env-mp.sh @@ -18,47 +18,48 @@ # This script should be sourced into other mpush # scripts to setup the env variables -# We use MPCFGDIR if defined, +# We use MP_CFG_DIR if defined, # otherwise we use /etc/mp # or the conf directory that is # a sibling of this script's directory -MPBINDIR="${MPBINDIR:-/usr/bin}" -MPUSH_PREFIX="${MPBINDIR}/.." +MP_BIN_DIR="${MP_BIN_DIR:-/usr/bin}" +MPUSH_PREFIX="${MP_BIN_DIR}/.." +MPUSH_HOME=$MPUSH_PREFIX -if [ "x$MPCFGDIR" = "x" ] +if [ "x$MP_CFG_DIR" = "x" ] then if [ -e "${MPUSH_PREFIX}/conf" ]; then - MPCFGDIR="$MPBINDIR/../conf" + MP_CFG_DIR="$MP_BIN_DIR/../conf" else - MPCFGDIR="$MPBINDIR/../etc/mpush" + MP_CFG_DIR="$MP_BIN_DIR/../etc/mpush" fi fi -if [ -f "${MPBINDIR}/set-env.sh" ]; then - . "${MPBINDIR}/set-env.sh" +if [ "x${MP_DATA_DIR}" = "x" ] +then + MP_DATA_DIR="${MPUSH_PREFIX}/tmp" fi -if [ "x$MPCFG" = "x" ] +if [ "x${MP_LOG_DIR}" = "x" ] then - MPCFG="mpush.conf" + MP_LOG_DIR="${MPUSH_PREFIX}/logs" fi -MPCFG="$MPCFGDIR/$MPCFG" - -if [ -f "$MPBINDIR/java.env" ] -then - . "$MPBINDIR/java.env" +if [ -f "${MP_BIN_DIR}/set-env.sh" ]; then + . "${MP_BIN_DIR}/set-env.sh" fi -if [ "x${MP_DATADIR}" = "x" ] +if [ "x$MP_CFG" = "x" ] then - MP_DATADIR="${MPUSH_PREFIX}/tmp" + MP_CFG="mpush.conf" fi -if [ "x${MP_LOG_DIR}" = "x" ] +MP_CFG="$MP_CFG_DIR/$MP_CFG" + +if [ -f "$MP_BIN_DIR/java.env" ] then - MP_LOG_DIR="${MPUSH_PREFIX}/logs" + . "$MP_BIN_DIR/java.env" fi if [ "x${MP_LOG4J_PROP}" = "x" ] @@ -74,40 +75,21 @@ fi #add the conf dir to classpath -CLASSPATH="$MPCFGDIR:$CLASSPATH" - -for i in "$MPBINDIR"/../src/java/lib/*.jar -do - CLASSPATH="$i:$CLASSPATH" -done +CLASSPATH="$MP_CFG_DIR:$MP_BIN_DIR/bootstrap.jar:$CLASSPATH" #make it work in the binary package -#(use array for LIBPATH to account for spaces within wildcard expansion) -if [ -e "${MPUSH_PREFIX}"/share/mpush/mpush-*.jar ]; then - LIBPATH=("${MPUSH_PREFIX}"/share/mpush/*.jar) -else - #release tarball format - for i in "$MPBINDIR"/../mpush-*.jar - do - CLASSPATH="$i:$CLASSPATH" - done - LIBPATH=("${MPBINDIR}"/../lib/*.jar) -fi -for i in "${LIBPATH[@]}" +for i in "${MPUSH_PREFIX}"/lib/*.jar do CLASSPATH="$i:$CLASSPATH" done -#make it work for developers -for d in "$MPBINDIR"/../build/lib/*.jar -do - CLASSPATH="$d:$CLASSPATH" -done - -#make it work for developers -CLASSPATH="$MPBINDIR/../build/classes:$CLASSPATH" - +if [ -e "${MPUSH_PREFIX}"/lib/plugins/*.jar ]; then + for j in "${MPUSH_PREFIX}"/lib/plugins/*.jar + do + CLASSPATH="$j:$CLASSPATH" + done +fi case "`uname`" in CYGWIN*) cygwin=true ;; @@ -118,5 +100,3 @@ if $cygwin then CLASSPATH=`cygpath -wp "$CLASSPATH"` fi - -#echo "CLASSPATH=$CLASSPATH" \ No newline at end of file diff --git a/bin/mp.cmd b/bin/mp.cmd index f578f4e0..496b0fc3 100644 --- a/bin/mp.cmd +++ b/bin/mp.cmd @@ -13,12 +13,20 @@ REM distributed under the License is distributed on an "AS IS" BASIS, REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. REM See the License for the specific language governing permissions and REM limitations under the License. +REM java -Dmp.conf=../conf/mpush.conf -Dmp.home=. -jar bootstrap.jar +REM setlocal + +REM call "%~dp0env-mp.cmd" + +REM set MPMAIN=-jar %~dp0bootstrap.jar + +REM call %JAVA% "-Dmp.conf=%MPCFG%" "-Dmp.home=%~dp0%.." -cp "%CLASSPATH%" %MPMAIN% %* + +REM endlocal + + +java -Dmp.conf=../conf/mpush.conf -Dmp.home=.. -jar bootstrap.jar + -setlocal -call "%~dp0mpEnv.cmd" -set MPMAIN="-jar ../boot.jar" -echo on -call %JAVA% "-Dmp.log.dir=%MP_LOG_DIR%" -cp "%CLASSPATH%" %MPMAIN% "%MPCFG%" %* -endlocal diff --git a/bin/mp.sh b/bin/mp.sh old mode 100644 new mode 100755 index c40e2d3d..e0af6086 --- a/bin/mp.sh +++ b/bin/mp.sh @@ -24,14 +24,14 @@ # use POSTIX interface, symlink is followed automatically -MPBIN="${BASH_SOURCE-$0}" -MPBIN="$(dirname "${MPBIN}")" -MPBINDIR="$(cd "${MPBIN}"; pwd)" +MP_BIN="${BASH_SOURCE-$0}" +MP_BIN="$(dirname "${MP_BIN}")" +MP_BIN_DIR="$(cd "${MP_BIN}"; pwd)" -if [ -e "$MPBIN/../libexec/mp-env.sh" ]; then - . "$MPBINDIR/../libexec/mp-env.sh" +if [ -e "$MP_BIN/../libexec/env-mp.sh" ]; then + . "$MP_BIN_DIR/../libexec/env-mp.sh" else - . "$MPBINDIR/mp-env.sh" + . "$MP_BIN_DIR/env-mp.sh" fi # See the following page for extensive details on setting @@ -51,7 +51,7 @@ then # for some reason these two options are necessary on jdk6 on Ubuntu # accord to the docs they are not necessary, but otw jconsole cannot # do a local attach - MPMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=$JMXLOCALONLY" + MP_MAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=$JMXLOCALONLY" else if [ "x$JMXAUTH" = "x" ] then @@ -69,41 +69,41 @@ then echo "MPush remote JMX authenticate set to $JMXAUTH" >&2 echo "MPush remote JMX ssl set to $JMXSSL" >&2 echo "MPush remote JMX log4j set to $JMXLOG4J" >&2 - MPMAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=$JMXPORT -Dcom.sun.management.jmxremote.authenticate=$JMXAUTH -Dcom.sun.management.jmxremote.ssl=$JMXSSL -Dmpush.jmx.log4j.disable=$JMXLOG4J" + MP_MAIN="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=$JMXPORT -Dcom.sun.management.jmxremote.authenticate=$JMXAUTH -Dcom.sun.management.jmxremote.ssl=$JMXSSL -Dmpush.jmx.log4j.disable=$JMXLOG4J" fi else echo "JMX disabled by user request" >&2 - MPMAIN="" + MP_MAIN="" fi -MPMAIN="$MPMAIN -jar $MPBINDIR/bootstrap.jar" +MP_MAIN="$MP_MAIN com.mpush.bootstrap.Main" -if [ "x$SERVER_JVMFLAGS" != "x" ] +if [ "x$SERVER_JVM_FLAGS" != "x" ] then - JVMFLAGS="$SERVER_JVMFLAGS $JVMFLAGS" + JVM_FLAGS="$SERVER_JVM_FLAGS $JVM_FLAGS" fi if [ "x$2" != "x" ] then - MPCFG="$MPCFGDIR/$2" + MP_CFG="$MP_CFG_DIR/$2" fi -# if we give a more complicated path to the config, don't screw around in $MPCFGDIR -if [ "x$(dirname "$MPCFG")" != "x$MPCFGDIR" ] +# if we give a more complicated path to the config, don't screw around in $MP_CFG_DIR +if [ "x$(dirname "$MP_CFG")" != "x$MP_CFG_DIR" ] then - MPCFG="$2" + MP_CFG="$2" fi if $cygwin then - MPCFG=`cygpath -wp "$MPCFG"` + MP_CFG=`cygpath -wp "$MP_CFG"` # cygwin has a "kill" in the shell itself, gets confused KILL=/bin/kill else KILL=kill fi -echo "Using config: $MPCFG" >&2 +echo "Using config: $MP_CFG" >&2 case "$OSTYPE" in *solaris*) @@ -113,19 +113,18 @@ case "$OSTYPE" in GREP=grep ;; esac -if [ -z "$MPPIDFILE" ]; then -# MP_DATADIR="$($GREP "^[[:space:]]*dataDir" "$MPCFG" | sed -e 's/.*=//')" - if [ ! -d "$MP_DATADIR" ]; then - mkdir -p "$MP_DATADIR" +if [ -z "$MP_PID_FILE" ]; then +# MP_DATA_DIR="$($GREP "^[[:space:]]*dataDir" "$MP_CFG" | sed -e 's/.*=//')" + if [ ! -d "$MP_DATA_DIR" ]; then + mkdir -p "$MP_DATA_DIR" fi - MPPIDFILE="$MP_DATADIR/mpush_server.pid" + MP_PID_FILE="$MP_DATA_DIR/mpush_server.pid" else # ensure it exists, otw stop will fail - mkdir -p "$(dirname "$MPPIDFILE")" + mkdir -p "$(dirname "$MP_PID_FILE")" fi if [ ! -w "$MP_LOG_DIR" ] ; then -echo $MP_LOG_DIR mkdir -p "$MP_LOG_DIR" fi @@ -134,22 +133,21 @@ _MP_DAEMON_OUT="$MP_LOG_DIR/mpush.out" case $1 in start) echo -n "Starting mpush ... " - if [ -f "$MPPIDFILE" ]; then - if kill -0 `cat "$MPPIDFILE"` > /dev/null 2>&1; then - echo $command already running as process `cat "$MPPIDFILE"`. + if [ -f "$MP_PID_FILE" ]; then + if kill -0 `cat "$MP_PID_FILE"` > /dev/null 2>&1; then + echo $command already running as process `cat "$MP_PID_FILE"`. exit 0 fi fi - nohup "$JAVA" "-Dmp.conf=$MPCFG" "-Dmp.log.dir=${MP_LOG_DIR}" "-Dmp.root.logger=${MP_LOG4J_PROP}" \ - -cp "$CLASSPATH" $JVMFLAGS $MPMAIN > "$_MP_DAEMON_OUT" 2>&1 < /dev/null & + nohup "$JAVA" "-Dmp.home=$MPUSH_HOME" "-Dmp.conf=$MP_CFG" -cp "$CLASSPATH" $JVM_FLAGS $MP_MAIN > "$_MP_DAEMON_OUT" 2>&1 < /dev/null & if [ $? -eq 0 ] then case "$OSTYPE" in *solaris*) - /bin/echo "${!}\\c" > "$MPPIDFILE" + /bin/echo "${!}\\c" > "$MP_PID_FILE" ;; *) - /bin/echo -n $! > "$MPPIDFILE" + /bin/echo -n $! > "$MP_PID_FILE" ;; esac if [ $? -eq 0 ]; @@ -166,67 +164,79 @@ start) fi ;; start-foreground) - MP_CMD=(exec "$JAVA") - if [ "${MP_NOEXEC}" != "" ]; then - MP_CMD=("$JAVA") - fi - "${MP_CMD[@]}" "-Dmp.log.dir=${MP_LOG_DIR}" "-Dmp.root.logger=${MP_LOG4J_PROP}" \ - -cp "$CLASSPATH" $JVMFLAGS $MPMAIN "-Dmp.conf=$MPCFG" + "$JAVA" "-Dmp.home=$MPUSH_HOME" "-Dmp.conf=$MP_CFG" -cp "$CLASSPATH" $JVM_FLAGS $MP_MAIN ;; print-cmd) - echo "\"$JAVA\" $MPMAIN " - echo "\"-Dmp.conf=$MPCFG\" -Dmp.log.dir=\"${MP_LOG_DIR}\" -Dmp.root.logger=\"${MP_LOG4J_PROP}\" " - echo "$JVMFLAGS " + echo "\"$JAVA\" $MP_MAIN " + echo "\"-Dmp.home=$MPUSH_HOME -Dmp.conf=$MP_CFG\" " + echo "$JVM_FLAGS " echo "-cp \"$CLASSPATH\" " echo "> \"$_MP_DAEMON_OUT\" 2>&1 < /dev/null" ;; stop) - echo -n "Stopping mpush ... " - if [ ! -f "$MPPIDFILE" ] + echo "Stopping mpush ... " + if [ ! -f "$MP_PID_FILE" ] then - echo "no mpush to stop (could not find file $MPPIDFILE)" + echo "no mpush to stop (could not find file $MP_PID_FILE)" else - $KILL -9 $(cat "$MPPIDFILE") - rm "$MPPIDFILE" - echo STOPPED + $KILL -15 $(cat "$MP_PID_FILE") + SLEEP=30 + SLEEP_COUNT=1 + while [ $SLEEP -ge 0 ]; do + kill -0 $(cat "$MP_PID_FILE") >/dev/null 2>&1 + if [ $? -gt 0 ]; then + rm -f "$MP_PID_FILE" >/dev/null 2>&1 + if [ $? != 0 ]; then + if [ -w "$MP_PID_FILE" ]; then + cat /dev/null > "$MP_PID_FILE" + else + echo "The PID file could not be removed or cleared." + fi + fi + echo STOPPED + break + fi + if [ $SLEEP -gt 0 ]; then + echo "stopping ... $SLEEP_COUNT" + sleep 1 + fi + if [ $SLEEP -eq 0 ]; then + echo "MPUSH did not stop in time." + echo "To aid diagnostics a thread dump has been written to standard out." + kill -3 `cat "$MP_PID_FILE"` + echo "force stop MPUSH." + kill -9 `cat "$MP_PID_FILE"` + echo STOPPED + fi + SLEEP=`expr $SLEEP - 1` + SLEEP_COUNT=`expr $SLEEP_COUNT + 1` + done fi exit 0 ;; upgrade) shift echo "upgrading the servers to 3.*" - "$JAVA" "-Dmpush.log.dir=${MP_LOG_DIR}" "-Dmpush.root.logger=${MP_LOG4J_PROP}" \ - -cp "$CLASSPATH" $JVMFLAGS com.mpush.tools.upgrade.UpgradeMain ${@} + "$JAVA" -cp "$CLASSPATH" $JVM_FLAGS com.mpush.tools.upgrade.UpgradeMain ${@} echo "Upgrading ... " ;; restart) shift "$0" stop ${@} - sleep 5 + sleep 1 "$0" start ${@} ;; status) # -q is necessary on some versions of linux where nc returns too quickly, and no stat result is output - clientPortAddress=`$GREP "^[[:space:]]*clientPortAddress[^[:alpha:]]" "$MPCFG" | sed -e 's/.*=//'` + clientPortAddress=`$GREP "^[[:space:]]*clientPortAddress[^[:alpha:]]" "$MP_CFG" | sed -e 's/.*=//'` if ! [ $clientPortAddress ] then - clientPortAddress="localhost" - fi - clientPort=`$GREP "^[[:space:]]*clientPort[^[:alpha:]]" "$MPCFG" | sed -e 's/.*=//'` - STAT=`"$JAVA" "-Dmp.log.dir=${MP_LOG_DIR}" "-Dmp.root.logger=${MP_LOG4J_PROP}" \ - -cp "$CLASSPATH" $JVMFLAGS org.apache.mpush.client.FourLetterWordMain \ - $clientPortAddress $clientPort srvr 2> /dev/null \ - | $GREP Mode` - if [ "x$STAT" = "x" ] - then - echo "Error contacting service. It is probably not running." - exit 1 - else - echo $STAT - exit 0 + clientPortAddress="localhost" fi + clientPort=`$GREP "^[[:space:]]*connect-server-port[^[:alpha:]]" "$MP_CFG" | sed -e 's/.*=//'` + telnet 127.0.0.1 3002 ;; *) echo "Usage: $0 {start|start-foreground|stop|restart|status|upgrade|print-cmd}" >&2 -esac +esac \ No newline at end of file diff --git a/bin/rsa.sh b/bin/rsa.sh new file mode 100755 index 00000000..472ddbe6 --- /dev/null +++ b/bin/rsa.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# use POSTIX interface, symlink is followed automatically +MP_BIN="${BASH_SOURCE-$0}" +MP_BIN="$(dirname "${MP_BIN}")" +MP_BIN_DIR="$(cd "${MP_BIN}"; pwd)" + +if [ -e "$MP_BIN/../libexec/env-mp.sh" ]; then + . "$MP_BIN_DIR/../libexec/env-mp.sh" +else + . "$MP_BIN_DIR/env-mp.sh" +fi + +if [ $1 -gt 1024 ] ;then + echo "use rsa key size $1" + keySize = $1 +else + echo "use rsa key size 1024" + keySize = 1024 +fi + +"$JAVA" -cp "$CLASSPATH" com.mpush.tools.crypto.RSAUtils $keySize \ No newline at end of file diff --git a/bin/set-env.sh b/bin/set-env.sh old mode 100644 new mode 100755 index 5325b967..cc67fa11 --- a/bin/set-env.sh +++ b/bin/set-env.sh @@ -1,2 +1,37 @@ #!/usr/bin/env bash -#JVMFLAGS="-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8008 -Dio.netty.leakDetectionLevel=advanced" \ No newline at end of file +#1. Netty 相关关设置项 + +#-Dio.netty.leakDetection.level +#netty的内存泄露检测分为四级: +#DISABLED: 不进行内存泄露的检测; +#SIMPLE: 抽样检测,且只对部分方法调用进行记录,消耗较小,有泄漏时可能会延迟报告,默认级别; +#ADVANCED: 抽样检测,记录对象最近几次的调用记录,有泄漏时可能会延迟报告; +#PARANOID: 每次创建一个对象时都进行泄露检测,且会记录对象最近的详细调用记录。是比较激进的内存泄露检测级别,消耗最大,建议只在测试时使用。 + +#-Dio.netty.selectorAutoRebuildThreshold=512 默认512 +#在NIO中通过Selector的轮询当前是否有IO事件,根据JDK NIO api描述,Selector的select方法会一直阻塞,直到IO事件达到或超时,但是在Linux平台上这里有时会出现问题,在某些场景下select方法会直接返回,即使没有超时并且也没有IO事件到达,这就是著名的epoll bug,这是一个比较严重的bug,它会导致线程陷入死循环,会让CPU飙到100%,极大地影响系统的可靠性,到目前为止,JDK都没有完全解决这个问题。 +#但是Netty有效的规避了这个问题,经过实践证明,epoll bug已Netty框架解决,Netty的处理方式是这样的: +#记录select空转的次数,定义一个阀值,这个阀值默认是512,可以在应用层通过设置系统属性io.netty.selectorAutoRebuildThreshold传入,当空转的次数超过了这个阀值,重新构建新Selector,将老Selector上注册的Channel转移到新建的Selector上,关闭老Selector,用新的Selector代替老Selector,详细实现可以查看NioEventLoop中的selector和rebuildSelector方法: + +#-Dio.netty.noKeySetOptimization +#是否禁用nio Selector.selectedKeys优化, 通过反射实现, 默认false + +JVM_FLAGS="-Dio.netty.leakDetection.level=advanced" + +#JMX + +JMXDISABLE=true +#JMXPORT=1099 + +#3. 开启远程调试 + +#JVM_FLAGS="$JVM_FLAGS -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8008" + +#4. GC配置 + +#运行模式 整个堆内存大小 GC算法 +#JVM_FLAGS="$JVM_FLAGS -server -Xmx1024m -Xms1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200" +#GC日志 发生OOM时创建堆内存转储文件 +#JVM_FLAGS="$JVM_FLAGS -Xloggc:$MP_LOG_DIR/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +#发生OOM后的操作 +#JVM_FLAGS="-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$MP_LOG_DIR -XX:OnOutOfMemoryError=$MP_BIN_DIR/restart.sh" \ No newline at end of file diff --git a/conf/conf-dev.properties b/conf/conf-dev.properties index 4a36a6eb..af07a4a1 100644 --- a/conf/conf-dev.properties +++ b/conf/conf-dev.properties @@ -1 +1,4 @@ -log.level=debug \ No newline at end of file +log.level=debug +min.hb=10s +rsa.privateKey=MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA= +rsa.publicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB diff --git a/conf/conf-pub.properties b/conf/conf-pub.properties index a5051a64..f957e52e 100644 --- a/conf/conf-pub.properties +++ b/conf/conf-pub.properties @@ -1 +1,4 @@ -log.level=warn \ No newline at end of file +log.level=warn +min.hb=3m +rsa.privateKey=MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA= +rsa.publicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB diff --git a/conf/reference.conf b/conf/reference.conf index 263e7849..90c0bfb6 100644 --- a/conf/reference.conf +++ b/conf/reference.conf @@ -8,53 +8,103 @@ # 配置文件格式采用HOCON格式。解析库由https://github.com/typesafehub/config提供。 # 具体可参照器说明文档,比如含有特殊字符的字符串必须用双引号包起来。 # -############################################################################################################## +################################################################################################################## mp { - log.level=warn - log.dir=${user.dir}/../logs + #基础配置 + home=${user.dir} //程序工作目前 + + #日志配置 + log-level=warn + log-dir=${mp.home}/logs + log-conf-path=${mp.home}/conf/logback.xml + #核心配置 core { - max-packet-size=10k//系统允许传输的最大包的大小 - compress-threshold=10k//数据包启用压缩的临界值,超过该值后对数据进行压缩 - min-heartbeat=10s - max-heartbeat=3m - max-hb-timeout-times=2//允许的心跳连续超时的最大次数 - session-expired-time=1d//用于快速重连的session 过期时间默认1天 - epoll-provider=netty//nio:jdk 自带,netty:有netty实现 + max-packet-size=10k //系统允许传输的最大包的大小 + compress-threshold=10k //数据包启用压缩的临界值,超过该值后对数据进行压缩 + min-heartbeat=3m //最小心跳间隔 + max-heartbeat=3m //最大心跳间隔 + max-hb-timeout-times=2 //允许的心跳连续超时的最大次数 + session-expired-time=1d //用于快速重连的session 过期时间默认1天 + epoll-provider=netty //nio:jdk自带,netty:由netty实现 } + #安全配置 security { + #rsa 私钥、公钥key长度为1024;可以使用脚本bin/rsa.sh生成, @see com.mpush.tools.crypto.RSAUtils#main private-key="MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA=" public-key="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB" - aes-key-length=16 - ras-key-length=1024 + aes-key-length=16 //AES key 长度 } + #网络配置 net { - connect-server-port=3000 - gateway-server-port=3001 - admin-server-port=3002 - public-host-mapping {//本机局域网IP和公网IP的映射关系 - "10.1.0.32":"111.1.57.148" + local-ip="" //本地ip, 默认取第一个网卡的本地IP + public-ip="" //外网ip, 默认取第一个网卡的外网IP + + connect-server-bind-ip="" //connSrv 绑定的本地ip (默认anyLocalAddress 0.0.0.0 or ::0) + connect-server-register-ip=${mp.net.public-ip} //公网ip, 注册到zk中的ip, 默认是public-ip + connect-server-port=3000 //长链接服务对外端口, 公网端口 + connect-server-register-attr { //注册到zk里的额外属性,比如配置权重,可在alloc里排序 + weight:1 + } + + gateway-server-bind-ip="" //gatewaySrv 绑定的本地ip (默认anyLocalAddress 0.0.0.0 or ::0) + gateway-server-register-ip=${mp.net.local-ip} //本地ip, 注册到zk中的ip, 默认是local-ip + gateway-server-port=3001 //网关服务端口, 内部端口 + gateway-server-net=tcp //网关服务使用的网络类型tcp/udp/sctp/udt + + gateway-client-port=4000 //UDP 客户端端口 + gateway-server-multicast="239.239.239.88" //239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效 + gateway-client-multicast="239.239.239.99" //239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效 + gateway-client-num=1 //网关客户端连接数 + + admin-server-port=3002 //控制台服务端口, 内部端口 + ws-server-port=0 //websocket对外端口, 公网端口, 0表示禁用websocket + ws-path="/" //websocket path + + public-host-mapping { //本机局域网IP和公网IP的映射关系, 该配置后续会被废弃 + //"10.0.10.156":"111.1.32.137" + //"10.0.10.166":"111.1.33.138" + } + + snd_buf { //tcp/udp 发送缓冲区大小 + connect-server=32k + gateway-server=0 + gateway-client=0 //0表示使用操作系统默认值 + } + + rcv_buf { //tcp/udp 接收缓冲区大小 + connect-server=32k + gateway-server=0 + gateway-client=0 //0表示使用操作系统默认值 + } + + write-buffer-water-mark { //netty 写保护 + connect-server-low=32k + connect-server-high=64k + gateway-server-low=10m + gateway-server-high=20m } - traffic-shaping { + + traffic-shaping { //流量整形配置 gateway-client { - enabled:true + enabled:false check-interval:100ms - write-global-limit:1k + write-global-limit:30k read-global-limit:0 - write-channel-limit:256b + write-channel-limit:3k read-channel-limit:0 } gateway-server { - enabled:true + enabled:false check-interval:100ms write-global-limit:0 - read-global-limit:10k + read-global-limit:30k write-channel-limit:0 - read-channel-limit:0.5k + read-channel-limit:3k } connect-server { @@ -68,11 +118,12 @@ mp { } } + #Zookeeper配置 zk { server-address="127.0.0.1:2181" namespace=mpush - digest=mpush - local-cache-path=/ + digest=mpush //zkCli.sh acl 命令 addauth digest mpush + watch-path=/ retry { #initial amount of time to wait between retries baseSleepTimeMs=3s @@ -85,18 +136,12 @@ mp { sessionTimeoutMs=5s } + #Redis集群配置 redis { - write-to-zk=true - #redis 集群配置,group 是个二维数组,第一层表示有多少组集群,每个集群下面可以有多台机器 - cluster-group:[ - [ - { - host:"111.1.57.148" - port:6379 - password:ShineMoIpo - } - ] - ] + cluster-model=single//single,cluster,sentinel + password=""//your password + nodes:[]//["127.0.0.1:6379"]格式ip:port:password,密码可以不设置ip:port + sentinel-master:"" config { maxTotal:8, maxIdle:4, @@ -113,75 +158,76 @@ mp { testWhileIdle:false, timeBetweenEvictionRunsMillis:60000, blockWhenExhausted:true, - jmxEnabled:true, + jmxEnabled:false, jmxNamePrefix:pool, jmxNameBase:pool } } + #HTTP代理配置 http { - proxy-enabled=false - max-conn-per-host=5 - default-read-timeout=10s - max-content-length=5m - dns-mapping { - "mpush.com":["127.0.0.1:8080","127.0.0.1:8081"] + proxy-enabled=false //启用Http代理 + max-conn-per-host=5 //每个域名的最大链接数, 建议web服务nginx超时时间设长一点, 以便保持长链接 + default-read-timeout=10s //请求超时时间 + max-content-length=5m //response body 最大大小 + dns-mapping { //域名映射外网地址转内部IP, 域名部分不包含端口号 + //"mpush.com":["127.0.0.1:8080", "127.0.0.1:8081"] } } + #线程池配置 thread { pool { - boss { - min:4 + conn-work:0 //接入服务线程池大小,0表示线程数根据cpu核数动态调整(2*cpu) + gateway-server-work:0 //网关服务线程池大小,0表示线程数根据cpu核数动态调整(2*cpu) + http-work:0 //http proxy netty client work pool size,0表示线程数根据cpu核数动态调整(2*cpu) + ack-timer:1 //处理ACK消息超时 + push-task:0 //消息推送中心,推送任务线程池大小, 如果为0表示使用Gateway Server的work线程池,tcp下推荐0 + gateway-client-work:0 //网关客户端线程池大小,0表示线程数根据cpu核数动态调整(2*cpu),该线程池在客户端运行 + push-client:2 //消息推送回调处理,该线程池在客户端运行 + + event-bus { //用户处理内部事件分发 + min:1 max:16 - queue-size:1000 - } - - work { - min:8 - max:32 - queue-size:1000 - } - - event-bus { - min:4 - max:4 queue-size:10000 //大量的online,offline, } - http-proxy { - min:8 - max:64 - queue-size:1000 - } - - biz { - min:4 - max:64 - queue-size:10 - } - - mq { - min:2 + mq { //用户上下线消息, 踢人等 + min:1 max:4 queue-size:10000 } + } + } + + #推送消息流控 + push { + flow-control { //qps = limit/(duration) + global:{ //针对非广播推送的流控,全局有效 + limit:5000 //qps = 5000 + max:0 //UN limit + duration:1s //1s + } - push-callback { - min:2 - max:2 - queue-size:0 + broadcast:{ //针对广播消息的流控,单次任务有效 + limit:3000 //qps = 3000 + max:100000 //10w + duration:1s //1s } - } + } } + #系统监控配置 monitor { - dump-dir=/tmp/logs/mpush/ - dump-stack=false - dump-period=1m - print-log=true + dump-dir=${mp.home}/tmp + dump-stack=false //是否定时dump堆栈 + dump-period=1m //多久监控一次 + print-log=true //是否打印监控日志 + profile-enabled=false //开启性能监控 + profile-slowly-duration=10ms //耗时超过10ms打印日志 } + #SPI扩展配置 spi { thread-pool-factory:"com.mpush.tools.thread.pool.DefaultThreadPoolFactory" dns-mapping-manager:"com.mpush.common.net.HttpProxyDnsMappingManager" diff --git a/mpush-api/pom.xml b/mpush-api/pom.xml index 141fb582..3361a1a6 100644 --- a/mpush-api/pom.xml +++ b/mpush-api/pom.xml @@ -2,23 +2,32 @@ + + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} mpush-api - ${mpush-api-version} - mpush-api jar + mpush-api + MPUSH消息推送系统Api模块 + https://github.com/mpusher/mpush io.netty netty-transport + compile + + + io.netty + netty-codec-http + compile diff --git a/mpush-api/src/main/java/com/mpush/api/Constants.java b/mpush-api/src/main/java/com/mpush/api/Constants.java index 27e5cece..169f0ab0 100644 --- a/mpush-api/src/main/java/com/mpush/api/Constants.java +++ b/mpush-api/src/main/java/com/mpush/api/Constants.java @@ -20,6 +20,7 @@ package com.mpush.api; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Created by ohun on 2015/12/23. @@ -27,9 +28,15 @@ * @author ohun@live.cn */ public interface Constants { - Charset UTF_8 = Charset.forName("UTF-8"); + Charset UTF_8 = StandardCharsets.UTF_8; byte[] EMPTY_BYTES = new byte[0]; - String HTTP_HEAD_READ_TIMEOUT = "readTimeout"; + String EMPTY_STRING = ""; + String ANY_HOST = "0.0.0.0"; + String KICK_CHANNEL_PREFIX = "/mpush/kick/"; + + static String getKickChannel(String hostAndPort) { + return KICK_CHANNEL_PREFIX + hostAndPort; + } } diff --git a/mpush-api/src/main/java/com/mpush/api/MPushContext.java b/mpush-api/src/main/java/com/mpush/api/MPushContext.java new file mode 100644 index 00000000..2d1d1f48 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/MPushContext.java @@ -0,0 +1,45 @@ +/* + * (C) Copyright 2015-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api; + +import com.mpush.api.common.Monitor; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.MQClient; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.api.srd.ServiceRegistry; + +/** + * Created by ohun on 2017/6/21. + * + * @author ohun@live.cn (夜色) + */ +public interface MPushContext { + + Monitor getMonitor(); + + ServiceDiscovery getDiscovery(); + + ServiceRegistry getRegistry(); + + CacheManager getCacheManager(); + + MQClient getMQClient(); + +} diff --git a/mpush-api/src/main/java/com/mpush/api/common/Condition.java b/mpush-api/src/main/java/com/mpush/api/common/Condition.java new file mode 100644 index 00000000..b596d9cc --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/common/Condition.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.common; + +import java.util.Map; +import java.util.function.Predicate; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public interface Condition extends Predicate> { +} diff --git a/mpush-api/src/main/java/com/mpush/api/common/Monitor.java b/mpush-api/src/main/java/com/mpush/api/common/Monitor.java new file mode 100644 index 00000000..882a2bd9 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/common/Monitor.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2015-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.common; + +import java.util.concurrent.Executor; + +/** + * Created by ohun on 2017/7/15. + * + * @author ohun@live.cn (夜色) + */ +public interface Monitor { + + void monitor(String name, Thread thread); + + void monitor(String name, Executor executor); +} diff --git a/mpush-api/src/main/java/com/mpush/api/common/ServerEventListener.java b/mpush-api/src/main/java/com/mpush/api/common/ServerEventListener.java new file mode 100644 index 00000000..1af33838 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/common/ServerEventListener.java @@ -0,0 +1,85 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.common; + +import com.mpush.api.event.*; +import com.mpush.api.spi.Plugin; + +/** + * Created by ohun on 16/10/19. + * + * @author ohun@live.cn (夜色) + */ +public interface ServerEventListener extends Plugin { + + /** + * 该事件通过guava EventBus发出,实现接口的方法必须增加 + * + * @Subscribe 和 @AllowConcurrentEvents注解, + * 并在构造函数调用EventBus.register(this); + */ + default void on(ServerStartupEvent event) { + } + + /** + * 该事件通过guava EventBus发出,实现接口的方法必须增加 + * + * @Subscribe 和 @AllowConcurrentEvents注解, + * 并在构造函数调用EventBus.register(this); + */ + default void on(ServerShutdownEvent server) { + } + + /** + * 该事件通过guava EventBus发出,实现接口的方法必须增加 + * + * @Subscribe 和 @AllowConcurrentEvents注解, + * 并在构造函数调用EventBus.register(this); + */ + default void on(RouterChangeEvent event) { + } + + /** + * 该事件通过guava EventBus发出,实现接口的方法必须增加 + * + * @Subscribe 和 @AllowConcurrentEvents注解, + * 并在构造函数调用EventBus.register(this); + */ + default void on(KickUserEvent event) { + } + + /** + * 该事件通过guava EventBus发出,实现接口的方法必须增加 + * + * @Subscribe 和 @AllowConcurrentEvents注解, + * 并在构造函数调用EventBus.register(this); + */ + default void on(UserOnlineEvent event) { + } + + /** + * 该事件通过guava EventBus发出,实现接口的方法必须增加 + * + * @Subscribe 和 @AllowConcurrentEvents注解, + * 并在构造函数调用EventBus.register(this); + */ + default void on(UserOfflineEvent event) { + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/connection/Connection.java b/mpush-api/src/main/java/com/mpush/api/connection/Connection.java index 7eee23f2..760bdd4f 100644 --- a/mpush-api/src/main/java/com/mpush/api/connection/Connection.java +++ b/mpush-api/src/main/java/com/mpush/api/connection/Connection.java @@ -26,12 +26,13 @@ /** * Created by ohun on 2015/12/22. + * + * @author ohun@live.cn (夜色) */ public interface Connection { - int STATUS_NEW = 0; - int STATUS_CONNECTED = 1; - int STATUS_DISCONNECTED = 2; - int STATUS_TIMEOUT = 3; + byte STATUS_NEW = 0; + byte STATUS_CONNECTED = 1; + byte STATUS_DISCONNECTED = 2; void init(Channel channel, boolean security); @@ -49,11 +50,13 @@ public interface Connection { boolean isConnected(); - boolean heartbeatTimeout(); + boolean isReadTimeout(); + + boolean isWriteTimeout(); void updateLastReadTime(); - long getLastReadTime(); + void updateLastWriteTime(); Channel getChannel(); diff --git a/mpush-api/src/main/java/com/mpush/api/connection/ConnectionManager.java b/mpush-api/src/main/java/com/mpush/api/connection/ConnectionManager.java index 58f12e76..896f0edd 100644 --- a/mpush-api/src/main/java/com/mpush/api/connection/ConnectionManager.java +++ b/mpush-api/src/main/java/com/mpush/api/connection/ConnectionManager.java @@ -25,6 +25,8 @@ /** * Created by ohun on 2015/12/30. + * + * @author ohun@live.cn (夜色) */ public interface ConnectionManager { @@ -34,7 +36,7 @@ public interface ConnectionManager { void add(Connection connection); - List getConnections(); + int getConnNum(); void init(); diff --git a/mpush-api/src/main/java/com/mpush/api/connection/SessionContext.java b/mpush-api/src/main/java/com/mpush/api/connection/SessionContext.java index 46fb0647..a68361e2 100644 --- a/mpush-api/src/main/java/com/mpush/api/connection/SessionContext.java +++ b/mpush-api/src/main/java/com/mpush/api/connection/SessionContext.java @@ -19,7 +19,8 @@ package com.mpush.api.connection; -import com.mpush.api.router.ClientType; + +import com.mpush.api.router.ClientClassifier; /** * Created by ohun on 2015/12/22. @@ -32,9 +33,10 @@ public final class SessionContext { public String clientVersion; public String deviceId; public String userId; - public int heartbeat; + public String tags; + public int heartbeat = 10000;// 10s public Cipher cipher; - private int clientType; + private byte clientType; public void changeCipher(Cipher cipher) { this.cipher = cipher; @@ -75,19 +77,28 @@ public boolean handshakeOk() { public int getClientType() { if (clientType == 0) { - clientType = ClientType.find(osName).type; + clientType = (byte) ClientClassifier.I.getClientType(osName); } return clientType; } + public boolean isSecurity() { + return cipher != null; + } + @Override public String toString() { - return "SessionContext [osName=" + osName - + ", osVersion=" + osVersion - + ", clientVersion=" + clientVersion - + ", deviceId=" + deviceId - + ", heartbeat=" + heartbeat - + "]"; - } + if (userId == null && deviceId == null) { + return ""; + } + return "{" + + "osName='" + osName + '\'' + + ", osVersion='" + osVersion + '\'' + + ", deviceId='" + deviceId + '\'' + + ", userId='" + userId + '\'' + + ", tags='" + tags + '\'' + + ", heartbeat=" + heartbeat + + '}'; + } } diff --git a/mpush-api/src/main/java/com/mpush/api/event/ConnectionCloseEvent.java b/mpush-api/src/main/java/com/mpush/api/event/ConnectionCloseEvent.java index a31dea4b..4d2779a1 100644 --- a/mpush-api/src/main/java/com/mpush/api/event/ConnectionCloseEvent.java +++ b/mpush-api/src/main/java/com/mpush/api/event/ConnectionCloseEvent.java @@ -23,6 +23,8 @@ /** * Created by ohun on 2016/1/10. + * + * @author ohun@live.cn (夜色) */ public final class ConnectionCloseEvent implements Event { public final Connection connection; diff --git a/mpush-api/src/main/java/com/mpush/api/event/ConnectionConnectEvent.java b/mpush-api/src/main/java/com/mpush/api/event/ConnectionConnectEvent.java new file mode 100644 index 00000000..86a9f328 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/event/ConnectionConnectEvent.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.event; + +import com.mpush.api.connection.Connection; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public final class ConnectionConnectEvent implements Event { + public final Connection connection; + + public ConnectionConnectEvent(Connection connection) { + this.connection = connection; + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/event/ServerShutdownEvent.java b/mpush-api/src/main/java/com/mpush/api/event/ServerShutdownEvent.java new file mode 100644 index 00000000..4a4e1d29 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/event/ServerShutdownEvent.java @@ -0,0 +1,28 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.event; + +/** + * Created by ohun on 16/10/19. + * + * @author ohun@live.cn (夜色) + */ +public final class ServerShutdownEvent implements Event { +} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/listener/MessageListener.java b/mpush-api/src/main/java/com/mpush/api/event/ServerStartupEvent.java similarity index 71% rename from mpush-cache/src/main/java/com/mpush/cache/redis/listener/MessageListener.java rename to mpush-api/src/main/java/com/mpush/api/event/ServerStartupEvent.java index da6989d1..1ffd3a79 100644 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/listener/MessageListener.java +++ b/mpush-api/src/main/java/com/mpush/api/event/ServerStartupEvent.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,15 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.cache.redis.listener; - - -public interface MessageListener { - - void onMessage(String channel, String message); +package com.mpush.api.event; +/** + * Created by ohun on 16/10/19. + * + * @author ohun@live.cn (夜色) + */ +public final class ServerStartupEvent implements Event { } diff --git a/mpush-api/src/main/java/com/mpush/api/event/Topics.java b/mpush-api/src/main/java/com/mpush/api/event/Topics.java new file mode 100644 index 00000000..fc6815d5 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/event/Topics.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.event; + +/** + * Created by ohun on 16/9/19. + * + * @author ohun@live.cn (夜色) + */ +public interface Topics { + String ONLINE_CHANNEL = "/mpush/online/"; + + String OFFLINE_CHANNEL = "/mpush/offline/"; +} diff --git a/mpush-api/src/main/java/com/mpush/api/Message.java b/mpush-api/src/main/java/com/mpush/api/message/Message.java similarity index 73% rename from mpush-api/src/main/java/com/mpush/api/Message.java rename to mpush-api/src/main/java/com/mpush/api/message/Message.java index 581c48fb..4b8b4715 100644 --- a/mpush-api/src/main/java/com/mpush/api/Message.java +++ b/mpush-api/src/main/java/com/mpush/api/message/Message.java @@ -17,7 +17,7 @@ * ohun@live.cn (夜色) */ -package com.mpush.api; +package com.mpush.api.message; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; @@ -32,8 +32,22 @@ public interface Message { Connection getConnection(); + void decodeBody(); + + void encodeBody(); + + /** + * 发送当前message, 并根据情况最body进行数据压缩、加密 + * + * @param listener 发送成功后的回调 + */ void send(ChannelFutureListener listener); + /** + * 发送当前message, 不会对body进行数据压缩、加密, 原样发送 + * + * @param listener 发送成功后的回调 + */ void sendRaw(ChannelFutureListener listener); Packet getPacket(); diff --git a/mpush-api/src/main/java/com/mpush/api/MessageHandler.java b/mpush-api/src/main/java/com/mpush/api/message/MessageHandler.java similarity index 96% rename from mpush-api/src/main/java/com/mpush/api/MessageHandler.java rename to mpush-api/src/main/java/com/mpush/api/message/MessageHandler.java index 5d7ee6b0..42ee3e41 100644 --- a/mpush-api/src/main/java/com/mpush/api/MessageHandler.java +++ b/mpush-api/src/main/java/com/mpush/api/message/MessageHandler.java @@ -17,7 +17,7 @@ * ohun@live.cn (夜色) */ -package com.mpush.api; +package com.mpush.api.message; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; diff --git a/mpush-api/src/main/java/com/mpush/api/PacketReceiver.java b/mpush-api/src/main/java/com/mpush/api/message/PacketReceiver.java similarity index 96% rename from mpush-api/src/main/java/com/mpush/api/PacketReceiver.java rename to mpush-api/src/main/java/com/mpush/api/message/PacketReceiver.java index d26e5cf7..53aa4748 100644 --- a/mpush-api/src/main/java/com/mpush/api/PacketReceiver.java +++ b/mpush-api/src/main/java/com/mpush/api/message/PacketReceiver.java @@ -17,7 +17,7 @@ * ohun@live.cn (夜色) */ -package com.mpush.api; +package com.mpush.api.message; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; diff --git a/mpush-api/src/main/java/com/mpush/api/protocol/Command.java b/mpush-api/src/main/java/com/mpush/api/protocol/Command.java index ad2e739a..379d388e 100644 --- a/mpush-api/src/main/java/com/mpush/api/protocol/Command.java +++ b/mpush-api/src/main/java/com/mpush/api/protocol/Command.java @@ -21,6 +21,8 @@ /** * Created by ohun on 2015/12/22. + * + * @author ohun@live.cn */ public enum Command { HEARTBEAT(1), @@ -45,6 +47,8 @@ public enum Command { GATEWAY_CHAT(20), GROUP(21), GATEWAY_GROUP(22), + ACK(23), + NACK(24), UNKNOWN(-1); Command(int cmd) { @@ -54,7 +58,8 @@ public enum Command { public final byte cmd; public static Command toCMD(byte b) { - if (b > 0 && b < values().length) return values()[b - 1]; + Command[] values = values(); + if (b > 0 && b < values.length) return values[b - 1]; return UNKNOWN; } } diff --git a/mpush-api/src/main/java/com/mpush/api/protocol/JsonPacket.java b/mpush-api/src/main/java/com/mpush/api/protocol/JsonPacket.java new file mode 100644 index 00000000..ecbb16ff --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/protocol/JsonPacket.java @@ -0,0 +1,95 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.protocol; + + +import com.mpush.api.Constants; +import com.mpush.api.spi.common.Json; +import com.mpush.api.spi.common.JsonFactory; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +import java.util.Map; + +/** + * Created by ohun on 2016/12/16. + * + * @author ohun@live.cn (夜色) + */ +public final class JsonPacket extends Packet { + + public Map body; + + public JsonPacket() { + super(Command.UNKNOWN); + this.addFlag(FLAG_JSON_BODY); + } + + public JsonPacket(Command cmd) { + super(cmd); + this.addFlag(FLAG_JSON_BODY); + } + + public JsonPacket(Command cmd, int sessionId) { + super(cmd, sessionId); + this.addFlag(FLAG_JSON_BODY); + } + + @Override + @SuppressWarnings("unchecked") + public Map getBody() { + return body; + } + + @Override + @SuppressWarnings("unchecked") + public void setBody(T body) { + this.body = (Map) body; + } + + @Override + public int getBodyLength() { + return body == null ? 0 : body.size(); + } + + @Override + public Packet response(Command command) { + return new JsonPacket(command, sessionId); + } + + @Override + public Object toFrame(Channel channel) { + byte[] json = Json.JSON.toJson(this).getBytes(Constants.UTF_8); + return new TextWebSocketFrame(Unpooled.wrappedBuffer(json)); + } + + @Override + public String toString() { + return "JsonPacket{" + + "cmd=" + cmd + + ", cc=" + cc + + ", flags=" + flags + + ", sessionId=" + sessionId + + ", lrc=" + lrc + + ", body=" + body + + '}'; + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/protocol/Packet.java b/mpush-api/src/main/java/com/mpush/api/protocol/Packet.java index d9921802..561cdba9 100644 --- a/mpush-api/src/main/java/com/mpush/api/protocol/Packet.java +++ b/mpush-api/src/main/java/com/mpush/api/protocol/Packet.java @@ -21,6 +21,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; + +import java.net.InetSocketAddress; /** * Created by ohun on 2015/12/19. @@ -28,21 +31,26 @@ * * @author ohun@live.cn */ -public final class Packet { +@SuppressWarnings("unchecked") +public class Packet { public static final int HEADER_LEN = 13; - public static final byte FLAG_CRYPTO = 0x01; - public static final byte FLAG_COMPRESS = 0x02; + + public static final byte FLAG_CRYPTO = 1; + public static final byte FLAG_COMPRESS = 2; + public static final byte FLAG_BIZ_ACK = 4; + public static final byte FLAG_AUTO_ACK = 8; + public static final byte FLAG_JSON_BODY = 16; public static final byte HB_PACKET_BYTE = -33; public static final byte[] HB_PACKET_BYTES = new byte[]{HB_PACKET_BYTE}; - public static final Packet HB_PACKE = new Packet(Command.HEARTBEAT); + public static final Packet HB_PACKET = new Packet(Command.HEARTBEAT); public byte cmd; //命令 - public short cc; //校验码 暂时没有用到 + transient public short cc; //校验码 暂时没有用到 public byte flags; //特性,如是否加密,是否压缩等 public int sessionId; // 会话id。客户端生成。 - public byte lrc; // 校验,纵向冗余校验。只校验head - public byte[] body; + transient public byte lrc; // 校验,纵向冗余校验。只校验head + transient public byte[] body; public Packet(byte cmd) { this.cmd = cmd; @@ -74,6 +82,14 @@ public boolean hasFlag(byte flag) { return (flags & flag) != 0; } + public T getBody() { + return (T) body; + } + + public void setBody(T body) { + this.body = (byte[]) body; + } + public short calcCheckCode() { short checkCode = 0; if (body != null) { @@ -99,7 +115,7 @@ public byte calcLrc() { return lrc; } - public boolean vaildCheckCode() { + public boolean validCheckCode() { return calcCheckCode() == cc; } @@ -107,9 +123,54 @@ public boolean validLrc() { return (lrc ^ calcLrc()) == 0; } + public InetSocketAddress sender() { + return null; + } + + public void setRecipient(InetSocketAddress sender) { + } + + public Packet response(Command command) { + return new Packet(command, sessionId); + } + + public Object toFrame(Channel channel) { + return this; + } + + public static Packet decodePacket(Packet packet, ByteBuf in, int bodyLength) { + packet.cc = in.readShort();//read cc + packet.flags = in.readByte();//read flags + packet.sessionId = in.readInt();//read sessionId + packet.lrc = in.readByte();//read lrc + + //read body + if (bodyLength > 0) { + in.readBytes(packet.body = new byte[bodyLength]); + } + return packet; + } + + public static void encodePacket(Packet packet, ByteBuf out) { + if (packet.cmd == Command.HEARTBEAT.cmd) { + out.writeByte(Packet.HB_PACKET_BYTE); + } else { + out.writeInt(packet.getBodyLength()); + out.writeByte(packet.cmd); + out.writeShort(packet.cc); + out.writeByte(packet.flags); + out.writeInt(packet.sessionId); + out.writeByte(packet.lrc); + if (packet.getBodyLength() > 0) { + out.writeBytes(packet.body); + } + } + packet.body = null; + } + @Override public String toString() { - return "Packet{" + + return "{" + "cmd=" + cmd + ", cc=" + cc + ", flags=" + flags + diff --git a/mpush-api/src/main/java/com/mpush/api/protocol/UDPPacket.java b/mpush-api/src/main/java/com/mpush/api/protocol/UDPPacket.java new file mode 100644 index 00000000..74ce4ee1 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/protocol/UDPPacket.java @@ -0,0 +1,82 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.protocol; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.socket.DatagramPacket; + +import java.net.InetSocketAddress; + +import static com.mpush.api.protocol.Command.HEARTBEAT; + +/** + * Created by ohun on 16/10/21. + * + * @author ohun@live.cn (夜色) + */ +public final class UDPPacket extends Packet { + private InetSocketAddress address; + + public UDPPacket(byte cmd, InetSocketAddress sender) { + super(cmd); + this.address = sender; + } + + public UDPPacket(Command cmd, int sessionId, InetSocketAddress sender) { + super(cmd, sessionId); + this.address = sender; + } + + public UDPPacket(byte cmd) { + super(cmd); + } + + public UDPPacket(Command cmd) { + super(cmd); + } + + public UDPPacket(Command cmd, int sessionId) { + super(cmd, sessionId); + } + + @Override + public InetSocketAddress sender() { + return address; + } + + @Override + public void setRecipient(InetSocketAddress recipient) { + this.address = recipient; + } + + @Override + public Packet response(Command command) { + return new UDPPacket(command, sessionId, address); + } + + @Override + public Object toFrame(Channel channel) { + int capacity = cmd == HEARTBEAT.cmd ? 1 : HEADER_LEN + getBodyLength(); + ByteBuf out = channel.alloc().buffer(capacity, capacity); + encodePacket(this, out); + return new DatagramPacket(out, sender()); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/push/AckModel.java b/mpush-api/src/main/java/com/mpush/api/push/AckModel.java new file mode 100644 index 00000000..aff9843a --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/push/AckModel.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.push; + +import com.mpush.api.protocol.Packet; + +/** + * Created by ohun on 16/9/6. + * + * @author ohun@live.cn (夜色) + */ +public enum AckModel { + NO_ACK((byte) 0),//不需要ACK + AUTO_ACK(Packet.FLAG_AUTO_ACK),//客户端收到消息后自动确认消息 + BIZ_ACK(Packet.FLAG_BIZ_ACK);//由客户端业务自己确认消息是否到达 + public final byte flag; + + AckModel(byte flag) { + this.flag = flag; + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/push/BroadcastController.java b/mpush-api/src/main/java/com/mpush/api/push/BroadcastController.java new file mode 100644 index 00000000..7c463661 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/push/BroadcastController.java @@ -0,0 +1,51 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.push; + +import java.util.List; + +/** + * Created by ohun on 16/10/25. + * + * @author ohun@live.cn (夜色) + */ +public interface BroadcastController { + + String taskId(); + + int qps(); + + void updateQps(int qps); + + boolean isDone(); + + int sendCount(); + + void cancel(); + + boolean isCancelled(); + + int incSendCount(int count); + + void success(String... userIds); + + List successUserIds(); + +} diff --git a/mpush-api/src/main/java/com/mpush/api/push/MsgType.java b/mpush-api/src/main/java/com/mpush/api/push/MsgType.java new file mode 100644 index 00000000..8ac3309a --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/push/MsgType.java @@ -0,0 +1,23 @@ +package com.mpush.api.push; + +public enum MsgType { + NOTIFICATION("提醒", 1),//会在通知栏显示 + MESSAGE("消息", 2),//不会在通知栏显示,业务自定义消息 + NOTIFICATION_AND_MESSAGE("提醒+消息", 3);//1+2 + + MsgType(String desc, int value) { + this.desc = desc; + this.value = value; + } + + private final String desc; + private final int value; + + public String getDesc() { + return desc; + } + + public int getValue() { + return value; + } +} \ No newline at end of file diff --git a/mpush-api/src/main/java/com/mpush/api/push/PushCallback.java b/mpush-api/src/main/java/com/mpush/api/push/PushCallback.java new file mode 100644 index 00000000..03f9480a --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/push/PushCallback.java @@ -0,0 +1,66 @@ +package com.mpush.api.push; + +import com.mpush.api.router.ClientLocation; + +import java.util.List; + +/** + * Created by ohun on 2015/12/30. + * + * @author ohun@live.cn + */ +public interface PushCallback { + + default void onResult(PushResult result) { + switch (result.resultCode) { + case PushResult.CODE_SUCCESS: + onSuccess(result.userId, result.location); + break; + case PushResult.CODE_FAILURE: + onFailure(result.userId, result.location); + break; + case PushResult.CODE_OFFLINE: + onOffline(result.userId, result.location); + break; + case PushResult.CODE_TIMEOUT: + onTimeout(result.userId, result.location); + break; + } + } + + /** + * 推送成功, 指定用户推送时重写此方法 + * + * @param userId 成功的用户, 如果是广播, 值为空 + * @param location 用户所在机器, 如果是广播, 值为空 + */ + default void onSuccess(String userId, ClientLocation location) { + } + + /** + * 推送失败 + * + * @param userId 推送用户 + * @param location 用户所在机器 + */ + default void onFailure(String userId, ClientLocation location) { + } + + /** + * 推送用户不在线 + * + * @param userId 推送用户 + * @param location 用户所在机器 + */ + default void onOffline(String userId, ClientLocation location) { + } + + /** + * 推送超时 + * + * @param userId 推送用户 + * @param location 用户所在机器 + */ + default void onTimeout(String userId, ClientLocation location) { + } +} \ No newline at end of file diff --git a/mpush-api/src/main/java/com/mpush/api/push/PushContext.java b/mpush-api/src/main/java/com/mpush/api/push/PushContext.java new file mode 100644 index 00000000..9de96fd3 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/push/PushContext.java @@ -0,0 +1,205 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.push; + +import com.mpush.api.Constants; + +import java.util.List; +import java.util.Set; +import java.util.UUID; + +/** + * Created by ohun on 16/9/8. + * + * @author ohun@live.cn (夜色) + */ +public class PushContext { + /** + * 待推送的内容 + */ + private byte[] context; + + /** + * 待推送的消息 + */ + private PushMsg pushMsg; + + /** + * 目标用户 + */ + private String userId; + + /** + * 目标用户,批量 + */ + private List userIds; + + /** + * 消息ack模式 + */ + private AckModel ackModel = AckModel.NO_ACK; + + /** + * 推送成功后的回调 + */ + private PushCallback callback; + + /** + * 推送超时时间 + */ + private int timeout = 3000; + + //================================broadcast=====================================// + + /** + * 全网广播在线用户 + */ + private boolean broadcast = false; + + /** + * 用户标签过滤,目前只有include, 后续会增加exclude + */ + private Set tags; + + /** + * 条件表达式, 满足条件的用户会被推送,目前支持的脚本语言为js + * 可以使用的参数为 userId,tags,clientVersion,osName,osVersion + * 比如 : + * 灰度:userId % 100 < 20 + * 包含test标签:tags!=null && tags.indexOf("test")!=-1 + * 判断客户端版本号:clientVersion.indexOf("android")!=-1 && clientVersion.replace(/[^\d]/g,"") > 20 + * 等等 + */ + private String condition; + + /** + * 广播推送的时候可以考虑生成一个ID, 便于控制任务。 + */ + private String taskId; + + public PushContext(byte[] context) { + this.context = context; + } + + public PushContext(PushMsg pushMsg) { + this.pushMsg = pushMsg; + } + + public static PushContext build(String msg) { + return new PushContext(msg.getBytes(Constants.UTF_8)); + } + + public static PushContext build(PushMsg msg) { + return new PushContext(msg); + } + + public static String genTaskId() { + return UUID.randomUUID().toString(); + } + + public byte[] getContext() { + return context; + } + + public String getUserId() { + return userId; + } + + public PushContext setUserId(String userId) { + this.userId = userId; + return this; + } + + public List getUserIds() { + return userIds; + } + + public PushContext setUserIds(List userIds) { + this.userIds = userIds; + return this; + } + + public AckModel getAckModel() { + return ackModel; + } + + public PushContext setAckModel(AckModel ackModel) { + this.ackModel = ackModel; + return this; + } + + public PushCallback getCallback() { + return callback; + } + + public PushContext setCallback(PushCallback callback) { + this.callback = callback; + return this; + } + + public PushMsg getPushMsg() { + return pushMsg; + } + + public boolean isBroadcast() { + return broadcast; + } + + public PushContext setBroadcast(boolean broadcast) { + this.broadcast = broadcast; + return this; + } + + public int getTimeout() { + return timeout; + } + + public PushContext setTimeout(int timeout) { + this.timeout = timeout; + return this; + } + + public Set getTags() { + return tags; + } + + public PushContext setTags(Set tags) { + this.tags = tags; + return this; + } + + public String getCondition() { + return condition; + } + + public PushContext setCondition(String condition) { + this.condition = condition; + return this; + } + + public String getTaskId() { + return taskId; + } + + public PushContext setTaskId(String taskId) { + this.taskId = taskId; + return this; + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/push/PushContent.java b/mpush-api/src/main/java/com/mpush/api/push/PushMsg.java similarity index 63% rename from mpush-api/src/main/java/com/mpush/api/push/PushContent.java rename to mpush-api/src/main/java/com/mpush/api/push/PushMsg.java index e4e741c3..f269f674 100644 --- a/mpush-api/src/main/java/com/mpush/api/push/PushContent.java +++ b/mpush-api/src/main/java/com/mpush/api/push/PushMsg.java @@ -19,8 +19,6 @@ package com.mpush.api.push; -import java.io.Serializable; - /** * msgId、msgType 必填 @@ -33,20 +31,19 @@ * msgType=3 :消息+提醒 * 作为一个push消息过去。和jpush不一样。jpush的消息和提醒是分开发送的。 */ -public final class PushContent implements Serializable { - private static final long serialVersionUID = -1805329333995385960L; +public final class PushMsg { + private final MsgType msgType; //type private String msgId; //返回使用 private String content; //content - private int msgType; //type - public PushContent(int msgType) { + public PushMsg(MsgType msgType) { this.msgType = msgType; } - public static PushContent build(PushType msgType, String content) { - PushContent pushContent = new PushContent(msgType.getValue()); - pushContent.setContent(content); - return pushContent; + public static PushMsg build(MsgType msgType, String content) { + PushMsg pushMessage = new PushMsg(msgType); + pushMessage.setContent(content); + return pushMessage; } public String getMsgId() { @@ -54,7 +51,7 @@ public String getMsgId() { } public int getMsgType() { - return msgType; + return msgType.getValue(); } public void setMsgId(String msgId) { @@ -69,26 +66,5 @@ public void setContent(String content) { this.content = content; } - public enum PushType { - NOTIFICATION("提醒", 1), - MESSAGE("消息", 2), - NOTIFICATIONANDMESSAGE("提醒+消息", 3); - - PushType(String desc, int value) { - this.desc = desc; - this.value = value; - } - - private final String desc; - private final int value; - - public String getDesc() { - return desc; - } - - public int getValue() { - return value; - } - } } \ No newline at end of file diff --git a/mpush-api/src/main/java/com/mpush/api/push/PushResult.java b/mpush-api/src/main/java/com/mpush/api/push/PushResult.java new file mode 100644 index 00000000..55dc23c7 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/push/PushResult.java @@ -0,0 +1,104 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.push; + +import com.mpush.api.router.ClientLocation; + +import java.util.Arrays; + +/** + * Created by ohun on 16/9/17. + * + * @author ohun@live.cn (夜色) + */ +public class PushResult { + public static final int CODE_SUCCESS = 1; + public static final int CODE_FAILURE = 2; + public static final int CODE_OFFLINE = 3; + public static final int CODE_TIMEOUT = 4; + public int resultCode; + public String userId; + public Object[] timeLine; + public ClientLocation location; + + public PushResult(int resultCode) { + this.resultCode = resultCode; + } + + public int getResultCode() { + return resultCode; + } + + public PushResult setResultCode(int resultCode) { + this.resultCode = resultCode; + return this; + } + + public String getUserId() { + return userId; + } + + public PushResult setUserId(String userId) { + this.userId = userId; + return this; + } + + public Object[] getTimeLine() { + return timeLine; + } + + public PushResult setTimeLine(Object[] timeLine) { + this.timeLine = timeLine; + return this; + } + + public ClientLocation getLocation() { + return location; + } + + public PushResult setLocation(ClientLocation location) { + this.location = location; + return this; + } + + public String getResultDesc() { + switch (resultCode) { + case CODE_SUCCESS: + return "success"; + case CODE_FAILURE: + return "failure"; + case CODE_OFFLINE: + return "offline"; + case CODE_TIMEOUT: + return "timeout"; + } + return Integer.toString(CODE_TIMEOUT); + } + + @Override + public String toString() { + return "PushResult{" + + "resultCode=" + getResultDesc() + + ", userId='" + userId + '\'' + + ", timeLine=" + Arrays.toString(timeLine) + + ", " + location + + '}'; + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/push/PushSender.java b/mpush-api/src/main/java/com/mpush/api/push/PushSender.java index 3c20996e..c1eb84d6 100644 --- a/mpush-api/src/main/java/com/mpush/api/push/PushSender.java +++ b/mpush-api/src/main/java/com/mpush/api/push/PushSender.java @@ -19,12 +19,10 @@ package com.mpush.api.push; -import com.mpush.api.router.ClientLocation; +import com.mpush.api.MPushContext; import com.mpush.api.service.Service; -import com.mpush.api.spi.SpiLoader; import com.mpush.api.spi.client.PusherFactory; -import java.util.Collection; import java.util.concurrent.FutureTask; /** @@ -34,25 +32,40 @@ */ public interface PushSender extends Service { + /** + * 创建PushSender实例 + * + * @return PushSender + */ static PushSender create() { - return SpiLoader.load(PusherFactory.class).get(); + return PusherFactory.create(); } - void send(String content, Collection userIds, Callback callback); + /** + * 推送push消息 + * + * @param context 推送参数 + * @return FutureTask 可用于同步调用 + */ + FutureTask send(PushContext context); - FutureTask send(String content, String userId, Callback callback); - - void send(byte[] content, Collection userIds, Callback callback); - - FutureTask send(byte[] content, String userId, Callback callback); - - interface Callback { - void onSuccess(String userId, ClientLocation location); - - void onFailure(String userId, ClientLocation location); + default FutureTask send(String context, String userId, PushCallback callback) { + return send(PushContext + .build(context) + .setUserId(userId) + .setCallback(callback) + ); + } - void onOffline(String userId, ClientLocation location); + default FutureTask send(String context, String userId, AckModel ackModel, PushCallback callback) { + return send(PushContext + .build(context) + .setAckModel(ackModel) + .setUserId(userId) + .setCallback(callback) + ); + } - void onTimeout(String userId, ClientLocation location); + default void setMPushContext(MPushContext context) { } } diff --git a/mpush-api/src/main/java/com/mpush/api/router/ClientClassifier.java b/mpush-api/src/main/java/com/mpush/api/router/ClientClassifier.java new file mode 100644 index 00000000..406dbe91 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/router/ClientClassifier.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.router; + +import com.mpush.api.spi.router.ClientClassifierFactory; + +/** + * Created by ohun on 16/10/26. + * + * @author ohun@live.cn (夜色) + */ +public interface ClientClassifier { + ClientClassifier I = ClientClassifierFactory.create(); + + int getClientType(String osName); +} diff --git a/mpush-api/src/main/java/com/mpush/api/router/ClientLocation.java b/mpush-api/src/main/java/com/mpush/api/router/ClientLocation.java index ca1f4102..311bfc26 100644 --- a/mpush-api/src/main/java/com/mpush/api/router/ClientLocation.java +++ b/mpush-api/src/main/java/com/mpush/api/router/ClientLocation.java @@ -34,6 +34,11 @@ public final class ClientLocation { */ private String host; + /** + * 长链接所在的机器端口 + */ + private int port; + /** * 客户端系统类型 */ @@ -68,6 +73,15 @@ public ClientLocation setHost(String host) { return this; } + public int getPort() { + return port; + } + + public ClientLocation setPort(int port) { + this.port = port; + return this; + } + public String getOsName() { return osName; } @@ -102,11 +116,32 @@ public void setConnId(String connId) { public int getClientType() { if (clientType == 0) { - clientType = ClientType.find(osName).type; + clientType = ClientClassifier.I.getClientType(osName); } return clientType; } + public boolean isOnline() { + return connId != null; + } + + public boolean isOffline() { + return connId == null; + } + + public ClientLocation offline() { + this.connId = null; + return this; + } + + public boolean isThisMachine(String host, int port) { + return this.port == port && this.host.equals(host); + } + + public String getHostAndPort() { + return host + ":" + port; + } + public static ClientLocation from(Connection connection) { SessionContext context = connection.getSessionContext(); ClientLocation location = new ClientLocation(); @@ -135,7 +170,7 @@ public int hashCode() { @Override public String toString() { return "ClientLocation{" + - "host='" + host + '\'' + + "host='" + host + ":" + port + "\'" + ", osName='" + osName + '\'' + ", clientVersion='" + clientVersion + '\'' + ", deviceId='" + deviceId + '\'' + diff --git a/mpush-api/src/main/java/com/mpush/api/router/RouterManager.java b/mpush-api/src/main/java/com/mpush/api/router/RouterManager.java index cd1bc7e3..7402935f 100644 --- a/mpush-api/src/main/java/com/mpush/api/router/RouterManager.java +++ b/mpush-api/src/main/java/com/mpush/api/router/RouterManager.java @@ -31,35 +31,35 @@ public interface RouterManager { /** * 注册路由 * - * @param userId - * @param router - * @return + * @param userId 用户ID + * @param router 新路由 + * @return 如果有旧的的路由信息则返回之,否则返回空。 */ R register(String userId, R router); /** * 删除路由 * - * @param userId - * @param clientType - * @return + * @param userId 用户ID + * @param clientType 客户端类型 + * @return true:成功,false:失败 */ boolean unRegister(String userId, int clientType); /** * 查询路由 * - * @param userId - * @return + * @param userId 用户ID + * @return userId对应的所有的路由信息 */ Set lookupAll(String userId); /** - * 查询路由 + * 查询指定设备类型的用户路由信息 * - * @param userId - * @param clientType - * @return + * @param userId 用户ID + * @param clientType 客户端类型 + * @return 指定类型的路由信息 */ R lookup(String userId, int clientType); } diff --git a/mpush-api/src/main/java/com/mpush/api/service/BaseService.java b/mpush-api/src/main/java/com/mpush/api/service/BaseService.java index 8d63c14b..b967e894 100644 --- a/mpush-api/src/main/java/com/mpush/api/service/BaseService.java +++ b/mpush-api/src/main/java/com/mpush/api/service/BaseService.java @@ -19,8 +19,7 @@ package com.mpush.api.service; -import java.util.concurrent.Future; -import java.util.concurrent.FutureTask; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -41,49 +40,67 @@ public boolean isRunning() { return started.get(); } - protected void tryStart(Listener listener, Function function) { - listener = wrap(listener); + protected void tryStart(Listener l, FunctionEx function) { + FutureListener listener = wrap(l); if (started.compareAndSet(false, true)) { try { init(); function.apply(listener); - listener.onSuccess("service " + this.getClass().getSimpleName() + " start success"); + listener.monitor(this);//主要用于异步,否则应该放置在function.apply(listener)之前 } catch (Throwable e) { listener.onFailure(e); throw new ServiceException(e); } } else { - listener.onFailure(new ServiceException("service already started.")); + if (throwIfStarted()) { + listener.onFailure(new ServiceException("service already started.")); + } else { + listener.onSuccess(); + } } } - protected void tryStop(Listener listener, Function function) { - listener = wrap(listener); + protected void tryStop(Listener l, FunctionEx function) { + FutureListener listener = wrap(l); if (started.compareAndSet(true, false)) { try { function.apply(listener); - listener.onSuccess("service " + this.getClass().getSimpleName() + " stop success"); + listener.monitor(this);//主要用于异步,否则应该放置在function.apply(listener)之前 } catch (Throwable e) { listener.onFailure(e); throw new ServiceException(e); } } else { - listener.onFailure(new ServiceException("service already stopped.")); + if (throwIfStopped()) { + listener.onFailure(new ServiceException("service already stopped.")); + } else { + listener.onSuccess(); + } } } - public final Future start() { - FutureListener listener = new FutureListener(); + public final CompletableFuture start() { + FutureListener listener = new FutureListener(started); start(listener); return listener; } - public final Future stop() { - FutureListener listener = new FutureListener(); + public final CompletableFuture stop() { + FutureListener listener = new FutureListener(started); stop(listener); return listener; } + @Override + public final boolean syncStart() { + return start().join(); + } + + @Override + public final boolean syncStop() { + return stop().join(); + } + @Override public void start(Listener listener) { tryStart(listener, this::doStart); @@ -94,58 +111,56 @@ public void stop(Listener listener) { tryStop(listener, this::doStop); } - protected abstract void doStart(Listener listener) throws Throwable; - - protected abstract void doStop(Listener listener) throws Throwable; + protected void doStart(Listener listener) throws Throwable { + listener.onSuccess(); + } - protected interface Function { - void apply(Listener l) throws Throwable; + protected void doStop(Listener listener) throws Throwable { + listener.onSuccess(); } /** - * 防止Listener被重复执行 + * 控制当服务已经启动后,重复调用start方法,是否抛出服务已经启动异常 + * 默认是true * - * @param l - * @return + * @return true:抛出异常 */ - public FutureListener wrap(Listener l) { - if (l == null) return new FutureListener(); - if (l instanceof FutureListener) return (FutureListener) l; - return new FutureListener(l); + protected boolean throwIfStarted() { + return true; } - protected class FutureListener extends FutureTask implements Listener { - private final Listener l;// 防止Listener被重复执行 - - public FutureListener() { - super(BaseService.this::isRunning); - this.l = null; - } - - public FutureListener(Listener l) { - super(BaseService.this::isRunning); - this.l = l; - } + /** + * 控制当服务已经停止后,重复调用stop方法,是否抛出服务已经停止异常 + * 默认是true + * + * @return true:抛出异常 + */ + protected boolean throwIfStopped() { + return true; + } - @Override - public void onSuccess(Object... args) { - if (isDone()) return;// 防止Listener被重复执行 - set(started.get()); - if (l != null) l.onSuccess(args); - } + /** + * 服务启动停止,超时时间, 默认是10s + * + * @return 超时时间 + */ + protected int timeoutMillis() { + return 1000 * 10; + } - @Override - public void onFailure(Throwable cause) { - if (isDone()) return;// 防止Listener被重复执行 - set(started.get()); - setException(cause); - if (l != null) l.onFailure(cause); - throw new ServiceException(cause); - } + protected interface FunctionEx { + void apply(Listener l) throws Throwable; + } - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - throw new UnsupportedOperationException(); - } + /** + * 防止Listener被重复执行 + * + * @param l listener + * @return FutureListener + */ + public FutureListener wrap(Listener l) { + if (l == null) return new FutureListener(started); + if (l instanceof FutureListener) return (FutureListener) l; + return new FutureListener(l, started); } } diff --git a/mpush-api/src/main/java/com/mpush/api/service/FutureListener.java b/mpush-api/src/main/java/com/mpush/api/service/FutureListener.java new file mode 100644 index 00000000..4b07d32c --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/service/FutureListener.java @@ -0,0 +1,59 @@ +package com.mpush.api.service; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class FutureListener extends CompletableFuture implements Listener { + private final Listener listener; + private final AtomicBoolean started; + + public FutureListener(AtomicBoolean started) { + this.started = started; + this.listener = null; + } + + public FutureListener(Listener listener, AtomicBoolean started) { + this.listener = listener; + this.started = started; + } + + @Override + public void onSuccess(Object... args) { + if (isDone()) return;// 防止Listener被重复执行 + complete(started.get()); + if (listener != null) listener.onSuccess(args); + } + + @Override + public void onFailure(Throwable cause) { + if (isDone()) return;// 防止Listener被重复执行 + completeExceptionally(cause); + if (listener != null) listener.onFailure(cause); + throw cause instanceof ServiceException + ? (ServiceException) cause + : new ServiceException(cause); + } + + /** + * 防止服务长时间卡在某个地方,增加超时监控 + * + * @param service 服务 + */ + public void monitor(BaseService service) { + if (isDone()) return;// 防止Listener被重复执行 + runAsync(() -> { + try { + this.get(service.timeoutMillis(), TimeUnit.MILLISECONDS); + } catch (Exception e) { + this.onFailure(new ServiceException(String.format("service %s monitor timeout", service.getClass().getSimpleName()))); + } + }); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + throw new UnsupportedOperationException(); + } + +} \ No newline at end of file diff --git a/mpush-api/src/main/java/com/mpush/api/service/Service.java b/mpush-api/src/main/java/com/mpush/api/service/Service.java index c158751b..6a7bc4bc 100644 --- a/mpush-api/src/main/java/com/mpush/api/service/Service.java +++ b/mpush-api/src/main/java/com/mpush/api/service/Service.java @@ -19,7 +19,7 @@ package com.mpush.api.service; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; /** * Created by yxx on 2016/5/17. @@ -32,9 +32,13 @@ public interface Service { void stop(Listener listener); - Future start(); + CompletableFuture start(); - Future stop(); + CompletableFuture stop(); + + boolean syncStart(); + + boolean syncStop(); void init(); diff --git a/mpush-api/src/main/java/com/mpush/api/spi/Factory.java b/mpush-api/src/main/java/com/mpush/api/spi/Factory.java index 3e99ce53..a90e3149 100644 --- a/mpush-api/src/main/java/com/mpush/api/spi/Factory.java +++ b/mpush-api/src/main/java/com/mpush/api/spi/Factory.java @@ -19,11 +19,13 @@ package com.mpush.api.spi; +import java.util.function.Supplier; + /** * Created by yxx on 2016/5/18. * * @author ohun@live.cn */ -public interface Factory { - T get(); +@FunctionalInterface +public interface Factory extends Supplier { } diff --git a/mpush-api/src/main/java/com/mpush/api/spi/Plugin.java b/mpush-api/src/main/java/com/mpush/api/spi/Plugin.java new file mode 100644 index 00000000..f674b1cf --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/Plugin.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2015-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi; + +import com.mpush.api.MPushContext; + +/** + * Created by ohun on 2017/6/21. + * + * @author ohun@live.cn (夜色) + */ +public interface Plugin { + + default void init(MPushContext context) { + + } + + default void destroy() { + + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/Spi.java b/mpush-api/src/main/java/com/mpush/api/spi/Spi.java new file mode 100644 index 00000000..0758d6b6 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/Spi.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi; + +import java.lang.annotation.*; + +/** + * Created by ohun on 16/10/14. + * + * @author ohun@live.cn (夜色) + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Spi { + + /** + * SPI name + * + * @return name + */ + String value() default ""; + + /** + * 排序顺序 + * + * @return sortNo + */ + int order() default 0; + +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/SpiLoader.java b/mpush-api/src/main/java/com/mpush/api/spi/SpiLoader.java index 59df059f..16b301a2 100644 --- a/mpush-api/src/main/java/com/mpush/api/spi/SpiLoader.java +++ b/mpush-api/src/main/java/com/mpush/api/spi/SpiLoader.java @@ -19,18 +19,21 @@ package com.mpush.api.spi; -import java.util.Iterator; -import java.util.Map; -import java.util.ServiceLoader; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public final class SpiLoader { private static final Map CACHE = new ConcurrentHashMap<>(); + public static void clear() { + CACHE.clear(); + } + public static T load(Class clazz) { return load(clazz, null); } + @SuppressWarnings("unchecked") public static T load(Class clazz, String name) { String key = clazz.getName(); Object o = CACHE.get(key); @@ -46,7 +49,7 @@ public static T load(Class clazz, String name) { return load0(clazz, name); } - public static T load0(Class clazz, String name) { + public static T load0(Class clazz, String name) { ServiceLoader factories = ServiceLoader.load(clazz); T t = filterByName(factories, name); @@ -65,9 +68,20 @@ public static T load0(Class clazz, String name) { private static T filterByName(ServiceLoader factories, String name) { Iterator it = factories.iterator(); if (name == null) { - if (it.hasNext()) { - return it.next(); + List list = new ArrayList(2); + while (it.hasNext()) { + list.add(it.next()); + } + if (list.size() > 1) { + list.sort((o1, o2) -> { + Spi spi1 = o1.getClass().getAnnotation(Spi.class); + Spi spi2 = o2.getClass().getAnnotation(Spi.class); + int order1 = spi1 == null ? 0 : spi1.order(); + int order2 = spi2 == null ? 0 : spi2.order(); + return order1 - order2; + }); } + if (list.size() > 0) return list.get(0); } else { while (it.hasNext()) { T t = it.next(); diff --git a/mpush-api/src/main/java/com/mpush/api/spi/client/PusherFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/client/PusherFactory.java index 6abcd77c..cf8ab3b1 100644 --- a/mpush-api/src/main/java/com/mpush/api/spi/client/PusherFactory.java +++ b/mpush-api/src/main/java/com/mpush/api/spi/client/PusherFactory.java @@ -21,6 +21,7 @@ import com.mpush.api.push.PushSender; import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; /** * Created by yxx on 2016/5/18. @@ -28,5 +29,7 @@ * @author ohun@live.cn */ public interface PusherFactory extends Factory { - + static PushSender create() { + return SpiLoader.load(PusherFactory.class).get(); + } } diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/CacheManager.java b/mpush-api/src/main/java/com/mpush/api/spi/common/CacheManager.java new file mode 100644 index 00000000..b1a26d06 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/CacheManager.java @@ -0,0 +1,69 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +import java.util.List; +import java.util.Map; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface CacheManager { + + void init(); + + void destroy(); + + void del(String key); + + long hincrBy(String key, String field, long value); + + void set(String key, String value); + + void set(String key, String value, int expireTime); + + void set(String key, Object value, int expireTime); + + T get(String key, Class tClass); + + void hset(String key, String field, String value); + + void hset(String key, String field, Object value); + + T hget(String key, String field, Class tClass); + + void hdel(String key, String field); + + Map hgetAll(String key, Class clazz); + + void zAdd(String key, String value); + + Long zCard(String key); + + void zRem(String key, String value); + + List zrange(String key, int start, int end, Class clazz); + + void lpush(String key, String... value); + + List lrange(String key, int start, int end, Class clazz); +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/CacheManagerFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/common/CacheManagerFactory.java new file mode 100644 index 00000000..6d268d36 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/CacheManagerFactory.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface CacheManagerFactory extends Factory { + static CacheManager create() { + return SpiLoader.load(CacheManagerFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/ThreadPoolFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/common/ExecutorFactory.java similarity index 71% rename from mpush-api/src/main/java/com/mpush/api/spi/common/ThreadPoolFactory.java rename to mpush-api/src/main/java/com/mpush/api/spi/common/ExecutorFactory.java index 84d394cb..9e6a34f2 100644 --- a/mpush-api/src/main/java/com/mpush/api/spi/common/ThreadPoolFactory.java +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/ExecutorFactory.java @@ -19,6 +19,8 @@ package com.mpush.api.spi.common; +import com.mpush.api.spi.SpiLoader; + import java.util.concurrent.Executor; /** @@ -26,14 +28,16 @@ * * @author ohun@live.cn */ -public interface ThreadPoolFactory { - String SERVER_BOSS = "sb"; - String SERVER_WORK = "sw"; - String HTTP_CLIENT_WORK = "hcw"; - String PUSH_CALLBACK = "pc"; - String EVENT_BUS = "eb"; - String MQ = "r"; - String BIZ = "b"; +public interface ExecutorFactory { + String PUSH_CLIENT = "push-client"; + String PUSH_TASK = "push-task"; + String ACK_TIMER = "ack-timer"; + String EVENT_BUS = "event-bus"; + String MQ = "mq"; Executor get(String name); + + static ExecutorFactory create() { + return SpiLoader.load(ExecutorFactory.class); + } } diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/Json.java b/mpush-api/src/main/java/com/mpush/api/spi/common/Json.java new file mode 100644 index 00000000..a4c41a08 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/Json.java @@ -0,0 +1,33 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +/** + * Created by ohun on 2016/12/17. + * + * @author ohun@live.cn (夜色) + */ +public interface Json { + Json JSON = JsonFactory.create(); + + T fromJson(String json, Class clazz); + + String toJson(Object json); +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/JsonFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/common/JsonFactory.java new file mode 100644 index 00000000..5384fe4f --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/JsonFactory.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 2016/12/17. + * + * @author ohun@live.cn (夜色) + */ +public interface JsonFactory extends Factory { + + static Json create() { + return SpiLoader.load(JsonFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/MQClient.java b/mpush-api/src/main/java/com/mpush/api/spi/common/MQClient.java new file mode 100644 index 00000000..6dcc4563 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/MQClient.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +import com.mpush.api.spi.Plugin; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface MQClient extends Plugin { + + void subscribe(String topic, MQMessageReceiver receiver); + + void publish(String topic, Object message); +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/MQClientFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/common/MQClientFactory.java new file mode 100644 index 00000000..50deef2f --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/MQClientFactory.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface MQClientFactory extends Factory { + + static MQClient create() { + return SpiLoader.load(MQClientFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/MQMessageReceiver.java b/mpush-api/src/main/java/com/mpush/api/spi/common/MQMessageReceiver.java new file mode 100644 index 00000000..d13081f9 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/MQMessageReceiver.java @@ -0,0 +1,29 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface MQMessageReceiver { + void receive(String topic, Object message); +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/ServiceDiscoveryFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/common/ServiceDiscoveryFactory.java new file mode 100644 index 00000000..96567824 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/ServiceDiscoveryFactory.java @@ -0,0 +1,36 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.api.srd.ServiceRegistry; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface ServiceDiscoveryFactory extends Factory { + static ServiceDiscovery create() { + return SpiLoader.load(ServiceDiscoveryFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/common/ServiceRegistryFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/common/ServiceRegistryFactory.java new file mode 100644 index 00000000..e1617a6d --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/common/ServiceRegistryFactory.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.common; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; +import com.mpush.api.srd.ServiceRegistry; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface ServiceRegistryFactory extends Factory { + static ServiceRegistry create() { + return SpiLoader.load(ServiceRegistryFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/core/CipherFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/core/CipherFactory.java index b3a486b9..c151eaa7 100644 --- a/mpush-api/src/main/java/com/mpush/api/spi/core/CipherFactory.java +++ b/mpush-api/src/main/java/com/mpush/api/spi/core/CipherFactory.java @@ -21,6 +21,7 @@ import com.mpush.api.connection.Cipher; import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; /** * Created by yxx on 2016/5/19. @@ -28,5 +29,7 @@ * @author ohun@live.cn */ public interface CipherFactory extends Factory { - Cipher get(); + static Cipher create() { + return SpiLoader.load(CipherFactory.class).get(); + } } diff --git a/mpush-api/src/main/java/com/mpush/api/spi/core/ServerEventListenerFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/core/ServerEventListenerFactory.java new file mode 100644 index 00000000..7d70ad73 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/core/ServerEventListenerFactory.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.core; + +import com.mpush.api.common.ServerEventListener; +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 16/10/19. + * + * @author ohun@live.cn (夜色) + */ +public interface ServerEventListenerFactory extends Factory { + static ServerEventListener create() { + return SpiLoader.load(ServerEventListenerFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/handler/BindValidator.java b/mpush-api/src/main/java/com/mpush/api/spi/handler/BindValidator.java new file mode 100644 index 00000000..63e21727 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/handler/BindValidator.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.handler; + +import com.mpush.api.spi.Plugin; + +/** + * Created by ohun on 16/10/19. + * + * @author ohun@live.cn (夜色) + */ +public interface BindValidator extends Plugin { + boolean validate(String userId, String data); +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/handler/BindValidatorFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/handler/BindValidatorFactory.java new file mode 100644 index 00000000..4d9f3c34 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/handler/BindValidatorFactory.java @@ -0,0 +1,34 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.handler; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 16/10/19. + * + * @author ohun@live.cn (夜色) + */ +public interface BindValidatorFactory extends Factory { + static BindValidator create() { + return SpiLoader.load(BindValidatorFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/handler/PushHandlerFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/handler/PushHandlerFactory.java new file mode 100644 index 00000000..b03f686b --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/handler/PushHandlerFactory.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.handler; + +import com.mpush.api.message.MessageHandler; +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 16/10/14. + * + * @author ohun@live.cn (夜色) + */ +public interface PushHandlerFactory extends Factory { + static MessageHandler create() { + return SpiLoader.load(PushHandlerFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/net/DnsMapping.java b/mpush-api/src/main/java/com/mpush/api/spi/net/DnsMapping.java index 0270d4d3..969baca2 100644 --- a/mpush-api/src/main/java/com/mpush/api/spi/net/DnsMapping.java +++ b/mpush-api/src/main/java/com/mpush/api/spi/net/DnsMapping.java @@ -24,8 +24,11 @@ import java.util.Objects; public class DnsMapping { - private String ip; - private int port; + protected String ip; + protected int port; + + public DnsMapping() { + } public DnsMapping(String ip, int port) { this.ip = ip; @@ -40,6 +43,16 @@ public int getPort() { return port; } + public DnsMapping setIp(String ip) { + this.ip = ip; + return this; + } + + public DnsMapping setPort(int port) { + this.port = port; + return this; + } + public static DnsMapping parse(String addr) { String[] host_port = Objects.requireNonNull(addr, "dns mapping can not be null") .split(":"); @@ -62,6 +75,24 @@ public String translate(URL uri) { return sb.toString(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DnsMapping that = (DnsMapping) o; + + if (port != that.port) return false; + return ip.equals(that.ip); + } + + @Override + public int hashCode() { + int result = ip.hashCode(); + result = 31 * result + port; + return result; + } + @Override public String toString() { return ip + ":" + port; diff --git a/mpush-api/src/main/java/com/mpush/api/spi/push/IPushMessage.java b/mpush-api/src/main/java/com/mpush/api/spi/push/IPushMessage.java new file mode 100644 index 00000000..ca92da9f --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/push/IPushMessage.java @@ -0,0 +1,57 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.push; + +import com.mpush.api.common.Condition; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +public interface IPushMessage { + + boolean isBroadcast(); + + String getUserId(); + + int getClientType(); + + byte[] getContent(); + + boolean isNeedAck(); + + byte getFlags(); + + int getTimeoutMills(); + + default String getTaskId() { + return null; + } + + default Condition getCondition() { + return null; + } + + default void finalized() { + + } + +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/push/MessagePusher.java b/mpush-api/src/main/java/com/mpush/api/spi/push/MessagePusher.java new file mode 100644 index 00000000..8706a1e7 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/push/MessagePusher.java @@ -0,0 +1,29 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.push; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +public interface MessagePusher { + void push(IPushMessage message); +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/push/MessagePusherFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/push/MessagePusherFactory.java new file mode 100644 index 00000000..9a5181de --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/push/MessagePusherFactory.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.push; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +public interface MessagePusherFactory extends Factory { + + static MessagePusher create() { + return SpiLoader.load(MessagePusherFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/push/PushListener.java b/mpush-api/src/main/java/com/mpush/api/spi/push/PushListener.java new file mode 100644 index 00000000..22a7f368 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/push/PushListener.java @@ -0,0 +1,63 @@ +package com.mpush.api.spi.push; + +import com.mpush.api.spi.Plugin; + +public interface PushListener extends Plugin { + + /** + * 消息下发成功后回调 + * 如果消息需要ACK则该方法不会被调用 + * + * @param message 要下发的消息 + * @param timePoints 消息流转时间节点 + */ + void onSuccess(T message, Object[] timePoints); + + /** + * 收到客户端ACK后回调 + * + * @param message 要下发的消息 + * @param timePoints 消息流转时间节点 + */ + void onAckSuccess(T message, Object[] timePoints); + + /** + * 广播消息推送全部结束后回调 + * + * @param message 要下发的消息 + * @param timePoints 消息流转时间节点 + */ + void onBroadcastComplete(T message, Object[] timePoints); + + /** + * 消息下发失败后回调 + * + * @param message 要下发的消息 + * @param timePoints 消息流转时间节点 + */ + void onFailure(T message, Object[] timePoints); + + /** + * 推送消息发现用户不在线时回调 + * + * @param message 要下发的消息 + * @param timePoints 消息流转时间节点 + */ + void onOffline(T message, Object[] timePoints); + + /** + * 推送消息发现用户不在当前机器时回调 + * + * @param message 要下发的消息 + * @param timePoints 消息流转时间节点 + */ + void onRedirect(T message, Object[] timePoints); + + /** + * 发送消息超时或等待客户端ACK超时时回调 + * + * @param message 要下发的消息 + * @param timePoints 消息流转时间节点 + */ + void onTimeout(T message, Object[] timePoints); +} \ No newline at end of file diff --git a/mpush-api/src/main/java/com/mpush/api/spi/push/PushListenerFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/push/PushListenerFactory.java new file mode 100644 index 00000000..7d3fbfa7 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/push/PushListenerFactory.java @@ -0,0 +1,36 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.push; + +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +public interface PushListenerFactory extends Factory> { + + @SuppressWarnings("unchecked") + static PushListener create() { + return (PushListener) SpiLoader.load(PushListenerFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/spi/router/ClientClassifierFactory.java b/mpush-api/src/main/java/com/mpush/api/spi/router/ClientClassifierFactory.java new file mode 100644 index 00000000..a4a54a50 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/spi/router/ClientClassifierFactory.java @@ -0,0 +1,36 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.spi.router; + +import com.mpush.api.router.ClientClassifier; +import com.mpush.api.spi.Factory; +import com.mpush.api.spi.SpiLoader; + +/** + * Created by ohun on 16/10/26. + * + * @author ohun@live.cn (夜色) + */ +public interface ClientClassifierFactory extends Factory { + + static ClientClassifier create() { + return SpiLoader.load(ClientClassifierFactory.class).get(); + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/srd/CommonServiceNode.java b/mpush-api/src/main/java/com/mpush/api/srd/CommonServiceNode.java new file mode 100644 index 00000000..61ce7866 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/srd/CommonServiceNode.java @@ -0,0 +1,126 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.srd; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public final class CommonServiceNode implements ServiceNode { + + private String host; + + private int port; + + private Map attrs; + + private transient String name; + + private transient String nodeId; + + private transient boolean persistent; + + public void setHost(String host) { + this.host = host; + } + + public void setPort(int port) { + this.port = port; + } + + public void setPersistent(boolean persistent) { + this.persistent = persistent; + } + + public void setServiceName(String name) { + this.name = name; + } + + public CommonServiceNode addAttr(String name, Object value) { + if (attrs == null) attrs = new HashMap<>(); + attrs.put(name, value); + return this; + } + + @SuppressWarnings("unchecked") + @Override + public T getAttr(String name) { + if (attrs == null || attrs.isEmpty()) { + return null; + } + return (T) attrs.get(name); + } + + @Override + public boolean isPersistent() { + return persistent; + } + + @Override + public String hostAndPort() { + return host + ":" + port; + } + + @Override + public String serviceName() { + return name; + } + + @Override + public String nodeId() { + if (nodeId == null) { + nodeId = UUID.randomUUID().toString(); + } + return nodeId; + } + + @Override + public String getHost() { + return host; + } + + @Override + public int getPort() { + return port; + } + + public Map getAttrs() { + return attrs; + } + + public void setAttrs(Map attrs) { + this.attrs = attrs; + } + + @Override + public String toString() { + return "{" + + "host='" + host + '\'' + + ", port=" + port + + ", attrs=" + attrs + + ", persistent=" + persistent + + '}'; + } +} diff --git a/mpush-api/src/main/java/com/mpush/api/srd/ServiceDiscovery.java b/mpush-api/src/main/java/com/mpush/api/srd/ServiceDiscovery.java new file mode 100644 index 00000000..09d404bb --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/srd/ServiceDiscovery.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.srd; + +import com.mpush.api.service.Service; + +import java.util.List; + +/** + * Created by ohun on 2016/12/19. + * + * @author ohun@live.cn (夜色) + */ +public interface ServiceDiscovery extends Service { + + List lookup(String path); + + void subscribe(String path, ServiceListener listener); + + void unsubscribe(String path, ServiceListener listener); +} diff --git a/mpush-test/src/test/java/com/mpush/test/redis/MPushUtilTest.java b/mpush-api/src/main/java/com/mpush/api/srd/ServiceEvent.java similarity index 66% rename from mpush-test/src/test/java/com/mpush/test/redis/MPushUtilTest.java rename to mpush-api/src/main/java/com/mpush/api/srd/ServiceEvent.java index 43a30342..2daefac9 100644 --- a/mpush-test/src/test/java/com/mpush/test/redis/MPushUtilTest.java +++ b/mpush-api/src/main/java/com/mpush/api/srd/ServiceEvent.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,19 +14,17 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.test.redis; +package com.mpush.api.srd; -import com.mpush.tools.Utils; -import org.junit.Test; - -public class MPushUtilTest { - - @Test - public void getIp() throws Exception { - System.out.println(Utils.getExtranetAddress()); - } +import com.mpush.api.event.Event; +/** + * Created by ohun on 2016/12/20. + * + * @author ohun@live.cn (夜色) + */ +public interface ServiceEvent extends Event { } diff --git a/mpush-zk/src/main/java/com/mpush/zk/cache/ZKNodeCache.java b/mpush-api/src/main/java/com/mpush/api/srd/ServiceListener.java similarity index 60% rename from mpush-zk/src/main/java/com/mpush/zk/cache/ZKNodeCache.java rename to mpush-api/src/main/java/com/mpush/api/srd/ServiceListener.java index c4a3023c..ce0a5988 100644 --- a/mpush-zk/src/main/java/com/mpush/zk/cache/ZKNodeCache.java +++ b/mpush-api/src/main/java/com/mpush/api/srd/ServiceListener.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,26 +14,22 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.zk.cache; +package com.mpush.api.srd; -import com.mpush.zk.node.ZKNode; - -import java.util.Collection; -import java.util.List; - -public interface ZKNodeCache { - - void addAll(List list); - - void put(String fullPath, T node); +/** + * Created by ohun on 2016/12/19. + * + * @author ohun@live.cn (夜色) + */ +public interface ServiceListener { - T remove(String fullPath); + void onServiceAdded(String path, ServiceNode node); - Collection values(); + void onServiceUpdated(String path, ServiceNode node); - void clear(); + void onServiceRemoved(String path, ServiceNode node); } diff --git a/mpush-api/src/main/java/com/mpush/api/srd/ServiceNames.java b/mpush-api/src/main/java/com/mpush/api/srd/ServiceNames.java new file mode 100644 index 00000000..fe244540 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/srd/ServiceNames.java @@ -0,0 +1,35 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.srd; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public interface ServiceNames { + String CONN_SERVER = "/cluster/cs"; + String WS_SERVER = "/cluster/ws"; + String GATEWAY_SERVER = "/cluster/gs"; + String DNS_MAPPING = "/dns/mapping"; + + String ATTR_PUBLIC_IP = "public_ip"; + +} diff --git a/mpush-api/src/main/java/com/mpush/api/srd/ServiceNode.java b/mpush-api/src/main/java/com/mpush/api/srd/ServiceNode.java new file mode 100644 index 00000000..c9889355 --- /dev/null +++ b/mpush-api/src/main/java/com/mpush/api/srd/ServiceNode.java @@ -0,0 +1,53 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.api.srd; + +/** + * Created by ohun on 2016/12/19. + * + * @author ohun@live.cn (夜色) + */ +public interface ServiceNode { + + String serviceName(); + + String nodeId(); + + String getHost(); + + int getPort(); + + default T getAttr(String name) { + return null; + } + + default boolean isPersistent() { + return false; + } + + default String hostAndPort() { + return getHost() + ":" + getPort(); + } + + default String nodePath() { + return serviceName() + '/' + nodeId(); + } + +} diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/RedisBoot.java b/mpush-api/src/main/java/com/mpush/api/srd/ServiceRegistry.java similarity index 68% rename from mpush-boot/src/main/java/com/mpush/bootstrap/job/RedisBoot.java rename to mpush-api/src/main/java/com/mpush/api/srd/ServiceRegistry.java index e98ca3ee..c1ac922f 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/RedisBoot.java +++ b/mpush-api/src/main/java/com/mpush/api/srd/ServiceRegistry.java @@ -14,23 +14,21 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.bootstrap.job; +package com.mpush.api.srd; -import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.api.service.Service; /** - * Created by yxx on 2016/5/14. + * Created by ohun on 2016/12/19. * - * @author ohun@live.cn + * @author ohun@live.cn (夜色) */ -public class RedisBoot extends BootJob { +public interface ServiceRegistry extends Service { - @Override - public void run() { - RedisManager.I.init(); - next(); - } + void register(ServiceNode node); + + void deregister(ServiceNode node); } diff --git a/mpush-boot/assembly.xml b/mpush-boot/assembly.xml index 24fc2cd4..c80612dd 100644 --- a/mpush-boot/assembly.xml +++ b/mpush-boot/assembly.xml @@ -1,7 +1,7 @@ - release-${mpush.version} - mpush-${mpush.version} + release-${project.version} + mpush-${project.version} true tar.gz @@ -35,6 +35,7 @@ conf mpush.conf + logback.xml diff --git a/mpush-boot/pom.xml b/mpush-boot/pom.xml index cb0b9a07..a1dbf97d 100644 --- a/mpush-boot/pom.xml +++ b/mpush-boot/pom.xml @@ -1,28 +1,32 @@ + + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} + mpush-boot - ${mpush-boot-version} - mpush-boot jar + mpush-boot + MPUSH消息推送系统启动模块 + https://github.com/mpusher/mpush - ${mpush.groupId} + ${project.groupId} mpush-core - ${mpush.groupId} - mpush-monitor + ${project.groupId} + mpush-cache - ${mpush.groupId} + ${project.groupId} mpush-zk @@ -41,48 +45,56 @@ true - - - maven-jar-plugin - - - - false - - - - true - - ../lib/ - - com.mpush.bootstrap.Main - - - - - - package - - - - - maven-assembly-plugin - 2.6 - - mpush - - assembly.xml - - - - - package - - single - - - - - + + + zip + + + + maven-jar-plugin + 3.0.2 + + + + false + + + + true + + ../lib/ + + com.mpush.bootstrap.Main + + + + + + package + + + + + maven-assembly-plugin + 2.6 + + mpush + + assembly.xml + + + + + package + + single + + + + + + + + diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/Main.java b/mpush-boot/src/main/java/com/mpush/bootstrap/Main.java index 2a0550ff..b85c0a20 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/Main.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/Main.java @@ -23,20 +23,41 @@ public class Main { + /** + * 源码启动请不要直接运行此方法,否则不能正确加载配置文件 + * + * @param args 启动参数 + */ public static void main(String[] args) { Logs.init(); - Logs.Console.error("launch mpush server..."); + Logs.Console.info("launch mpush server..."); ServerLauncher launcher = new ServerLauncher(); + launcher.init(); launcher.start(); addHook(launcher); } - private static void addHook(final ServerLauncher launcher) { - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - launcher.stop(); - Logs.Console.error("jvm exit, all server stopped..."); - } - }); + /** + * 注意点 + * 1.不要ShutdownHook Thread 里调用System.exit()方法,否则会造成死循环。 + * 2.如果有非守护线程,只有所有的非守护线程都结束了才会执行hook + * 3.Thread默认都是非守护线程,创建的时候要注意 + * 4.注意线程抛出的异常,如果没有被捕获都会跑到Thread.dispatchUncaughtException + * + * @param launcher + */ + private static void addHook(ServerLauncher launcher) { + Runtime.getRuntime().addShutdownHook( + new Thread(() -> { + + try { + launcher.stop(); + } catch (Exception e) { + Logs.Console.error("mpush server stop ex", e); + } + Logs.Console.info("jvm exit, all service stopped."); + + }, "mpush-shutdown-hook-thread") + ); } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/ServerLauncher.java b/mpush-boot/src/main/java/com/mpush/bootstrap/ServerLauncher.java index 18edd96b..4d8476cf 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/ServerLauncher.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/ServerLauncher.java @@ -20,58 +20,85 @@ package com.mpush.bootstrap; -import com.mpush.api.service.Server; +import com.mpush.api.common.ServerEventListener; +import com.mpush.api.spi.core.ServerEventListenerFactory; import com.mpush.bootstrap.job.*; -import com.mpush.core.server.AdminServer; -import com.mpush.core.server.ConnectionServer; -import com.mpush.core.server.GatewayServer; -import com.mpush.monitor.service.MonitorService; +import com.mpush.core.MPushServer; import com.mpush.tools.config.CC; -import com.mpush.zk.ZKClient; -import com.mpush.zk.node.ZKServerNode; + +import static com.mpush.tools.config.CC.mp.net.*; /** * Created by yxx on 2016/5/14. * * @author ohun@live.cn */ -public class ServerLauncher { - private final ZKServerNode csNode = ZKServerNode.csNode(); +public final class ServerLauncher { - private final ZKServerNode gsNode = ZKServerNode.gsNode(); + private MPushServer mPushServer; + private BootChain chain; + private ServerEventListener serverEventListener; - private final ConnectionServer connectServer = new ConnectionServer(csNode.getPort()); + public void init() { + if (mPushServer == null) { + mPushServer = new MPushServer(); + } - private final GatewayServer gatewayServer = new GatewayServer(gsNode.getPort()); + if (chain == null) { + chain = BootChain.chain(); + } - private final AdminServer adminServer = new AdminServer(CC.mp.net.admin_server_port, connectServer, gatewayServer); + if (serverEventListener == null) { + serverEventListener = ServerEventListenerFactory.create(); + } + serverEventListener.init(mPushServer); - public void start() { - BootChain chain = BootChain.chain(); chain.boot() - .setNext(new ZKBoot())//1.启动ZK节点数据变化监听 - .setNext(new RedisBoot())//2.注册redis sever 到ZK - .setNext(new ServerBoot(connectServer, csNode))//3.启动长连接服务 - .setNext(new ServerBoot(gatewayServer, gsNode))//4.启动网关服务 - .setNext(new ServerBoot(adminServer, null))//5.启动控制台服务 - .setNext(new HttpProxyBoot())//6.启动http代理服务,解析dns - .setNext(new MonitorBoot())//7.启动监控 - .setNext(new LastBoot());//8.启动结束 - chain.run(); + .setNext(new CacheManagerBoot())//1.初始化缓存模块 + .setNext(new ServiceRegistryBoot())//2.启动服务注册与发现模块 + .setNext(new ServiceDiscoveryBoot())//2.启动服务注册与发现模块 + .setNext(new ServerBoot(mPushServer.getConnectionServer(), mPushServer.getConnServerNode()))//3.启动接入服务 + .setNext(() -> new ServerBoot(mPushServer.getWebsocketServer(), mPushServer.getWebsocketServerNode()), wsEnabled())//4.启动websocket接入服务 + .setNext(() -> new ServerBoot(mPushServer.getUdpGatewayServer(), mPushServer.getGatewayServerNode()), udpGateway())//5.启动udp网关服务 + .setNext(() -> new ServerBoot(mPushServer.getGatewayServer(), mPushServer.getGatewayServerNode()), tcpGateway())//6.启动tcp网关服务 + .setNext(new ServerBoot(mPushServer.getAdminServer(), null))//7.启动控制台服务 + .setNext(new RouterCenterBoot(mPushServer))//8.启动路由中心组件 + .setNext(new PushCenterBoot(mPushServer))//9.启动推送中心组件 + .setNext(() -> new HttpProxyBoot(mPushServer), CC.mp.http.proxy_enabled)//10.启动http代理服务,dns解析服务 + .setNext(new MonitorBoot(mPushServer))//11.启动监控服务 + .end(); + } + + public void start() { + chain.start(); } public void stop() { - stopServer(connectServer); - stopServer(gatewayServer); - stopServer(adminServer); - ZKClient.I.stop(); - MonitorService.I.stop(); + chain.stop(); } - private void stopServer(Server server) { - if (server != null) { - server.stop(null); - } + public void setMPushServer(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } + + public void setChain(BootChain chain) { + this.chain = chain; + } + + public MPushServer getMPushServer() { + return mPushServer; + } + + public BootChain getChain() { + return chain; + } + + public ServerEventListener getServerEventListener() { + return serverEventListener; + } + + public void setServerEventListener(ServerEventListener serverEventListener) { + this.serverEventListener = serverEventListener; } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootChain.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootChain.java index 32544f85..f9b0fb45 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootChain.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootChain.java @@ -19,35 +19,92 @@ package com.mpush.bootstrap.job; +import com.mpush.api.event.ServerShutdownEvent; +import com.mpush.api.event.ServerStartupEvent; +import com.mpush.api.spi.core.ServerEventListenerFactory; +import com.mpush.tools.event.EventBus; import com.mpush.tools.log.Logs; +import java.util.function.Supplier; + /** * Created by yxx on 2016/5/15. * * @author ohun@live.cn */ -public class BootChain { - private BootJob first = first(); +public final class BootChain { + private final BootJob boot = new BootJob() { + { + ServerEventListenerFactory.create();// 初始化服务监听 + } + + @Override + protected void start() { + Logs.Console.info("bootstrap chain starting..."); + startNext(); + } + + @Override + protected void stop() { + stopNext(); + Logs.Console.info("bootstrap chain stopped."); + Logs.Console.info("==================================================================="); + Logs.Console.info("====================MPUSH SERVER STOPPED SUCCESS==================="); + Logs.Console.info("==================================================================="); + } + }; + + private BootJob last = boot; - public void run() { - first.run(); + public void start() { + boot.start(); + } + + public void stop() { + boot.stop(); } public static BootChain chain() { return new BootChain(); } - private BootJob first() { - return new BootJob() { + public BootChain boot() { + return this; + } + + public void end() { + setNext(new BootJob() { @Override - public void run() { - Logs.Console.error("begin run bootstrap chain..."); - next(); + protected void start() { + EventBus.post(new ServerStartupEvent()); + Logs.Console.info("bootstrap chain started."); + Logs.Console.info("==================================================================="); + Logs.Console.info("====================MPUSH SERVER START SUCCESS====================="); + Logs.Console.info("==================================================================="); } - }; + + @Override + protected void stop() { + Logs.Console.info("bootstrap chain stopping..."); + EventBus.post(new ServerShutdownEvent()); + } + + @Override + protected String getName() { + return "LastBoot"; + } + }); + } + + public BootChain setNext(BootJob bootJob) { + this.last = last.setNext(bootJob); + return this; } - public BootJob boot() { - return first; + public BootChain setNext(Supplier next, boolean enabled) { + if (enabled) { + return setNext(next.get()); + } + return this; } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootJob.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootJob.java index 32bb5b36..100cc9fc 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootJob.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/BootJob.java @@ -21,20 +21,31 @@ import com.mpush.tools.log.Logs; +import java.util.function.Supplier; + /** * Created by yxx on 2016/5/14. * * @author ohun@live.cn */ public abstract class BootJob { - private BootJob next; + protected BootJob next; + + protected abstract void start(); - abstract void run(); + protected abstract void stop(); - public void next() { + public void startNext() { if (next != null) { - Logs.Console.error("run next bootstrap job [" + next.getClass().getSimpleName() + "]"); - next.run(); + Logs.Console.info("start bootstrap job [{}]", getNextName()); + next.start(); + } + } + + public void stopNext() { + if (next != null) { + next.stop(); + Logs.Console.info("stopped bootstrap job [{}]", getNextName()); } } @@ -42,4 +53,19 @@ public BootJob setNext(BootJob next) { this.next = next; return next; } + + public BootJob setNext(Supplier next, boolean enabled) { + if (enabled) { + return setNext(next.get()); + } + return this; + } + + protected String getNextName() { + return next.getName(); + } + + protected String getName() { + return this.getClass().getSimpleName(); + } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/LastBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/CacheManagerBoot.java similarity index 62% rename from mpush-boot/src/main/java/com/mpush/bootstrap/job/LastBoot.java rename to mpush-boot/src/main/java/com/mpush/bootstrap/job/CacheManagerBoot.java index bc194877..e84c9a9f 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/LastBoot.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/CacheManagerBoot.java @@ -19,21 +19,26 @@ package com.mpush.bootstrap.job; +import com.mpush.api.spi.common.CacheManagerFactory; import com.mpush.common.user.UserManager; -import com.mpush.tools.log.Logs; +import com.mpush.core.MPushServer; /** * Created by yxx on 2016/5/14. * * @author ohun@live.cn */ -public class LastBoot extends BootJob { +public final class CacheManagerBoot extends BootJob { + + @Override + protected void start() { + CacheManagerFactory.create().init(); + startNext(); + } + @Override - public void run() { - UserManager.I.clearUserOnlineData(); - Logs.Console.error("end run bootstrap chain..."); - Logs.Console.error("==================================================================="); - Logs.Console.error("====================MPUSH SERVER START SUCCESS====================="); - Logs.Console.error("==================================================================="); + protected void stop() { + stopNext(); + CacheManagerFactory.create().destroy(); } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/HttpProxyBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/HttpProxyBoot.java index c1a01431..f1732b90 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/HttpProxyBoot.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/HttpProxyBoot.java @@ -19,22 +19,34 @@ package com.mpush.bootstrap.job; -import com.mpush.api.spi.SpiLoader; import com.mpush.api.spi.net.DnsMappingManager; -import com.mpush.common.net.HttpProxyDnsMappingManager; -import com.mpush.tools.config.CC; +import com.mpush.core.MPushServer; /** * Created by yxx on 2016/5/15. * * @author ohun@live.cn */ -public class HttpProxyBoot extends BootJob { +public final class HttpProxyBoot extends BootJob { + + private final MPushServer mPushServer; + + public HttpProxyBoot(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } + + @Override + protected void start() { + mPushServer.getHttpClient().syncStart(); + DnsMappingManager.create().start(); + + startNext(); + } + @Override - void run() { - if (CC.mp.http.proxy_enabled) { - SpiLoader.load(DnsMappingManager.class, CC.mp.spi.dns_mapping_manager).start(); - } - next(); + protected void stop() { + stopNext(); + mPushServer.getHttpClient().syncStop(); + DnsMappingManager.create().stop(); } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/MonitorBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/MonitorBoot.java index d0b59ae7..cc430e16 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/MonitorBoot.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/MonitorBoot.java @@ -19,17 +19,30 @@ package com.mpush.bootstrap.job; -import com.mpush.monitor.service.MonitorService; +import com.mpush.core.MPushServer; /** * Created by yxx on 2016/5/15. * * @author ohun@live.cn */ -public class MonitorBoot extends BootJob { +public final class MonitorBoot extends BootJob { + + private final MPushServer mPushServer; + + public MonitorBoot(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } + + @Override + protected void start() { + mPushServer.getMonitor().start(); + startNext(); + } + @Override - void run() { - MonitorService.I.start(); - next(); + protected void stop() { + stopNext(); + mPushServer.getMonitor().stop(); } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/PushCenterBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/PushCenterBoot.java new file mode 100644 index 00000000..d4f0b76f --- /dev/null +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/PushCenterBoot.java @@ -0,0 +1,47 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.bootstrap.job; + +import com.mpush.core.MPushServer; + +/** + * Created by ohun on 16/10/25. + * + * @author ohun@live.cn (夜色) + */ +public final class PushCenterBoot extends BootJob { + private final MPushServer mPushServer; + + public PushCenterBoot(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } + + @Override + protected void start() { + mPushServer.getPushCenter().start(); + startNext(); + } + + @Override + protected void stop() { + stopNext(); + mPushServer.getPushCenter().stop(); + } +} diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/RouterCenterBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/RouterCenterBoot.java new file mode 100644 index 00000000..968a411c --- /dev/null +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/RouterCenterBoot.java @@ -0,0 +1,47 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.bootstrap.job; + +import com.mpush.core.MPushServer; + +/** + * Created by ohun on 16/10/25. + * + * @author ohun@live.cn (夜色) + */ +public final class RouterCenterBoot extends BootJob { + private final MPushServer mPushServer; + + public RouterCenterBoot(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } + + @Override + protected void start() { + mPushServer.getRouterCenter().start(); + startNext(); + } + + @Override + protected void stop() { + stopNext(); + mPushServer.getRouterCenter().stop(); + } +} diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServerBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServerBoot.java index 86bde932..4edf1a07 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServerBoot.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServerBoot.java @@ -21,56 +21,59 @@ import com.mpush.api.service.Listener; import com.mpush.api.service.Server; -import com.mpush.tools.Jsons; +import com.mpush.api.spi.common.ServiceRegistryFactory; +import com.mpush.api.srd.ServiceNode; import com.mpush.tools.log.Logs; -import com.mpush.tools.thread.pool.ThreadPoolManager; -import com.mpush.zk.ZKClient; -import com.mpush.zk.node.ZKServerNode; /** * Created by yxx on 2016/5/14. * * @author ohun@live.cn */ -public class ServerBoot extends BootJob { +public final class ServerBoot extends BootJob { private final Server server; - private final ZKServerNode node; + private final ServiceNode node; - public ServerBoot(Server server, ZKServerNode node) { + public ServerBoot(Server server, ServiceNode node) { this.server = server; this.node = node; } @Override - public void run() { - final String serverName = server.getClass().getSimpleName(); - ThreadPoolManager.I.newThread(serverName, new Runnable() { + public void start() { + server.init(); + server.start(new Listener() { @Override - public void run() { - server.init(); - server.start(new Listener() { - @Override - public void onSuccess(Object... args) { - Logs.Console.error("start " + serverName + " success listen:" + args[0]); - if (node != null) { - registerServerToZk(node.getZkPath(), Jsons.toJson(node)); - } - next(); - } + public void onSuccess(Object... args) { + Logs.Console.info("start {} success on:{}", server.getClass().getSimpleName(), args[0]); + if (node != null) {//注册应用到zk + ServiceRegistryFactory.create().register(node); + Logs.RSD.info("register {} to srd success.", node); + } + startNext(); + } - @Override - public void onFailure(Throwable cause) { - Logs.Console.error("start " + serverName + " failure, jvm exit with code -1", cause); - System.exit(-1); - } - }); + @Override + public void onFailure(Throwable cause) { + Logs.Console.error("start {} failure, jvm exit with code -1", server.getClass().getSimpleName(), cause); + System.exit(-1); } - }).start(); + }); + } + + @Override + protected void stop() { + stopNext(); + if (node != null) { + ServiceRegistryFactory.create().deregister(node); + } + Logs.Console.info("try shutdown {}...", server.getClass().getSimpleName()); + server.stop().join(); + Logs.Console.info("{} shutdown success.", server.getClass().getSimpleName()); } - //step7 注册应用到zk - public void registerServerToZk(String path, String value) { - ZKClient.I.registerEphemeralSequential(path, value); - Logs.Console.error("register server node=" + value + " to zk name=" + path); + @Override + protected String getName() { + return super.getName() + '(' + server.getClass().getSimpleName() + ')'; } } diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServiceDiscoveryBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServiceDiscoveryBoot.java new file mode 100644 index 00000000..67c397bd --- /dev/null +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServiceDiscoveryBoot.java @@ -0,0 +1,47 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.bootstrap.job; + +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.api.spi.common.ServiceRegistryFactory; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.tools.log.Logs; + +/** + * Created by yxx on 2016/5/14. + * + * @author ohun@live.cn + */ +public final class ServiceDiscoveryBoot extends BootJob { + + @Override + protected void start() { + Logs.Console.info("init service discovery waiting for connected..."); + ServiceDiscoveryFactory.create().syncStart(); + startNext(); + } + + @Override + protected void stop() { + stopNext(); + ServiceDiscoveryFactory.create().syncStop(); + Logs.Console.info("service discovery closed..."); + } +} diff --git a/mpush-boot/src/main/java/com/mpush/bootstrap/job/ZKBoot.java b/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServiceRegistryBoot.java similarity index 61% rename from mpush-boot/src/main/java/com/mpush/bootstrap/job/ZKBoot.java rename to mpush-boot/src/main/java/com/mpush/bootstrap/job/ServiceRegistryBoot.java index e8c33d1d..ef335917 100644 --- a/mpush-boot/src/main/java/com/mpush/bootstrap/job/ZKBoot.java +++ b/mpush-boot/src/main/java/com/mpush/bootstrap/job/ServiceRegistryBoot.java @@ -19,29 +19,27 @@ package com.mpush.bootstrap.job; -import com.mpush.api.service.Listener; -import com.mpush.bootstrap.BootException; -import com.mpush.zk.ZKClient; +import com.mpush.api.spi.common.ServiceRegistryFactory; +import com.mpush.tools.log.Logs; /** * Created by yxx on 2016/5/14. * * @author ohun@live.cn */ -public class ZKBoot extends BootJob { +public final class ServiceRegistryBoot extends BootJob { @Override - public void run() { - ZKClient.I.start(new Listener() { - @Override - public void onSuccess(Object... args) { - next(); - } + protected void start() { + Logs.Console.info("init service registry waiting for connected..."); + ServiceRegistryFactory.create().syncStart(); + startNext(); + } - @Override - public void onFailure(Throwable cause) { - throw new BootException("init zk client failure", cause); - } - }); + @Override + protected void stop() { + stopNext(); + ServiceRegistryFactory.create().syncStop(); + Logs.Console.info("service registry closed..."); } } diff --git a/mpush-boot/src/main/resources/logback.xml b/mpush-boot/src/main/resources/logback.xml index 7b8ebb77..dcbc3f70 100644 --- a/mpush-boot/src/main/resources/logback.xml +++ b/mpush-boot/src/main/resources/logback.xml @@ -3,10 +3,7 @@ - - - @@ -116,12 +113,12 @@ - - - ${log.home}/redis-mpush.log + + + ${log.home}/cache-mpush.log true - ${log.home}/redis-mpush.log.%d{yyyy-MM-dd} + ${log.home}/cache-mpush.log.%d{yyyy-MM-dd} 5 @@ -144,12 +141,26 @@ - - - ${log.home}/zk-mpush.log + + + ${log.home}/srd-mpush.log true - ${log.home}/zk-mpush.log.%d{yyyy-MM-dd} + ${log.home}/srd-mpush.log.%d{yyyy-MM-dd} + + 10 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} - %msg%n + + + + + + ${log.home}/profile-mpush.log + true + + ${log.home}/profile-mpush.log.%d{yyyy-MM-dd} 10 @@ -160,13 +171,12 @@ System.out - UTF-8 DEBUG - + %d{HH:mm:ss.SSS} - %msg%n - + @@ -175,7 +185,8 @@ - + + @@ -199,17 +210,21 @@ - + + + + + - + - + - + - + diff --git a/mpush-boot/src/main/resources/mpush.conf b/mpush-boot/src/main/resources/mpush.conf index 6ee565ff..9debe252 100644 --- a/mpush-boot/src/main/resources/mpush.conf +++ b/mpush-boot/src/main/resources/mpush.conf @@ -1,4 +1,15 @@ -mp.log.level=${log.level} -mp.security.private-key="MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA=" -mp.security.public-key="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB" -mp.zk.namespace=mpush +mp.log-level=${log.level} +mp.core.min-heartbeat=${min.hb} +mp.security.private-key="${rsa.privateKey}" +mp.security.public-key="${rsa.publicKey}" +mp.zk.server-address="127.0.0.1:2181" +mp.redis { //redis 集群配置 + nodes:["127.0.0.1:6379"] //格式是ip:port + cluster-model:single //single, cluster +} +mp.net.local-ip="" //本地ip, 默认取第一个网卡的本地IP +mp.net.public-ip="" //外网ip, 默认取第一个网卡的外网IP +mp.net.ws-server-port=0 //websocket对外端口, 0表示禁用websocket +mp.net.gateway-server-net=tcp // 网关服务使用的网络 udp/tcp +mp.net.connect-server-port=3000 //接入服务的端口号 +mp.http.proxy-enabled=true //启用Http代理功能 diff --git a/mpush-cache/pom.xml b/mpush-cache/pom.xml index 0f1b4dae..e56028e2 100644 --- a/mpush-cache/pom.xml +++ b/mpush-cache/pom.xml @@ -2,21 +2,25 @@ + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} mpush-cache - ${mpush.version} + jar + mpush-cache + MPUSH消息推送系统缓存模块 + https://github.com/mpusher/mpush - ${mpush.groupId} - mpush-zk + ${project.groupId} + mpush-monitor redis.clients diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisClient.java b/mpush-cache/src/main/java/com/mpush/cache/redis/RedisClient.java deleted file mode 100644 index 2ff4c2cf..00000000 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisClient.java +++ /dev/null @@ -1,766 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.cache.redis; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.mpush.tools.Jsons; -import com.mpush.tools.config.CC; -import com.mpush.tools.log.Logs; -import redis.clients.jedis.*; - -import java.util.*; - -import static redis.clients.jedis.Protocol.DEFAULT_TIMEOUT; - -public class RedisClient { - public static final JedisPoolConfig CONFIG = buildConfig(); - private static final Map POOL_MAP = Maps.newConcurrentMap(); - - private static JedisPoolConfig buildConfig() { - JedisPoolConfig config = new JedisPoolConfig(); - //连接池中最大连接数。高版本:maxTotal,低版本:maxActive - config.setMaxTotal(CC.mp.redis.config.maxTotal); - //连接池中最大空闲的连接数 - config.setMaxIdle(CC.mp.redis.config.maxIdle); - //连接池中最少空闲的连接数 - config.setMinIdle(CC.mp.redis.config.minIdle); - //当连接池资源耗尽时,调用者最大阻塞的时间,超时将跑出异常。单位,毫秒数;默认为-1.表示永不超时。高版本:maxWaitMillis,低版本:maxWait - config.setMaxWaitMillis(CC.mp.redis.config.maxWaitMillis); - //连接空闲的最小时间,达到此值后空闲连接将可能会被移除。负值(-1)表示不移除 - config.setMinEvictableIdleTimeMillis(CC.mp.redis.config.minEvictableIdleTimeMillis); - //对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3 - config.setNumTestsPerEvictionRun(CC.mp.redis.config.numTestsPerEvictionRun); - //“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1 - config.setTimeBetweenEvictionRunsMillis(CC.mp.redis.config.timeBetweenEvictionRunsMillis); - //testOnBorrow:向调用者输出“链接”资源时,是否检测是有有效,如果无效则从连接池中移除,并尝试获取继续获取。默认为false。建议保持默认值. - config.setTestOnBorrow(CC.mp.redis.config.testOnBorrow); - //testOnReturn:向连接池“归还”链接时,是否检测“链接”对象的有效性。默认为false。建议保持默认值 - config.setTestOnReturn(CC.mp.redis.config.testOnReturn); - //testWhileIdle:向调用者输出“链接”对象时,是否检测它的空闲超时;默认为false。如果“链接”空闲超时,将会被移除。建议保持默认值. - config.setTestWhileIdle(CC.mp.redis.config.testWhileIdle); - return config; - } - - public static Jedis getClient(RedisServer node) { - JedisPool pool = POOL_MAP.get(node); - if (pool == null) { - pool = new JedisPool(CONFIG, node.getHost(), node.getPort(), DEFAULT_TIMEOUT, node.getPassword()); - POOL_MAP.put(node, pool); - } - return pool.getResource(); - } - - public static void close(Jedis jedis) { - if (jedis != null) jedis.close(); - } - - public static long incr(List nodeList, String key, Integer time) { - long incrRet = -1; - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - long ret = jedis.incr(key); - if (ret == 1 && time != null) { - jedis.expire(key, time); - } - incrRet = ret; - } catch (Exception e) { - Logs.REDIS.error("redis incr exception:{}, {}, {}, {}", key, time, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - return incrRet; - - } - - public static long incrBy(List nodeList, String key, long delt) { - long incrRet = -1; - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - long ret = jedis.incrBy(key, delt); - incrRet = ret; - } catch (Exception e) { - Logs.REDIS.error("redis incr exception:{}, {}, {}, {}", key, delt, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - return incrRet; - - } - - /********************* k v redis start ********************************/ - /** - * @param node redis实例 - * @param key - * @param clazz - * @return - */ - @SuppressWarnings("unchecked") - public static T get(RedisServer node, String key, Class clazz) { - - String value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - value = jedis.get(key); - } catch (Exception e) { - Logs.REDIS.error("redis get exception:{}, {}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - if (clazz == String.class) return (T) value; - return Jsons.fromJson(value, clazz); - } - - public static void set(List nodeList, String key, String value) { - set(nodeList, key, value, null); - } - - public static void set(List nodeList, String key, T value) { - set(nodeList, key, value, null); - } - - public static void set(List nodeList, String key, T value, Integer time) { - String jsonValue = Jsons.toJson(value); - set(nodeList, key, jsonValue, time); - } - - /** - * @param nodeList - * @param key - * @param value - * @param time seconds - */ - public static void set(List nodeList, String key, String value, Integer time) { - if (time == null) { - time = -1; - } - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.set(key, value); - if (time > 0) { - jedis.expire(key, time); - } - } catch (Exception e) { - Logs.REDIS.error("redis set exception:{}, {}, {}, {}", key, value, time, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - } - - public static void del(List nodeList, String key) { - - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.del(key); - } catch (Exception e) { - Logs.REDIS.error("redis del exception:{}, {}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - } - - /********************* k v redis end ********************************/ - - /********************* - * hash redis start - ********************************/ - public static void hset(List nodeList, String key, String field, String value) { - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.hset(key, field, value); - } catch (Exception e) { - Logs.REDIS.error("redis hset exception:{}, {}, {}, {}", key, field, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - } - - public static void hset(List nodeList, String key, String field, T value) { - hset(nodeList, key, field, Jsons.toJson(value)); - } - - public static T hget(RedisServer node, String key, String field, Class clazz) { - - String value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - value = jedis.hget(key, field); - } catch (Exception e) { - Logs.REDIS.error("redis hget exception:{}, {}", key, field, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return Jsons.fromJson(value, clazz); - - } - - public static void hdel(List nodeList, String key, String field) { - - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.hdel(key, field); - } catch (Exception e) { - Logs.REDIS.error("redis hdel exception:{}, {}, {}", key, field, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - } - - public static Map hgetAll(RedisServer node, String key) { - Map result = null; - Jedis jedis = null; - try { - jedis = getClient(node); - result = jedis.hgetAll(key); - } catch (Exception e) { - Logs.REDIS.error("redis hgetAll exception:{}, {}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return result; - } - - public static Map hgetAll(RedisServer node, String key, Class clazz) { - Map result = hgetAll(node, key); - if (result != null) { - Map newMap = Maps.newHashMap(); - Iterator> iterator = result.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - String k = entry.getKey(); - String v = entry.getValue(); - newMap.put(k, Jsons.fromJson(v, clazz)); - } - return newMap; - } else { - return null; - } - } - - /** - * 返回 key 指定的哈希集中所有字段的名字。 - * - * @param node - * @param key - * @return - */ - public static Set hkeys(RedisServer node, String key) { - Set result = null; - Jedis jedis = null; - try { - jedis = getClient(node); - result = jedis.hkeys(key); - } catch (Exception e) { - Logs.REDIS.error("redis hkeys exception:{},{}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - - } - return result; - } - - /** - * 返回 key 指定的哈希集中指定字段的值 - * - * @param node - * @param fields - * @param clazz - * @return - */ - public static List hmget(RedisServer node, String key, Class clazz, String... fields) { - - List value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - value = jedis.hmget(key, fields); - } catch (Exception e) { - Logs.REDIS.error("redis hmget exception:{},{},{}", key, fields, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return toList(value, clazz); - - } - - /** - * 设置 key 指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key - * 关联 - * - * @param nodeList - * @param hash - * @param time - */ - public static void hmset(List nodeList, String key, Map hash, Integer time) { - - if (time == null) { - time = -1; - } - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.hmset(key, hash); - if (time > 0) { - jedis.expire(key, time); - } - - } catch (Exception e) { - Logs.REDIS.error("redis hmset exception:{},{},{}", key, time, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - } - - public static void hmset(List nodeList, String key, Map hash) { - hmset(nodeList, key, hash, null); - } - - /********************* hash redis end ********************************/ - - /********************* list redis start ********************************/ - /** - * 从队列的左边入队 - */ - public static void lpush(List nodeList, String key, String value) { - - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.lpush(key, value); - } catch (Exception e) { - Logs.REDIS.error("redis lpush exception:{},{},{}", key, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - } - - public static void lpush(List nodeList, String key, T value) { - - lpush(nodeList, key, Jsons.toJson(value)); - - } - - /** - * 从队列的右边入队 - */ - public static void rpush(List nodeList, String key, String value) { - - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.rpush(key, value); - } catch (Exception e) { - Logs.REDIS.error("redis rpush exception:{},{},{}", key, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - } - - public static void rpush(List nodeList, String key, T value) { - rpush(nodeList, key, Jsons.toJson(value)); - } - - /** - * 移除并且返回 key 对应的 list 的第一个元素 - */ - public static T lpop(List nodeList, String key, Class clazz) { - String retValue = null; - for (RedisServer node : nodeList) { - Jedis jedis = null; - String vaule = null; - try { - jedis = getClient(node); - vaule = jedis.lpop(key); - retValue = vaule; - } catch (Exception e) { - Logs.REDIS.error("redis lpop exception:{},{}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - return Jsons.fromJson(retValue, clazz); - } - - /** - * 从队列的右边出队一个元素 - */ - public static T rpop(List nodeList, String key, Class clazz) { - String retValue = null; - for (RedisServer node : nodeList) { - Jedis jedis = null; - String vaule = null; - try { - jedis = getClient(node); - vaule = jedis.rpop(key); - retValue = vaule; - } catch (Exception e) { - Logs.REDIS.error("redis rpop exception:{},{}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - return Jsons.fromJson(retValue, clazz); - } - - /** - * 从列表中获取指定返回的元素 start 和 end - * 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。 - * 偏移量也可以是负数,表示偏移量是从list尾部开始计数。 例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。 - */ - public static List lrange(RedisServer node, String key, int start, int end, Class clazz) { - List value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - value = jedis.lrange(key, start, end); - } catch (Exception e) { - Logs.REDIS.error("redis lrange exception:{},{},{},{}", key, start, end, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return toList(value, clazz); - } - - /** - * 返回存储在 key 里的list的长度。 如果 key 不存在,那么就被看作是空list,并且返回长度为 0。 当存储在 key - * 里的值不是一个list的话,会返回error。 - */ - public static long llen(RedisServer node, String key) { - - long len = 0; - Jedis jedis = null; - try { - jedis = getClient(node); - len = jedis.llen(key); - } catch (Exception e) { - Logs.REDIS.error("redis llen exception:{},{}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return len; - } - - /** - * 移除表中所有与 value 相等的值 - * - * @param nodeList - * @param key - * @param value - */ - public static void lRem(List nodeList, String key, String value) { - - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.lrem(key, 0, value); - } catch (Exception e) { - Logs.REDIS.error("redis lrem exception:{},{},{}", key, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - } - - /********************* list redis end ********************************/ - - /********************* - * mq redis start - ********************************/ - - - public static void publish(RedisServer node, String channel, T message) { - Jedis jedis = null; - String value = null; - if (message instanceof String) { - value = (String) message; - } else { - value = Jsons.toJson(message); - } - try { - jedis = getClient(node); - jedis.publish(channel, value); - } catch (Exception e) { - Logs.REDIS.error("redis publish exception:{},{},{}", value, Jsons.toJson(node), Jsons.toJson(channel), e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - public static void subscribe(Set nodeList, final JedisPubSub pubsub, final String... channels) { - for (final RedisServer node : nodeList) { - new Thread(new Runnable() { - @Override - public void run() { - subscribe(node, pubsub, channels); - } - }, node.getHost() + "_" + Jsons.toJson(channels) - ).start(); - } - } - - public static void subscribe(RedisServer node, JedisPubSub pubsub, String... channel) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.subscribe(pubsub, channel); - } catch (Exception e) { - Logs.REDIS.error("redis subscribe exception:{},{}", Jsons.toJson(node), Jsons.toJson(channel), e); - } finally { - // 返还到连接池 - close(jedis); - } - - } - - /********************* - * set redis start - ********************************/ - /** - * @param nodeList - * @param key - * @param value - */ - public static void sAdd(List nodeList, String key, String value) { - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.sadd(key, value); - } catch (Exception e) { - Logs.REDIS.error("redis sadd exception:{},{},{}", key, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - } - - /** - * @param node 返回个数 - * @param key - * @return - */ - public static Long sCard(RedisServer node, String key) { - - Long value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - value = jedis.scard(key); - } catch (Exception e) { - Logs.REDIS.error("redis scard exception:{},{}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return value; - } - - public static void sRem(List nodeList, String key, String value) { - - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.srem(key, value); - } catch (Exception e) { - Logs.REDIS.error("redis srem exception:{},{},{}", key, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - } - - /** - * 默认使用每页10个 - * - * @param node - * @param key - * @param clazz - * @return - */ - public static List sScan(RedisServer node, String key, Class clazz, int start) { - - List value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - ScanResult sscanResult = jedis.sscan(key, start + "", new ScanParams().count(10)); - if (sscanResult != null && sscanResult.getResult() != null) { - value = sscanResult.getResult(); - } - } catch (Exception e) { - Logs.REDIS.error("redis sscan exception:{},{},{}", key, start, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return toList(value, clazz); - } - - /********************* - * sorted set - ********************************/ - /** - * @param nodeList - * @param key - * @param value - */ - public static void zAdd(List nodeList, String key, String value) { - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.zadd(key, 0, value); - } catch (Exception e) { - Logs.REDIS.error("redis zadd exception:{},{},{}", key, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - } - - /** - * @param node 返回个数 - * @param key - * @return - */ - public static Long zCard(RedisServer node, String key) { - - Long value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - value = jedis.zcard(key); - } catch (Exception e) { - Logs.REDIS.error("redis zcard exception:{},{}", key, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - return value; - } - - public static void zRem(List nodeList, String key, String value) { - - for (RedisServer node : nodeList) { - Jedis jedis = null; - try { - jedis = getClient(node); - jedis.zrem(key, value); - } catch (Exception e) { - Logs.REDIS.error("redis srem exception:{},{},{}", key, value, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - } - - } - - /** - * 从列表中获取指定返回的元素 start 和 end - * 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。 - * 偏移量也可以是负数,表示偏移量是从list尾部开始计数。 例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。 - */ - public static List zrange(RedisServer node, String key, int start, int end, Class clazz) { - Set value = null; - Jedis jedis = null; - try { - jedis = getClient(node); - value = jedis.zrange(key, start, end); - } catch (Exception e) { - Logs.REDIS.error("redis zrange exception:{},{},{},{}", key, start, end, node, e); - } finally { - // 返还到连接池 - close(jedis); - } - - return toList(value, clazz); - } - - private static List toList(Collection value, Class clazz) { - if (value != null) { - List newValue = Lists.newArrayList(); - for (String temp : value) { - newValue.add(Jsons.fromJson(temp, clazz)); - } - return newValue; - } - return null; - } - -} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisGroup.java b/mpush-cache/src/main/java/com/mpush/cache/redis/RedisGroup.java deleted file mode 100644 index cd11ef06..00000000 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisGroup.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.cache.redis; - -import com.google.common.collect.Lists; - -import java.util.List; - - -/** - * redis 组 - */ -public class RedisGroup { - private List redisServerList = Lists.newArrayList(); - - public List getRedisServerList() { - return redisServerList; - } - - public void setRedisServerList(List redisServerList) { - this.redisServerList = redisServerList; - } - - public void addRedisNode(RedisServer node) { - redisServerList.add(node); - } - - public void remove(int i) { - if (redisServerList != null) { - redisServerList.remove(i); - } - } - - public void clear() { - if (redisServerList != null) { - redisServerList.clear(); - } - } - - public RedisServer get(String key) { - int i = key.hashCode() % redisServerList.size(); - return redisServerList.get(i); - } - - public static RedisGroup from(com.mpush.tools.config.data.RedisGroup node) { - RedisGroup group = new RedisGroup(); - for (com.mpush.tools.config.data.RedisServer rs : node.redisNodeList) { - group.addRedisNode(new RedisServer(rs.getHost(), rs.getPort(), rs.getPassword())); - } - return group; - } -} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisServer.java b/mpush-cache/src/main/java/com/mpush/cache/redis/RedisServer.java index 88a4abdc..9bea88e7 100644 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisServer.java +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/RedisServer.java @@ -19,13 +19,20 @@ package com.mpush.cache.redis; +import com.mpush.tools.config.data.RedisNode; +import redis.clients.jedis.HostAndPort; + /** * redis 相关的配置信息 */ -public class RedisServer extends com.mpush.tools.config.data.RedisServer { +public class RedisServer extends RedisNode { + + public RedisServer(String ip, int port) { + super(ip, port); + } - public RedisServer(String ip, int port, String password) { - super(ip, port, password); + public HostAndPort convert() { + return new HostAndPort(host, port); } } diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/connection/RedisConnectionFactory.java b/mpush-cache/src/main/java/com/mpush/cache/redis/connection/RedisConnectionFactory.java new file mode 100644 index 00000000..49d55350 --- /dev/null +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/connection/RedisConnectionFactory.java @@ -0,0 +1,349 @@ +/* + * Copyright 2011-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mpush.cache.redis.connection; + +import com.mpush.cache.redis.RedisServer; +import com.mpush.tools.config.data.RedisNode; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import redis.clients.jedis.*; +import redis.clients.util.Pool; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Connection factory creating Jedis based connections. + * + * @author Costin Leau + * @author Thomas Darimont + * @author Christoph Strobl + * @author Mark Paluch + */ +public class RedisConnectionFactory { + + private final static Logger log = LoggerFactory.getLogger(RedisConnectionFactory.class); + + private String hostName = "localhost"; + private int port = Protocol.DEFAULT_PORT; + private int timeout = Protocol.DEFAULT_TIMEOUT; + private String password; + + private String sentinelMaster; + private List redisServers; + private boolean isCluster = false; + private int dbIndex = 0; + + private JedisShardInfo shardInfo; + private Pool pool; + private JedisCluster cluster; + private JedisPoolConfig poolConfig = new JedisPoolConfig(); + + /** + * Constructs a new JedisConnectionFactory instance with default settings (default connection pooling, no + * shard information). + */ + public RedisConnectionFactory() { + } + + /** + * Returns a Jedis instance to be used as a Redis connection. The instance can be newly created or retrieved from a + * pool. + */ + protected Jedis fetchJedisConnector() { + try { + + if (pool != null) { + return pool.getResource(); + } + Jedis jedis = new Jedis(getShardInfo()); + // force initialization (see Jedis issue #82) + jedis.connect(); + return jedis; + } catch (Exception ex) { + throw new RuntimeException("Cannot get Jedis connection", ex); + } + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void init() { + if (shardInfo == null) { + shardInfo = new JedisShardInfo(hostName, port); + + if (StringUtils.isNotEmpty(password)) { + shardInfo.setPassword(password); + } + + if (timeout > 0) { + shardInfo.setConnectionTimeout(timeout); + } + } + + if (isCluster) { + this.cluster = createCluster(); + } else { + this.pool = createPool(); + } + } + + private Pool createPool() { + if (StringUtils.isNotBlank(sentinelMaster)) { + return createRedisSentinelPool(); + } + return createRedisPool(); + } + + /** + * Creates {@link JedisSentinelPool}. + * + * @return + * @since 1.4 + */ + protected Pool createRedisSentinelPool() { + Set hostAndPorts = redisServers + .stream() + .map(redisNode -> new HostAndPort(redisNode.host, redisNode.port).toString()) + .collect(Collectors.toSet()); + + return new JedisSentinelPool(sentinelMaster, hostAndPorts, poolConfig, getShardInfo().getSoTimeout(), getShardInfo().getPassword()); + } + + + /** + * Creates {@link JedisPool}. + * + * @return + * @since 1.4 + */ + protected Pool createRedisPool() { + return new JedisPool(getPoolConfig(), shardInfo.getHost(), shardInfo.getPort(), shardInfo.getSoTimeout(), shardInfo.getPassword()); + } + + /** + * @return + * @since 1.7 + */ + protected JedisCluster createCluster() { + + Set hostAndPorts = redisServers + .stream() + .map(redisNode -> new HostAndPort(redisNode.host, redisNode.port)) + .collect(Collectors.toSet()); + + + if (StringUtils.isNotEmpty(getPassword())) { + throw new IllegalArgumentException("Jedis does not support password protected Redis Cluster configurations!"); + } + int redirects = 5; + return new JedisCluster(hostAndPorts, timeout, redirects, poolConfig); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() { + if (pool != null) { + try { + pool.destroy(); + } catch (Exception ex) { + log.warn("Cannot properly close Jedis pool", ex); + } + pool = null; + } + if (cluster != null) { + try { + cluster.close(); + } catch (Exception ex) { + log.warn("Cannot properly close Jedis cluster", ex); + } + cluster = null; + } + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisConnectionFactory#getConnection() + */ + public Jedis getJedisConnection() { + Jedis jedis = fetchJedisConnector(); + if (dbIndex > 0 && jedis != null) { + jedis.select(dbIndex); + } + return jedis; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.redis.connection.RedisConnectionFactory#getClusterConnection() + */ + public JedisCluster getClusterConnection() { + return cluster; + } + + public boolean isCluster() { + return isCluster; + } + + /** + * Returns the Redis hostName. + * + * @return Returns the hostName + */ + public String getHostName() { + return hostName; + } + + /** + * Sets the Redis hostName. + * + * @param hostName The hostName to set. + */ + public void setHostName(String hostName) { + this.hostName = hostName; + } + + /** + * Returns the password used for authenticating with the Redis server. + * + * @return password for authentication + */ + public String getPassword() { + return password; + } + + /** + * Sets the password used for authenticating with the Redis server. + * + * @param password the password to set + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Returns the port used to connect to the Redis instance. + * + * @return Redis port. + */ + public int getPort() { + return port; + + } + + /** + * Sets the port used to connect to the Redis instance. + * + * @param port Redis port + */ + public void setPort(int port) { + this.port = port; + } + + /** + * Returns the shardInfo. + * + * @return Returns the shardInfo + */ + public JedisShardInfo getShardInfo() { + return shardInfo; + } + + /** + * Sets the shard info for this factory. + * + * @param shardInfo The shardInfo to set. + */ + public void setShardInfo(JedisShardInfo shardInfo) { + this.shardInfo = shardInfo; + } + + /** + * Returns the timeout. + * + * @return Returns the timeout + */ + public int getTimeout() { + return timeout; + } + + /** + * @param timeout The timeout to set. + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Returns the poolConfig. + * + * @return Returns the poolConfig + */ + public JedisPoolConfig getPoolConfig() { + return poolConfig; + } + + /** + * Sets the pool configuration for this factory. + * + * @param poolConfig The poolConfig to set. + */ + public void setPoolConfig(JedisPoolConfig poolConfig) { + this.poolConfig = poolConfig; + } + + /** + * Returns the index of the database. + * + * @return Returns the database index + */ + public int getDatabase() { + return dbIndex; + } + + /** + * Sets the index of the database used by this connection factory. Default is 0. + * + * @param index database index + */ + public void setDatabase(int index) { + this.dbIndex = index; + } + + public void setCluster(boolean cluster) { + isCluster = cluster; + } + + public void setSentinelMaster(String sentinelMaster) { + this.sentinelMaster = sentinelMaster; + } + + public void setRedisServers(List redisServers) { + if (redisServers == null || redisServers.isEmpty()) { + throw new IllegalArgumentException("redis server node can not be empty, please check your conf."); + } + this.redisServers = redisServers; + this.hostName = redisServers.get(0).getHost(); + this.port = redisServers.get(0).getPort(); + } +} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/hash/ConsistentHash.java b/mpush-cache/src/main/java/com/mpush/cache/redis/hash/ConsistentHash.java deleted file mode 100644 index ee43587a..00000000 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/hash/ConsistentHash.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.cache.redis.hash; - -import redis.clients.util.Hashing; - -import java.util.Collection; -import java.util.SortedMap; -import java.util.TreeMap; - -public class ConsistentHash { - - private final Hashing hash; - private final int numberOfReplicas; - private final SortedMap circle = new TreeMap(); - - public ConsistentHash(Hashing hash, int numberOfReplicas, - Collection nodes) { - super(); - this.hash = hash; - this.numberOfReplicas = numberOfReplicas; - for (Node node : nodes) { - add(node); - } - } - - /** - * 增加真实机器节点 - * - * @param node - */ - public void add(Node node) { - for (int i = 0; i < this.numberOfReplicas; i++) { - circle.put(this.hash.hash(node.toString() + i), node); - } - } - - /** - * 删除真实机器节点 - * - * @param node - */ - public void remove(String node) { - for (int i = 0; i < this.numberOfReplicas; i++) { - circle.remove(this.hash.hash(node.toString() + i)); - } - } - - /** - * 取得真实机器节点 - * - * @param key - * @return - */ - public Node get(String key) { - if (circle.isEmpty()) { - return null; - } - long hash = this.hash.hash(key); - if (!circle.containsKey(hash)) { - SortedMap tailMap = circle.tailMap(hash);// 沿环的顺时针找到一个虚拟节点 - hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); - } - return circle.get(hash); // 返回该虚拟节点对应的真实机器节点的信息 - } - - -} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/listener/ListenerDispatcher.java b/mpush-cache/src/main/java/com/mpush/cache/redis/listener/ListenerDispatcher.java deleted file mode 100644 index f74f1c27..00000000 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/listener/ListenerDispatcher.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.cache.redis.listener; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.mpush.cache.redis.manager.RedisManager; -import com.mpush.cache.redis.mq.Subscriber; -import com.mpush.tools.log.Logs; -import com.mpush.tools.thread.pool.ThreadPoolManager; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.Executor; - -public class ListenerDispatcher implements MessageListener { - - public static final ListenerDispatcher I = new ListenerDispatcher(); - - private final Map> subscribes = Maps.newTreeMap(); - - private final Executor executor = ThreadPoolManager.I.getRedisExecutor(); - - private ListenerDispatcher() { - } - - @Override - public void onMessage(final String channel, final String message) { - List listeners = subscribes.get(channel); - if (listeners == null) { - Logs.REDIS.info("cannot find listener:%s,%s", channel, message); - return; - } - for (final MessageListener listener : listeners) { - executor.execute(() -> listener.onMessage(channel, message)); - } - } - - public void subscribe(String channel, MessageListener listener) { - List listeners = subscribes.get(channel); - if (listeners == null) { - listeners = Lists.newArrayList(); - subscribes.put(channel, listeners); - } - listeners.add(listener); - RedisManager.I.subscribe(Subscriber.holder, channel); - } -} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisCacheManagerFactory.java b/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisCacheManagerFactory.java new file mode 100644 index 00000000..ebde35fd --- /dev/null +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisCacheManagerFactory.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.cache.redis.manager; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 1) +public final class RedisCacheManagerFactory implements CacheManagerFactory { + @Override + public CacheManager get() { + return RedisManager.I; + } +} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisClusterManager.java b/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisClusterManager.java index 9c90877b..8126c85f 100644 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisClusterManager.java +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisClusterManager.java @@ -19,7 +19,6 @@ package com.mpush.cache.redis.manager; -import com.mpush.cache.redis.RedisGroup; import com.mpush.cache.redis.RedisServer; import java.util.List; @@ -28,9 +27,5 @@ public interface RedisClusterManager { void init(); - List getGroupList(); - - RedisServer randomGetRedisNode(String key); - - List hashSet(String key); + List getServers(); } diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisManager.java b/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisManager.java index 50c10e21..9b2dd102 100644 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisManager.java +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/manager/RedisManager.java @@ -19,57 +19,111 @@ package com.mpush.cache.redis.manager; -import com.google.common.collect.Sets; -import com.mpush.cache.redis.RedisClient; -import com.mpush.cache.redis.RedisGroup; -import com.mpush.cache.redis.RedisServer; +import com.google.common.collect.Lists; +import com.mpush.api.spi.common.*; +import com.mpush.cache.redis.connection.RedisConnectionFactory; import com.mpush.tools.Jsons; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPubSub; +import com.mpush.tools.Utils; +import com.mpush.tools.config.CC; +import com.mpush.tools.log.Logs; +import com.mpush.monitor.service.ThreadPoolManager; +import redis.clients.jedis.*; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; /** * redis 对外封装接口 */ -public class RedisManager { +public final class RedisManager implements CacheManager { public static final RedisManager I = new RedisManager(); - private final RedisClusterManager clusterManager = ZKRedisClusterManager.I; + private final RedisConnectionFactory factory = new RedisConnectionFactory(); public void init() { - ZKRedisClusterManager.I.init(); - test(clusterManager.getGroupList()); + Logs.CACHE.info("begin init redis..."); + factory.setPassword(CC.mp.redis.password); + factory.setPoolConfig(CC.mp.redis.getPoolConfig(JedisPoolConfig.class)); + factory.setRedisServers(CC.mp.redis.nodes); + factory.setCluster(CC.mp.redis.isCluster()); + if (CC.mp.redis.isSentinel()) { + factory.setSentinelMaster(CC.mp.redis.sentinelMaster); + } + factory.init(); + test(); + Logs.CACHE.info("init redis success..."); + } + + private R call(Function function, R d) { + if (factory.isCluster()) { + try { + return function.apply(factory.getClusterConnection()); + } catch (Exception e) { + Logs.CACHE.error("redis ex", e); + throw new RuntimeException(e); + } + } else { + try (Jedis jedis = factory.getJedisConnection()) { + return function.apply(jedis); + } catch (Exception e) { + Logs.CACHE.error("redis ex", e); + throw new RuntimeException(e); + } + } } - public long incr(String key, Integer time) { - List nodeList = clusterManager.hashSet(key); - return RedisClient.incr(nodeList, key, time); + private void call(Consumer consumer) { + if (factory.isCluster()) { + try { + consumer.accept(factory.getClusterConnection()); + } catch (Exception e) { + Logs.CACHE.error("redis ex", e); + throw new RuntimeException(e); + } + } else { + try (Jedis jedis = factory.getJedisConnection()) { + consumer.accept(jedis); + } catch (Exception e) { + Logs.CACHE.error("redis ex", e); + throw new RuntimeException(e); + } + } } - public long incrBy(String key, long delt) { - List nodeList = clusterManager.hashSet(key); - return RedisClient.incrBy(nodeList, key, delt); + public long incr(String key) { + return call(jedis -> jedis.incr(key), 0L); } - /********************* - * k v redis start - ********************************/ + public long incrBy(String key, long delt) { + return call(jedis -> jedis.incrBy(key, delt), 0L); + } + /********************* k v redis start ********************************/ + /** + * @param key + * @param clazz + * @return + */ + @SuppressWarnings("unchecked") public T get(String key, Class clazz) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.get(node, key, clazz); + String value = call(jedis -> jedis.get(key), null); + if (value == null) return null; + if (clazz == String.class) return (T) value; + return Jsons.fromJson(value, clazz); + } + + public void set(String key, String value) { + set(key, value, 0); } - public void set(String key, T value) { - set(key, value, null); + public void set(String key, Object value) { + set(key, value, 0); } - public void set(String key, T value, Integer time) { - String jsonValue = Jsons.toJson(value); - set(key, jsonValue, time); + public void set(String key, Object value, int time) { + set(key, Jsons.toJson(value), time); } /** @@ -77,67 +131,64 @@ public void set(String key, T value, Integer time) { * @param value * @param time seconds */ - public void set(String key, String value, Integer time) { - List nodeList = clusterManager.hashSet(key); - RedisClient.set(nodeList, key, value, time); + public void set(String key, String value, int time) { + call(jedis -> { + jedis.set(key, value); + if (time > 0) { + jedis.expire(key, time); + } + }); } public void del(String key) { - - List nodeList = clusterManager.hashSet(key); - RedisClient.del(nodeList, key); - + call(jedis -> jedis.del(key)); } - /*********************k v redis end********************************/ - + /********************* k v redis end ********************************/ /********************* * hash redis start ********************************/ public void hset(String key, String field, String value) { - - List nodeList = clusterManager.hashSet(field); - RedisClient.hset(nodeList, key, field, value); - + call(jedis -> jedis.hset(key, field, value)); } - public void hset(String key, String field, T value) { + public void hset(String key, String field, Object value) { hset(key, field, Jsons.toJson(value)); } + @SuppressWarnings("unchecked") public T hget(String key, String field, Class clazz) { - - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.hget(node, key, field, clazz); - + String value = call(jedis -> jedis.hget(key, field), null); + if (value == null) return null; + if (clazz == String.class) return (T) value; + return Jsons.fromJson(value, clazz); } public void hdel(String key, String field) { - List nodeList = clusterManager.hashSet(key); - RedisClient.hdel(nodeList, key, field); + call(jedis -> jedis.hdel(key, field)); } public Map hgetAll(String key) { - - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.hgetAll(node, key); - + return call(jedis -> jedis.hgetAll(key), Collections.emptyMap()); } public Map hgetAll(String key, Class clazz) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.hgetAll(node, key, clazz); + Map result = hgetAll(key); + if (result.isEmpty()) return Collections.emptyMap(); + Map newMap = new HashMap<>(result.size()); + result.forEach((k, v) -> newMap.put(k, Jsons.fromJson(v, clazz))); + return newMap; } /** * 返回 key 指定的哈希集中所有字段的名字。 * + * @param key * @return */ public Set hkeys(String key) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.hkeys(node, key); + return call(jedis -> jedis.hkeys(key), Collections.emptySet()); } /** @@ -148,39 +199,48 @@ public Set hkeys(String key) { * @return */ public List hmget(String key, Class clazz, String... fields) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.hmget(node, key, clazz, fields); + return call(jedis -> jedis.hmget(key, fields), Collections.emptyList()) + .stream() + .map(s -> Jsons.fromJson(s, clazz)) + .collect(Collectors.toList()); + } /** - * 设置 key 指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联 + * 设置 key 指定的哈希集中指定字段的值。该命令将重写所有在哈希集中存在的字段。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key + * 关联 * * @param hash * @param time */ - public void hmset(String key, Map hash, Integer time) { - List nodeList = clusterManager.hashSet(key); - RedisClient.hmset(nodeList, key, hash, time); + public void hmset(String key, Map hash, int time) { + call(jedis -> { + jedis.hmset(key, hash); + if (time > 0) { + jedis.expire(key, time); + } + }); } public void hmset(String key, Map hash) { - hmset(key, hash, null); + hmset(key, hash, 0); } + public long hincrBy(String key, String field, long value) { + return call(jedis -> jedis.hincrBy(key, field, value), 0L); + } - /*********************hash redis end********************************/ - + /********************* hash redis end ********************************/ - /*********************list redis start********************************/ + /********************* list redis start ********************************/ /** * 从队列的左边入队 */ - public void lpush(String key, String value) { - List nodeList = clusterManager.hashSet(key); - RedisClient.lpush(nodeList, key, value); + public void lpush(String key, String... value) { + call(jedis -> jedis.lpush(key, value)); } - public void lpush(String key, T value) { + public void lpush(String key, Object value) { lpush(key, Jsons.toJson(value)); } @@ -188,131 +248,190 @@ public void lpush(String key, T value) { * 从队列的右边入队 */ public void rpush(String key, String value) { - List nodeList = clusterManager.hashSet(key); - RedisClient.rpush(nodeList, key, value); + call(jedis -> jedis.lpush(key, value)); } - public void rpush(String key, T value) { + public void rpush(String key, Object value) { rpush(key, Jsons.toJson(value)); } /** * 移除并且返回 key 对应的 list 的第一个元素 */ + @SuppressWarnings("unchecked") public T lpop(String key, Class clazz) { - List nodeList = clusterManager.hashSet(key); - return RedisClient.lpop(nodeList, key, clazz); + String value = call(jedis -> jedis.lpop(key), null); + if (value == null) return null; + if (clazz == String.class) return (T) value; + return Jsons.fromJson(value, clazz); } /** * 从队列的右边出队一个元素 */ + @SuppressWarnings("unchecked") public T rpop(String key, Class clazz) { - List nodeList = clusterManager.hashSet(key); - return RedisClient.rpop(nodeList, key, clazz); + String value = call(jedis -> jedis.rpop(key), null); + if (value == null) return null; + if (clazz == String.class) return (T) value; + return Jsons.fromJson(value, clazz); } - /** - * 从列表中获取指定返回的元素 - * start 和 end 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。 + * 从列表中获取指定返回的元素 start 和 end + * 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。 * 偏移量也可以是负数,表示偏移量是从list尾部开始计数。 例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。 */ public List lrange(String key, int start, int end, Class clazz) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.lrange(node, key, start, end, clazz); + return call(jedis -> jedis.lrange(key, start, end), Collections.emptyList()) + .stream() + .map(s -> Jsons.fromJson(s, clazz)) + .collect(Collectors.toList()); } /** - * 返回存储在 key 里的list的长度。 如果 key 不存在,那么就被看作是空list,并且返回长度为 0。 当存储在 key 里的值不是一个list的话,会返回error。 + * 返回存储在 key 里的list的长度。 如果 key 不存在,那么就被看作是空list,并且返回长度为 0。 当存储在 key + * 里的值不是一个list的话,会返回error。 */ public long llen(String key) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.llen(node, key); + return call(jedis -> jedis.llen(key), 0L); } - public void lrem(String key, T value) { - String jsonValue = Jsons.toJson(value); - List nodeList = clusterManager.hashSet(key); - RedisClient.lRem(nodeList, key, jsonValue); + /** + * 移除表中所有与 value 相等的值 + * + * @param key + * @param value + */ + public void lRem(String key, String value) { + call(jedis -> jedis.lrem(key, 0, value)); } - public void publish(String channel, T message) { - - RedisServer node = clusterManager.randomGetRedisNode(channel); - RedisClient.publish(node, channel, message); + /********************* list redis end ********************************/ - } + /********************* + * mq redis start + ********************************/ - public void subscribe(JedisPubSub pubsub, String... channels) { - Set set = Sets.newHashSet(); - for (String channel : channels) { - List nodeList = clusterManager.hashSet(channel); - set.addAll(nodeList); - } + public void publish(String channel, Object message) { + String msg = message instanceof String ? (String) message : Jsons.toJson(message); + call(jedis -> { + if (jedis instanceof MultiKeyCommands) { + ((MultiKeyCommands) jedis).publish(channel, msg); + } else if (jedis instanceof MultiKeyJedisClusterCommands) { + ((MultiKeyJedisClusterCommands) jedis).publish(channel, msg); + } + }); + } - RedisClient.subscribe(set, pubsub, channels); + public void subscribe(final JedisPubSub pubsub, final String channel) { + Utils.newThread(channel, + () -> call(jedis -> { + if (jedis instanceof MultiKeyCommands) { + ((MultiKeyCommands) jedis).subscribe(pubsub, channel); + } else if (jedis instanceof MultiKeyJedisClusterCommands) { + ((MultiKeyJedisClusterCommands) jedis).subscribe(pubsub, channel); + } + }) + ).start(); } - public void sAdd(String key, T value) { - String jsonValue = Jsons.toJson(value); - List nodeList = clusterManager.hashSet(key); - RedisClient.sAdd(nodeList, key, jsonValue); + /********************* + * set redis start + ********************************/ + /** + * @param key + * @param value + */ + public void sAdd(String key, String value) { + call(jedis -> jedis.sadd(key, value)); } - public Long sCard(String key) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.sCard(node, key); + /** + * @param key + * @return + */ + public long sCard(String key) { + return call(jedis -> jedis.scard(key), 0L); } - public void sRem(String key, T value) { - String jsonValue = Jsons.toJson(value); - List nodeList = clusterManager.hashSet(key); - RedisClient.sRem(nodeList, key, jsonValue); + public void sRem(String key, String value) { + call(jedis -> jedis.srem(key, value)); } - public List sScan(String key, int start, Class clazz) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.sScan(node, key, clazz, start); + /** + * 默认使用每页10个 + * + * @param key + * @param clazz + * @return + */ + public List sScan(String key, Class clazz, int start) { + List list = call(jedis -> jedis.sscan(key, Integer.toString(start), new ScanParams().count(10)).getResult(), null); + return toList(list, clazz); } - public void zAdd(String key, T value) { - String jsonValue = Jsons.toJson(value); - List nodeList = clusterManager.hashSet(key); - RedisClient.zAdd(nodeList, key, jsonValue); + /********************* + * sorted set + ********************************/ + /** + * @param key + * @param value + */ + public void zAdd(String key, String value) { + call(jedis -> jedis.zadd(key, 0, value)); } + /** + * @param key + * @return + */ public Long zCard(String key) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.zCard(node, key); + return call(jedis -> jedis.zcard(key), 0L); } - public void zRem(String key, T value) { - String jsonValue = Jsons.toJson(value); - List nodeList = clusterManager.hashSet(key); - RedisClient.zRem(nodeList, key, jsonValue); + public void zRem(String key, String value) { + call(jedis -> jedis.zrem(key, value)); } + /** + * 从列表中获取指定返回的元素 start 和 end + * 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头),第二个元素下标是1,以此类推。 + * 偏移量也可以是负数,表示偏移量是从list尾部开始计数。 例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。 + */ public List zrange(String key, int start, int end, Class clazz) { - RedisServer node = clusterManager.randomGetRedisNode(key); - return RedisClient.zrange(node, key, start, end, clazz); + Set value = call(jedis -> jedis.zrange(key, start, end), null); + return toList(value, clazz); } - public void test(List groupList) { - if (groupList == null || groupList.isEmpty()) { - throw new RuntimeException("init redis sever error."); - } - for (RedisGroup group : groupList) { - List list = group.getRedisServerList(); - if (list == null || list.isEmpty()) { - throw new RuntimeException("init redis sever error."); + @SuppressWarnings("unchecked") + private List toList(Collection value, Class clazz) { + if (value != null) { + if (clazz == String.class) { + return (List) new ArrayList<>(value); } - for (RedisServer node : list) { - Jedis jedis = RedisClient.getClient(node); - if (jedis == null) throw new RuntimeException("init redis sever error."); - jedis.close(); + List newValue = Lists.newArrayList(); + for (String temp : value) { + newValue.add(Jsons.fromJson(temp, clazz)); } + return newValue; + } + return null; + } + + public void destroy() { + if (factory != null) factory.destroy(); + } + + public void test() { + if (factory.isCluster()) { + JedisCluster cluster = factory.getClusterConnection(); + if (cluster == null) throw new RuntimeException("init redis cluster error."); + } else { + Jedis jedis = factory.getJedisConnection(); + if (jedis == null) throw new RuntimeException("init redis error, can not get connection."); + jedis.close(); } } } diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/manager/ZKRedisClusterManager.java b/mpush-cache/src/main/java/com/mpush/cache/redis/manager/ZKRedisClusterManager.java deleted file mode 100644 index 54abe851..00000000 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/manager/ZKRedisClusterManager.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.cache.redis.manager; - -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.mpush.cache.redis.RedisException; -import com.mpush.cache.redis.RedisGroup; -import com.mpush.cache.redis.RedisServer; -import com.mpush.tools.Jsons; -import com.mpush.tools.config.CC; -import com.mpush.tools.log.Logs; -import com.mpush.zk.ZKClient; -import com.mpush.zk.listener.ZKRedisNodeWatcher; -import com.mpush.zk.node.ZKRedisNode; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import static com.mpush.zk.ZKPath.REDIS_SERVER; - -public class ZKRedisClusterManager implements RedisClusterManager { - public static final ZKRedisClusterManager I = new ZKRedisClusterManager(); - - private ZKRedisClusterManager() { - } - - private final List groups = new ArrayList<>(); - - /** - * zk 启动的时候需要调用这个 - */ - @Override - public void init() { - Logs.Console.error("begin init redis cluster"); - if (!ZKClient.I.isRunning()) throw new RedisException("init redis cluster ex, ZK client not running."); - List groupList = CC.mp.redis.cluster_group; - if (groupList.size() > 0) { - if (CC.mp.redis.write_to_zk) { - register(groupList); - } else if (!ZKClient.I.isExisted(REDIS_SERVER.getRootPath())) { - register(groupList); - } else if (Strings.isNullOrEmpty(ZKClient.I.get(REDIS_SERVER.getRootPath()))) { - register(groupList); - } - } - - ZKRedisNodeWatcher watcher = new ZKRedisNodeWatcher(); - watcher.beginWatch(); - Collection nodes = watcher.getCache().values(); - if (nodes == null || nodes.isEmpty()) { - Logs.REDIS.info("init redis client error, redis server is none."); - throw new RedisException("init redis client error, redis server is none."); - } - for (ZKRedisNode node : nodes) { - groups.add(RedisGroup.from(node)); - } - if (groups.isEmpty()) throw new RedisException("init redis sever fail groupList is null"); - Logs.Console.error("init redis cluster success..."); - } - - @Override - public List getGroupList() { - return Collections.unmodifiableList(groups); - } - - public int groupSize() { - return groups.size(); - } - - /** - * 随机获取一个redis 实例 - * - * @param key - * @return - */ - @Override - public RedisServer randomGetRedisNode(String key) { - int size = groupSize(); - if (size == 1) return groups.get(0).get(key); - int i = (int) ((Math.random() % size) * size); - RedisGroup group = groups.get(i); - return group.get(key); - } - - /** - * 写操作的时候,获取所有redis 实例 - * - * @param key - * @return - */ - @Override - public List hashSet(String key) { - List nodeList = Lists.newArrayList(); - for (RedisGroup group : groups) { - RedisServer node = group.get(key); - nodeList.add(node); - } - return nodeList; - } - - private void register(List groupList) { - String data = Jsons.toJson(groupList); - ZKClient.I.registerPersist(REDIS_SERVER.getRootPath(), data); - Logs.Console.error("register redis server group success, group=" + data); - } - - public void addGroup(RedisGroup group) { - groups.add(group); - } -} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/mq/ListenerDispatcher.java b/mpush-cache/src/main/java/com/mpush/cache/redis/mq/ListenerDispatcher.java new file mode 100644 index 00000000..64bcc331 --- /dev/null +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/mq/ListenerDispatcher.java @@ -0,0 +1,79 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.cache.redis.mq; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.mpush.api.MPushContext; +import com.mpush.api.spi.common.ExecutorFactory; +import com.mpush.api.spi.common.MQClient; +import com.mpush.api.spi.common.MQMessageReceiver; +import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.monitor.service.MonitorService; +import com.mpush.monitor.service.ThreadPoolManager; +import com.mpush.tools.log.Logs; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; + +public final class ListenerDispatcher implements MQClient { + + private final Map> subscribes = Maps.newTreeMap(); + + private final Subscriber subscriber; + + private Executor executor; + + @Override + public void init(MPushContext context) { + executor = ((MonitorService) context.getMonitor()).getThreadPoolManager().getRedisExecutor(); + } + + public ListenerDispatcher() { + this.subscriber = new Subscriber(this); + } + + public void onMessage(String channel, String message) { + List listeners = subscribes.get(channel); + if (listeners == null) { + Logs.CACHE.info("cannot find listener:{}, {}", channel, message); + return; + } + + for (MQMessageReceiver listener : listeners) { + executor.execute(() -> listener.receive(channel, message)); + } + } + + public void subscribe(String channel, MQMessageReceiver listener) { + subscribes.computeIfAbsent(channel, k -> Lists.newArrayList()).add(listener); + RedisManager.I.subscribe(subscriber, channel); + } + + @Override + public void publish(String topic, Object message) { + RedisManager.I.publish(topic, message); + } + + public Subscriber getSubscriber() { + return subscriber; + } +} diff --git a/mpush-zk/src/main/java/com/mpush/zk/node/ZKRedisNode.java b/mpush-cache/src/main/java/com/mpush/cache/redis/mq/RedisMQClientFactory.java similarity index 53% rename from mpush-zk/src/main/java/com/mpush/zk/node/ZKRedisNode.java rename to mpush-cache/src/main/java/com/mpush/cache/redis/mq/RedisMQClientFactory.java index 0d30efec..288c128c 100644 --- a/mpush-zk/src/main/java/com/mpush/zk/node/ZKRedisNode.java +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/mq/RedisMQClientFactory.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,32 +14,26 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.zk.node; +package com.mpush.cache.redis.mq; -import com.mpush.tools.Jsons; -import com.mpush.tools.config.data.RedisGroup; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.MQClient; +import com.mpush.api.spi.common.MQClientFactory; /** - * Created by yxx on 2016/5/18. + * Created by ohun on 2016/12/27. * - * @author ohun@live.cn + * @author ohun@live.cn (夜色) */ -public class ZKRedisNode extends RedisGroup implements ZKNode { - private transient String zkPath; - - public String getZkPath() { - return zkPath; - } - - public void setZkPath(String zkPath) { - this.zkPath = zkPath; - } +@Spi(order = 1) +public final class RedisMQClientFactory implements MQClientFactory { + private ListenerDispatcher listenerDispatcher = new ListenerDispatcher(); @Override - public String encode() { - return Jsons.toJson(this); + public MQClient get() { + return listenerDispatcher; } } diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/mq/Subscriber.java b/mpush-cache/src/main/java/com/mpush/cache/redis/mq/Subscriber.java index 95afbcf6..db4f54c6 100644 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/mq/Subscriber.java +++ b/mpush-cache/src/main/java/com/mpush/cache/redis/mq/Subscriber.java @@ -19,67 +19,64 @@ package com.mpush.cache.redis.mq; -import com.mpush.cache.redis.listener.ListenerDispatcher; import com.mpush.tools.Jsons; import com.mpush.tools.log.Logs; import redis.clients.jedis.JedisPubSub; -public class Subscriber extends JedisPubSub { +public final class Subscriber extends JedisPubSub { + private final ListenerDispatcher listenerDispatcher; - private static ListenerDispatcher dispatcher = ListenerDispatcher.I; - - public static Subscriber holder = new Subscriber(); - - private Subscriber() { + public Subscriber(ListenerDispatcher listenerDispatcher) { + this.listenerDispatcher = listenerDispatcher; } @Override public void onMessage(String channel, String message) { - Logs.REDIS.info("onMessage:{},{}", channel, message); - dispatcher.onMessage(channel, message); + Logs.CACHE.info("onMessage:{},{}", channel, message); + listenerDispatcher.onMessage(channel, message); super.onMessage(channel, message); } @Override public void onPMessage(String pattern, String channel, String message) { - Logs.REDIS.info("onPMessage:{},{},{}", pattern, channel, message); + Logs.CACHE.info("onPMessage:{},{},{}", pattern, channel, message); super.onPMessage(pattern, channel, message); } @Override public void onPSubscribe(String pattern, int subscribedChannels) { - Logs.REDIS.info("onPSubscribe:{},{}", pattern, subscribedChannels); + Logs.CACHE.info("onPSubscribe:{},{}", pattern, subscribedChannels); super.onPSubscribe(pattern, subscribedChannels); } @Override public void onPUnsubscribe(String pattern, int subscribedChannels) { - Logs.REDIS.info("onPUnsubscribe:{},{}", pattern, subscribedChannels); + Logs.CACHE.info("onPUnsubscribe:{},{}", pattern, subscribedChannels); super.onPUnsubscribe(pattern, subscribedChannels); } @Override public void onSubscribe(String channel, int subscribedChannels) { - Logs.REDIS.info("onSubscribe:{},{}", channel, subscribedChannels); + Logs.CACHE.info("onSubscribe:{},{}", channel, subscribedChannels); super.onSubscribe(channel, subscribedChannels); } @Override public void onUnsubscribe(String channel, int subscribedChannels) { - Logs.REDIS.info("onUnsubscribe:{},{}", channel, subscribedChannels); + Logs.CACHE.info("onUnsubscribe:{},{}", channel, subscribedChannels); super.onUnsubscribe(channel, subscribedChannels); } @Override public void unsubscribe() { - Logs.REDIS.info("unsubscribe"); + Logs.CACHE.info("unsubscribe"); super.unsubscribe(); } @Override public void unsubscribe(String... channels) { - Logs.REDIS.info("unsubscribe:{}", Jsons.toJson(channels)); + Logs.CACHE.info("unsubscribe:{}", Jsons.toJson(channels)); super.unsubscribe(channels); } diff --git a/mpush-cache/src/main/resources/META-INF/services/com.mpush.api.spi.common.CacheManagerFactory b/mpush-cache/src/main/resources/META-INF/services/com.mpush.api.spi.common.CacheManagerFactory new file mode 100644 index 00000000..54e843c3 --- /dev/null +++ b/mpush-cache/src/main/resources/META-INF/services/com.mpush.api.spi.common.CacheManagerFactory @@ -0,0 +1 @@ +com.mpush.cache.redis.manager.RedisCacheManagerFactory \ No newline at end of file diff --git a/mpush-cache/src/main/resources/META-INF/services/com.mpush.api.spi.common.MQClientFactory b/mpush-cache/src/main/resources/META-INF/services/com.mpush.api.spi.common.MQClientFactory new file mode 100644 index 00000000..3c3abb42 --- /dev/null +++ b/mpush-cache/src/main/resources/META-INF/services/com.mpush.api.spi.common.MQClientFactory @@ -0,0 +1 @@ +com.mpush.cache.redis.mq.RedisMQClientFactory \ No newline at end of file diff --git a/mpush-client/.gitignore b/mpush-client/.gitignore deleted file mode 100644 index ae3c1726..00000000 --- a/mpush-client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/bin/ diff --git a/mpush-client/pom.xml b/mpush-client/pom.xml index c62a39b4..0607b657 100644 --- a/mpush-client/pom.xml +++ b/mpush-client/pom.xml @@ -2,34 +2,38 @@ + + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} mpush-client - ${mpush.version} - mpush-client jar + mpush-client + MPUSH消息推送系统服务端SDK + https://github.com/mpusher/mpush + - ${mpush.groupId} - mpush-api - - - ${mpush.groupId} + ${project.groupId} mpush-netty - ${mpush.groupId} + ${project.groupId} mpush-common - ${mpush.groupId} + ${project.groupId} mpush-cache + + ${project.groupId} + mpush-zk + diff --git a/mpush-client/src/main/java/com/mpush/client/ClientExecutorFactory.java b/mpush-client/src/main/java/com/mpush/client/ClientExecutorFactory.java new file mode 100644 index 00000000..de4b8c88 --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/ClientExecutorFactory.java @@ -0,0 +1,63 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client; + +import com.mpush.api.spi.Spi; +import com.mpush.common.CommonExecutorFactory; +import com.mpush.tools.log.Logs; +import com.mpush.tools.thread.NamedPoolThreadFactory; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import static com.mpush.tools.config.CC.mp.thread.pool.ack_timer; +import static com.mpush.tools.config.CC.mp.thread.pool.push_client; +import static com.mpush.tools.thread.ThreadNames.T_ARK_REQ_TIMER; +import static com.mpush.tools.thread.ThreadNames.T_PUSH_CLIENT_TIMER; + +/** + * 此线程池可伸缩,线程空闲一定时间后回收,新请求重新创建线程 + */ +@Spi(order = 1) +public final class ClientExecutorFactory extends CommonExecutorFactory { + + @Override + public Executor get(String name) { + switch (name) { + case PUSH_CLIENT: { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(push_client + , new NamedPoolThreadFactory(T_PUSH_CLIENT_TIMER), (r, e) -> r.run() // run caller thread + ); + executor.setRemoveOnCancelPolicy(true); + return executor; + } + case ACK_TIMER: { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(ack_timer, + new NamedPoolThreadFactory(T_ARK_REQ_TIMER), + (r, e) -> Logs.PUSH.error("one ack context was rejected, context=" + r) + ); + executor.setRemoveOnCancelPolicy(true); + return executor; + } + default: + return super.get(name); + } + } +} diff --git a/mpush-client/src/main/java/com/mpush/client/MPushClient.java b/mpush-client/src/main/java/com/mpush/client/MPushClient.java new file mode 100644 index 00000000..ab74cfee --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/MPushClient.java @@ -0,0 +1,105 @@ +/* + * (C) Copyright 2015-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client; + +import com.mpush.api.MPushContext; +import com.mpush.api.spi.common.*; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.api.srd.ServiceRegistry; +import com.mpush.client.gateway.connection.GatewayConnectionFactory; +import com.mpush.client.push.PushRequestBus; +import com.mpush.common.router.CachedRemoteRouterManager; +import com.mpush.monitor.service.MonitorService; +import com.mpush.monitor.service.ThreadPoolManager; +import com.mpush.tools.event.EventBus; + +/** + * Created by ohun on 2017/7/15. + * + * @author ohun@live.cn (夜色) + */ +public final class MPushClient implements MPushContext { + + private MonitorService monitorService; + + private PushRequestBus pushRequestBus; + + private CachedRemoteRouterManager cachedRemoteRouterManager; + + private GatewayConnectionFactory gatewayConnectionFactory; + + public MPushClient() { + monitorService = new MonitorService(); + + EventBus.create(monitorService.getThreadPoolManager().getEventBusExecutor()); + + pushRequestBus = new PushRequestBus(this); + + cachedRemoteRouterManager = new CachedRemoteRouterManager(); + + gatewayConnectionFactory = GatewayConnectionFactory.create(this); + } + + public MonitorService getMonitorService() { + return monitorService; + } + + public ThreadPoolManager getThreadPoolManager() { + return monitorService.getThreadPoolManager(); + } + + public PushRequestBus getPushRequestBus() { + return pushRequestBus; + } + + public CachedRemoteRouterManager getCachedRemoteRouterManager() { + return cachedRemoteRouterManager; + } + + public GatewayConnectionFactory getGatewayConnectionFactory() { + return gatewayConnectionFactory; + } + + @Override + public MonitorService getMonitor() { + return monitorService; + } + + @Override + public ServiceDiscovery getDiscovery() { + return ServiceDiscoveryFactory.create(); + } + + @Override + public ServiceRegistry getRegistry() { + return ServiceRegistryFactory.create(); + } + + @Override + public CacheManager getCacheManager() { + return CacheManagerFactory.create(); + } + + @Override + public MQClient getMQClient() { + return MQClientFactory.create(); + } + +} diff --git a/mpush-client/src/main/java/com/mpush/client/connect/ClientConfig.java b/mpush-client/src/main/java/com/mpush/client/connect/ClientConfig.java index ed5e5131..693a78ea 100644 --- a/mpush-client/src/main/java/com/mpush/client/connect/ClientConfig.java +++ b/mpush-client/src/main/java/com/mpush/client/connect/ClientConfig.java @@ -95,4 +95,11 @@ public void setUserId(String userId) { this.userId = userId; } + @Override + public String toString() { + return "{" + + "deviceId='" + deviceId + '\'' + + ", userId='" + userId + '\'' + + '}'; + } } diff --git a/mpush-client/src/main/java/com/mpush/client/connect/ConnClientChannelHandler.java b/mpush-client/src/main/java/com/mpush/client/connect/ConnClientChannelHandler.java index 7daaf8ba..6bf1c6d1 100644 --- a/mpush-client/src/main/java/com/mpush/client/connect/ConnClientChannelHandler.java +++ b/mpush-client/src/main/java/com/mpush/client/connect/ConnClientChannelHandler.java @@ -21,42 +21,52 @@ import com.google.common.collect.Maps; +import com.mpush.api.Constants; import com.mpush.api.connection.Connection; import com.mpush.api.event.ConnectionCloseEvent; import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; -import com.mpush.cache.redis.RedisKey; -import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; +import com.mpush.common.CacheKeys; import com.mpush.common.message.*; import com.mpush.common.security.AesCipher; import com.mpush.common.security.CipherBox; import com.mpush.netty.connection.NettyConnection; import com.mpush.tools.event.EventBus; -import com.mpush.tools.thread.PoolThreadFactory; +import com.mpush.tools.thread.NamedPoolThreadFactory; import com.mpush.tools.thread.ThreadNames; -import io.netty.channel.*; -import io.netty.util.HashedWheelTimer; -import io.netty.util.Timeout; -import io.netty.util.Timer; -import io.netty.util.TimerTask; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.concurrent.TimeUnit; + /** * Created by ohun on 2015/12/19. * * @author ohun@live.cn */ -@ChannelHandler.Sharable -public final class ConnClientChannelHandler extends ChannelHandlerAdapter { +public final class ConnClientChannelHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(ConnClientChannelHandler.class); - private static final Timer HASHED_WHEEL_TIMER = new HashedWheelTimer(new PoolThreadFactory(ThreadNames.T_NETTY_TIMER)); + private static final Timer HASHED_WHEEL_TIMER = new HashedWheelTimer(new NamedPoolThreadFactory(ThreadNames.T_CONN_TIMER)); + public static final AttributeKey CONFIG_KEY = AttributeKey.newInstance("clientConfig"); + public static final TestStatistics STATISTICS = new TestStatistics(); + private static CacheManager cacheManager = CacheManagerFactory.create(); private final Connection connection = new NettyConnection(); - private final ClientConfig clientConfig; + + private ClientConfig clientConfig; + private boolean perfTest; + private int hbTimeoutTimes; + + public ConnClientChannelHandler() { + perfTest = true; + } public ConnClientChannelHandler(ClientConfig clientConfig) { this.clientConfig = clientConfig; @@ -69,20 +79,25 @@ public Connection getConnection() { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { connection.updateLastReadTime(); - //加密 if (msg instanceof Packet) { Packet packet = (Packet) msg; Command command = Command.toCMD(packet.cmd); if (command == Command.HANDSHAKE) { + int connectedNum = STATISTICS.connectedNum.incrementAndGet(); connection.getSessionContext().changeCipher(new AesCipher(clientConfig.getClientKey(), clientConfig.getIv())); HandshakeOkMessage message = new HandshakeOkMessage(packet, connection); + message.decodeBody(); byte[] sessionKey = CipherBox.I.mixKey(clientConfig.getClientKey(), message.serverKey); connection.getSessionContext().changeCipher(new AesCipher(sessionKey, clientConfig.getIv())); - startHeartBeat(message.heartbeat); - LOGGER.warn("会话密钥:{},message={}", sessionKey, message); + connection.getSessionContext().setHeartbeat(message.heartbeat); + startHeartBeat(message.heartbeat - 1000); + LOGGER.info("handshake success, clientConfig={}, connectedNum={}", clientConfig, connectedNum); bindUser(clientConfig); - saveToRedisForFastConnection(clientConfig, message.sessionId, message.expireTime, sessionKey); + if (!perfTest) { + saveToRedisForFastConnection(clientConfig, message.sessionId, message.expireTime, sessionKey); + } } else if (command == Command.FAST_CONNECT) { + int connectedNum = STATISTICS.connectedNum.incrementAndGet(); String cipherStr = clientConfig.getCipher(); String[] cs = cipherStr.split(","); byte[] key = AesCipher.toArray(cs[0]); @@ -90,34 +105,53 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception connection.getSessionContext().changeCipher(new AesCipher(key, iv)); FastConnectOkMessage message = new FastConnectOkMessage(packet, connection); - startHeartBeat(message.heartbeat); + message.decodeBody(); + connection.getSessionContext().setHeartbeat(message.heartbeat); + startHeartBeat(message.heartbeat - 1000); bindUser(clientConfig); - LOGGER.warn("fast connect success, message=" + message); + LOGGER.info("fast connect success, clientConfig={}, connectedNum={}", clientConfig, connectedNum); } else if (command == Command.KICK) { KickUserMessage message = new KickUserMessage(packet, connection); - LOGGER.error("receive kick user userId={}, deviceId={}, message={},", clientConfig.getUserId(), clientConfig.getDeviceId(), message); + LOGGER.error("receive kick user msg userId={}, deviceId={}, message={},", clientConfig.getUserId(), clientConfig.getDeviceId(), message); ctx.close(); } else if (command == Command.ERROR) { - ErrorMessage errorMessage = new ErrorMessage(packet, connection); - LOGGER.error("receive an error packet=" + errorMessage); - } else if (command == Command.BIND) { - OkMessage okMessage = new OkMessage(packet, connection); - LOGGER.warn("receive an success packet=" + okMessage); - HttpRequestMessage message = new HttpRequestMessage(connection); - message.uri = "http://baidu.com"; - message.send(); + ErrorMessage message = new ErrorMessage(packet, connection); + message.decodeBody(); + LOGGER.error("receive an error packet=" + message); } else if (command == Command.PUSH) { + int receivePushNum = STATISTICS.receivePushNum.incrementAndGet(); + PushMessage message = new PushMessage(packet, connection); - LOGGER.warn("receive an push message, content=" + message.content); + message.decodeBody(); + LOGGER.info("receive push message, content={}, receivePushNum={}" + , new String(message.content, Constants.UTF_8), receivePushNum); + + if (message.needAck()) { + AckMessage.from(message).sendRaw(); + LOGGER.info("send ack success for sessionId={}", message.getSessionId()); + } + } else if (command == Command.HEARTBEAT) { - LOGGER.warn("receive a heartbeat pong..."); - } else { - LOGGER.warn("receive a message, type=" + command + "," + packet); + LOGGER.info("receive heartbeat pong..."); + } else if (command == Command.OK) { + OkMessage message = new OkMessage(packet, connection); + message.decodeBody(); + int bindUserNum = STATISTICS.bindUserNum.get(); + if (message.cmd == Command.BIND.cmd) { + bindUserNum = STATISTICS.bindUserNum.incrementAndGet(); + } + + LOGGER.info("receive {}, bindUserNum={}", message, bindUserNum); + + } else if (command == Command.HTTP_PROXY) { + HttpResponseMessage message = new HttpResponseMessage(packet, connection); + message.decodeBody(); + LOGGER.info("receive http response, message={}, body={}", + message, message.body == null ? null : new String(message.body, Constants.UTF_8)); } } - - LOGGER.warn("update currentTime:" + ctx.channel() + "," + msg); + LOGGER.debug("receive package={}, chanel={}", msg, ctx.channel()); } @Override @@ -128,16 +162,33 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - LOGGER.info("client connect channel={}", ctx.channel()); + int clientNum = STATISTICS.clientNum.incrementAndGet(); + LOGGER.info("client connect channel={}, clientNum={}", ctx.channel(), clientNum); + + for (int i = 0; i < 3; i++) { + if (clientConfig != null) break; + clientConfig = ctx.channel().attr(CONFIG_KEY).getAndSet(null); + if (clientConfig == null) TimeUnit.SECONDS.sleep(1); + } + + if (clientConfig == null) { + throw new NullPointerException("client config is null, channel=" + ctx.channel()); + } + connection.init(ctx.channel(), true); - handshake(clientConfig); + if (perfTest) { + handshake(); + } else { + tryFastConnect(); + } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { + int clientNum = STATISTICS.clientNum.decrementAndGet(); connection.close(); - EventBus.I.post(new ConnectionCloseEvent(connection)); - LOGGER.info("client disconnect connection={}", connection); + EventBus.post(new ConnectionCloseEvent(connection)); + LOGGER.info("client disconnect channel={}, clientNum={}", connection, clientNum); } private void tryFastConnect() { @@ -145,19 +196,19 @@ private void tryFastConnect() { Map sessionTickets = getFastConnectionInfo(clientConfig.getDeviceId()); if (sessionTickets == null) { - handshake(clientConfig); + handshake(); return; } String sessionId = sessionTickets.get("sessionId"); if (sessionId == null) { - handshake(clientConfig); + handshake(); return; } String expireTime = sessionTickets.get("expireTime"); if (expireTime != null) { long exp = Long.parseLong(expireTime); if (exp < System.currentTimeMillis()) { - handshake(clientConfig); + handshake(); return; } } @@ -172,15 +223,19 @@ private void tryFastConnect() { if (channelFuture.isSuccess()) { clientConfig.setCipher(cipher); } else { - handshake(clientConfig); + handshake(); } }); + LOGGER.debug("send fast connect message={}", message); } private void bindUser(ClientConfig client) { BindUserMessage message = new BindUserMessage(connection); message.userId = client.getUserId(); + message.tags = "test"; message.send(); + connection.getSessionContext().setUserId(client.getUserId()); + LOGGER.debug("send bind user message={}", message); } private void saveToRedisForFastConnection(ClientConfig client, String sessionId, Long expireTime, byte[] sessionKey) { @@ -188,50 +243,61 @@ private void saveToRedisForFastConnection(ClientConfig client, String sessionId, map.put("sessionId", sessionId); map.put("expireTime", expireTime + ""); map.put("cipherStr", connection.getSessionContext().cipher.toString()); - String key = RedisKey.getDeviceIdKey(client.getDeviceId()); - RedisManager.I.set(key, map, 60 * 5); //5分钟 + String key = CacheKeys.getDeviceIdKey(client.getDeviceId()); + cacheManager.set(key, map, 60 * 5); //5分钟 } + @SuppressWarnings("unchecked") private Map getFastConnectionInfo(String deviceId) { - String key = RedisKey.getDeviceIdKey(deviceId); - return RedisManager.I.get(key, Map.class); + String key = CacheKeys.getDeviceIdKey(deviceId); + return cacheManager.get(key, Map.class); } - private void handshake(ClientConfig client) { + private void handshake() { HandshakeMessage message = new HandshakeMessage(connection); - message.clientKey = client.getClientKey(); - message.iv = client.getIv(); - message.clientVersion = client.getClientVersion(); - message.deviceId = client.getDeviceId(); - message.osName = client.getOsName(); - message.osVersion = client.getOsVersion(); + message.clientKey = clientConfig.getClientKey(); + message.iv = clientConfig.getIv(); + message.clientVersion = clientConfig.getClientVersion(); + message.deviceId = clientConfig.getDeviceId(); + message.osName = clientConfig.getOsName(); + message.osVersion = clientConfig.getOsVersion(); message.timestamp = System.currentTimeMillis(); message.send(); + LOGGER.debug("send handshake message={}", message); } - public void startHeartBeat(final int heartbeat) throws Exception { + private void startHeartBeat(final int heartbeat) throws Exception { HASHED_WHEEL_TIMER.newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { - final TimerTask self = this; - final Channel channel = connection.getChannel(); - if (channel.isActive()) { - ChannelFuture channelFuture = channel.writeAndFlush(Packet.getHBPacket()); - channelFuture.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - LOGGER.debug("client send msg hb success:" + channel.remoteAddress().toString()); - } else { - LOGGER.warn("client send msg hb false:" + channel.remoteAddress().toString(), future.cause()); - } - HASHED_WHEEL_TIMER.newTimeout(self, heartbeat, TimeUnit.MILLISECONDS); - } - }); - } else { - LOGGER.error("connection was closed, connection={}", connection); + if (connection.isConnected() && healthCheck()) { + HASHED_WHEEL_TIMER.newTimeout(this, heartbeat, TimeUnit.MILLISECONDS); } } }, heartbeat, TimeUnit.MILLISECONDS); } + + private boolean healthCheck() { + + if (connection.isReadTimeout()) { + hbTimeoutTimes++; + LOGGER.warn("heartbeat timeout times={}, client={}", hbTimeoutTimes, connection); + } else { + hbTimeoutTimes = 0; + } + + if (hbTimeoutTimes >= 2) { + LOGGER.warn("heartbeat timeout times={} over limit={}, client={}", hbTimeoutTimes, 2, connection); + hbTimeoutTimes = 0; + connection.close(); + return false; + } + + if (connection.isWriteTimeout()) { + LOGGER.info("send heartbeat ping..."); + connection.send(Packet.HB_PACKET); + } + + return true; + } } \ No newline at end of file diff --git a/mpush-client/src/main/java/com/mpush/client/connect/ConnectClient.java b/mpush-client/src/main/java/com/mpush/client/connect/ConnectClient.java index 0a69d57d..529decd9 100644 --- a/mpush-client/src/main/java/com/mpush/client/connect/ConnectClient.java +++ b/mpush-client/src/main/java/com/mpush/client/connect/ConnectClient.java @@ -21,17 +21,16 @@ import com.google.common.eventbus.Subscribe; import com.mpush.api.event.ConnectionCloseEvent; -import com.mpush.netty.client.NettyClient; +import com.mpush.netty.client.NettyTCPClient; import com.mpush.tools.event.EventBus; import io.netty.channel.ChannelHandler; -public class ConnectClient extends NettyClient { +public class ConnectClient extends NettyTCPClient { private final ConnClientChannelHandler handler; public ConnectClient(String host, int port, ClientConfig config) { - super(host, port); handler = new ConnClientChannelHandler(config); - EventBus.I.register(this); + EventBus.register(this); } @Override @@ -44,4 +43,8 @@ void on(ConnectionCloseEvent event) { this.stop(); } + @Override + protected int getWorkThreadNum() { + return 1; + } } diff --git a/mpush-client/src/main/java/com/mpush/client/connect/TestStatistics.java b/mpush-client/src/main/java/com/mpush/client/connect/TestStatistics.java new file mode 100644 index 00000000..47dd683e --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/connect/TestStatistics.java @@ -0,0 +1,44 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.connect; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by ohun on 2016/12/8. + * + * @author ohun@live.cn (夜色) + */ +public final class TestStatistics { + public AtomicInteger clientNum = new AtomicInteger(); + public AtomicInteger connectedNum = new AtomicInteger(); + public AtomicInteger bindUserNum = new AtomicInteger(); + public AtomicInteger receivePushNum = new AtomicInteger(); + + @Override + public String toString() { + return "TestStatistics{" + + "clientNum=" + clientNum + + ", connectedNum=" + connectedNum + + ", bindUserNum=" + bindUserNum + + ", receivePushNum=" + receivePushNum + + '}'; + } +} diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClient.java b/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClient.java index 4b423d04..8160e256 100644 --- a/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClient.java +++ b/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClient.java @@ -19,32 +19,56 @@ package com.mpush.client.gateway; -import com.mpush.api.connection.Connection; +import com.mpush.api.connection.ConnectionManager; +import com.mpush.api.protocol.Command; import com.mpush.api.service.Listener; -import com.mpush.netty.client.NettyClient; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelPipeline; +import com.mpush.client.MPushClient; +import com.mpush.client.gateway.handler.GatewayClientChannelHandler; +import com.mpush.client.gateway.handler.GatewayErrorHandler; +import com.mpush.client.gateway.handler.GatewayOKHandler; +import com.mpush.common.MessageDispatcher; +import com.mpush.netty.client.NettyTCPClient; +import com.mpush.netty.connection.NettyConnectionManager; +import com.mpush.tools.config.CC; +import com.mpush.tools.config.CC.mp.net.rcv_buf; +import com.mpush.tools.config.CC.mp.net.snd_buf; +import com.mpush.tools.thread.NamedPoolThreadFactory; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.sctp.nio.NioSctpChannel; +import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler; +import java.nio.channels.spi.SelectorProvider; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import static com.mpush.tools.config.CC.mp.net.traffic_shaping.gateway_client.*; +import static com.mpush.tools.thread.ThreadNames.T_TRAFFIC_SHAPING; /** * Created by yxx on 2016/5/17. * * @author ohun@live.cn */ -public class GatewayClient extends NettyClient { - private final GatewayClientChannelHandler handler = new GatewayClientChannelHandler(); +public class GatewayClient extends NettyTCPClient { + private final GatewayClientChannelHandler handler; private GlobalChannelTrafficShapingHandler trafficShapingHandler; + private ScheduledExecutorService trafficShapingExecutor; + private final ConnectionManager connectionManager; + private final MessageDispatcher messageDispatcher; - public GatewayClient(String host, int port) { - super(host, port); + public GatewayClient(MPushClient mPushClient) { + messageDispatcher = new MessageDispatcher(); + messageDispatcher.register(Command.OK, () -> new GatewayOKHandler(mPushClient)); + messageDispatcher.register(Command.ERROR, () -> new GatewayErrorHandler(mPushClient)); + connectionManager = new NettyConnectionManager(); + handler = new GatewayClientChannelHandler(connectionManager, messageDispatcher); if (enabled) { + trafficShapingExecutor = Executors.newSingleThreadScheduledExecutor(new NamedPoolThreadFactory(T_TRAFFIC_SHAPING)); trafficShapingHandler = new GlobalChannelTrafficShapingHandler( - Executors.newSingleThreadScheduledExecutor() - , write_global_limit, read_global_limit, + trafficShapingExecutor, + write_global_limit, read_global_limit, write_channel_limit, read_channel_limit, check_interval); } @@ -55,10 +79,6 @@ public ChannelHandler getChannelHandler() { return handler; } - public Connection getConnection() { - return handler.getConnection(); - } - @Override protected void initPipeline(ChannelPipeline pipeline) { super.initPipeline(pipeline); @@ -71,7 +91,44 @@ protected void initPipeline(ChannelPipeline pipeline) { protected void doStop(Listener listener) throws Throwable { if (trafficShapingHandler != null) { trafficShapingHandler.release(); + trafficShapingExecutor.shutdown(); } super.doStop(listener); } + + @Override + protected void initOptions(Bootstrap b) { + super.initOptions(b); + if (snd_buf.gateway_client > 0) b.option(ChannelOption.SO_SNDBUF, snd_buf.gateway_client); + if (rcv_buf.gateway_client > 0) b.option(ChannelOption.SO_RCVBUF, rcv_buf.gateway_client); + } + + @Override + public ChannelFactory getChannelFactory() { + if (CC.mp.net.tcpGateway()) return super.getChannelFactory(); + if (CC.mp.net.udtGateway()) return NioUdtProvider.BYTE_CONNECTOR; + if (CC.mp.net.sctpGateway()) return NioSctpChannel::new; + return super.getChannelFactory(); + } + + @Override + public SelectorProvider getSelectorProvider() { + if (CC.mp.net.tcpGateway()) return super.getSelectorProvider(); + if (CC.mp.net.udtGateway()) return NioUdtProvider.BYTE_PROVIDER; + if (CC.mp.net.sctpGateway()) return super.getSelectorProvider(); + return super.getSelectorProvider(); + } + + @Override + protected int getWorkThreadNum() { + return CC.mp.thread.pool.gateway_client_work; + } + + public ConnectionManager getConnectionManager() { + return connectionManager; + } + + public MessageDispatcher getMessageDispatcher() { + return messageDispatcher; + } } diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClientChannelHandler.java b/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClientChannelHandler.java deleted file mode 100644 index cfb9bcd9..00000000 --- a/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClientChannelHandler.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.client.gateway; - - -import com.mpush.api.connection.Connection; -import com.mpush.api.protocol.Command; -import com.mpush.api.protocol.Packet; -import com.mpush.client.push.PushRequest; -import com.mpush.client.push.PushRequestBus; -import com.mpush.common.message.ErrorMessage; -import com.mpush.common.message.OkMessage; -import com.mpush.netty.connection.NettyConnection; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerAdapter; -import io.netty.channel.ChannelHandlerContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static com.mpush.common.ErrorCode.*; - -/** - * Created by ohun on 2015/12/19. - * - * @author ohun@live.cn - */ -@ChannelHandler.Sharable -public final class GatewayClientChannelHandler extends ChannelHandlerAdapter { - - private static final Logger LOGGER = LoggerFactory.getLogger(GatewayClientChannelHandler.class); - - private final Connection connection = new NettyConnection(); - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - LOGGER.info("receive gateway packet={}, channel={}", msg, ctx.channel()); - connection.updateLastReadTime(); - if (msg instanceof Packet) { - Packet packet = (Packet) msg; - if (packet.cmd == Command.OK.cmd) { - handleOK(new OkMessage(packet, connection)); - } else if (packet.cmd == Command.ERROR.cmd) { - handleError(new ErrorMessage(packet, connection)); - } - } - } - - private void handleOK(OkMessage message) { - if (message.cmd == Command.GATEWAY_PUSH.cmd) { - handPush(message, null, message.getPacket()); - } - } - - private void handleError(ErrorMessage message) { - if (message.cmd == Command.GATEWAY_PUSH.cmd) { - handPush(null, message, message.getPacket()); - } - } - - private void handPush(OkMessage ok, ErrorMessage error, Packet packet) { - PushRequest request = PushRequestBus.I.getAndRemove(packet.sessionId); - if (request == null) { - LOGGER.warn("receive a gateway response, but request has timeout. ok={}, error={}", ok, error); - return; - } - - if (ok != null) {//推送成功 - request.success(); - } else if (error != null) {//推送失败 - LOGGER.warn("receive an error gateway response, message={}", error); - if (error.code == OFFLINE.errorCode) {//用户离线 - request.offline(); - } else if (error.code == PUSH_CLIENT_FAILURE.errorCode) {//下发到客户端失败 - request.failure(); - } else if (error.code == ROUTER_CHANGE.errorCode) {//用户路由信息更改 - request.redirect(); - } - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - connection.close(); - LOGGER.error("caught an ex, channel={}", ctx.channel(), cause); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - LOGGER.info("client connect channel={}", ctx.channel()); - connection.init(ctx.channel(), false); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - connection.close(); - LOGGER.info("client disconnect channel={}", ctx.channel()); - //TODO notify gateway-client-factory to removeAndClose this gateway-client - } - - public Connection getConnection() { - return connection; - } -} \ No newline at end of file diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClientFactory.java b/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClientFactory.java deleted file mode 100644 index 6714c4d9..00000000 --- a/mpush-client/src/main/java/com/mpush/client/gateway/GatewayClientFactory.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.client.gateway; - -import com.google.common.collect.Maps; -import com.mpush.api.connection.Connection; -import com.mpush.api.service.Client; -import com.mpush.api.service.Listener; -import com.mpush.zk.cache.ZKServerNodeCache; -import com.mpush.zk.node.ZKServerNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; - -/** - * Created by yxx on 2016/5/17. - * - * @author ohun@live.cn - */ -public class GatewayClientFactory extends ZKServerNodeCache { - public static final GatewayClientFactory I = new GatewayClientFactory(); - - private final Logger logger = LoggerFactory.getLogger(GatewayClientFactory.class); - - private final Map ip_client = Maps.newConcurrentMap(); - - @Override - public void put(String fullPath, ZKServerNode node) { - super.put(fullPath, node); - addClient(node.getIp(), node.getPort()); - } - - @Override - public ZKServerNode remove(String fullPath) { - ZKServerNode node = super.remove(fullPath); - removeClient(node); - logger.warn("Gateway Server zkNode={} was removed.", node); - return node; - } - - @Override - public void clear() { - super.clear(); - for (GatewayClient client : ip_client.values()) { - client.stop(null); - } - } - - public GatewayClient getClient(String ip) { - GatewayClient client = ip_client.get(ip); - if (client == null) { - return null;//TODO create client - } - return client; - } - - public Connection getConnection(String ip) { - GatewayClient client = ip_client.get(ip); - if (client == null) { - return null;//TODO create client - } - Connection connection = client.getConnection(); - if (connection.isConnected()) { - return connection; - } - restartClient(client); - return null; - } - - private void restartClient(final GatewayClient client) { - ip_client.remove(client.getHost()); - client.stop(new Listener() { - @Override - public void onSuccess(Object... args) { - addClient(client.getHost(), client.getPort()); - } - - @Override - public void onFailure(Throwable cause) { - addClient(client.getHost(), client.getPort()); - } - }); - } - - private void removeClient(ZKServerNode node) { - if (node != null) { - Client client = ip_client.remove(node.getIp()); - if (client != null) { - client.stop(null); - } - } - } - - private void addClient(final String host, final int port) { - final GatewayClient client = new GatewayClient(host, port); - client.start(new Listener() { - @Override - public void onSuccess(Object... args) { - ip_client.put(host, client); - } - - @Override - public void onFailure(Throwable cause) { - logger.error("create gateway client ex, client={}", client, cause); - } - }); - } -} diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/GatewayUDPConnector.java b/mpush-client/src/main/java/com/mpush/client/gateway/GatewayUDPConnector.java new file mode 100644 index 00000000..4713e41e --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/gateway/GatewayUDPConnector.java @@ -0,0 +1,96 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.gateway; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; +import com.mpush.api.service.Listener; +import com.mpush.client.MPushClient; +import com.mpush.client.gateway.handler.GatewayErrorHandler; +import com.mpush.client.gateway.handler.GatewayOKHandler; +import com.mpush.common.MessageDispatcher; +import com.mpush.netty.udp.UDPChannelHandler; +import com.mpush.netty.udp.NettyUDPConnector; +import com.mpush.tools.Utils; +import com.mpush.tools.config.CC; +import com.mpush.tools.config.CC.mp.net.rcv_buf; +import com.mpush.tools.config.CC.mp.net.snd_buf; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelOption; + +import static com.mpush.common.MessageDispatcher.POLICY_LOG; + +/** + * Created by ohun on 2015/12/30. + * + * @author ohun@live.cn + */ +public final class GatewayUDPConnector extends NettyUDPConnector { + + private UDPChannelHandler channelHandler; + private MessageDispatcher messageDispatcher; + private MPushClient mPushClient; + + public GatewayUDPConnector(MPushClient mPushClient) { + super(CC.mp.net.gateway_client_port); + this.mPushClient = mPushClient; + this.messageDispatcher = new MessageDispatcher(POLICY_LOG); + } + + @Override + public void init() { + super.init(); + messageDispatcher.register(Command.OK, () -> new GatewayOKHandler(mPushClient)); + messageDispatcher.register(Command.ERROR, () -> new GatewayErrorHandler(mPushClient)); + channelHandler = new UDPChannelHandler(messageDispatcher); + channelHandler.setMulticastAddress(Utils.getInetAddress(CC.mp.net.gateway_client_multicast)); + channelHandler.setNetworkInterface(Utils.getLocalNetworkInterface()); + } + + + @Override + public void stop(Listener listener) { + super.stop(listener); + } + + + @Override + protected void initOptions(Bootstrap b) { + super.initOptions(b); + b.option(ChannelOption.IP_MULTICAST_LOOP_DISABLED, true); + b.option(ChannelOption.IP_MULTICAST_TTL, 255); + if (snd_buf.gateway_client > 0) b.option(ChannelOption.SO_SNDBUF, snd_buf.gateway_client); + if (rcv_buf.gateway_client > 0) b.option(ChannelOption.SO_RCVBUF, rcv_buf.gateway_client); + } + + @Override + public ChannelHandler getChannelHandler() { + return channelHandler; + } + + public Connection getConnection() { + return channelHandler.getConnection(); + } + + public MessageDispatcher getMessageDispatcher() { + return messageDispatcher; + } +} diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayConnectionFactory.java b/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayConnectionFactory.java new file mode 100644 index 00000000..f9e6304c --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayConnectionFactory.java @@ -0,0 +1,53 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.gateway.connection; + +import com.mpush.api.connection.Connection; +import com.mpush.api.service.BaseService; +import com.mpush.api.srd.ServiceListener; +import com.mpush.client.MPushClient; +import com.mpush.common.message.BaseMessage; +import com.mpush.tools.config.CC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Created by yxx on 2016/5/17. + * + * @author ohun@live.cn + */ +public abstract class GatewayConnectionFactory extends BaseService implements ServiceListener { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + public static GatewayConnectionFactory create(MPushClient mPushClient) { + return CC.mp.net.udpGateway() ? new GatewayUDPConnectionFactory(mPushClient) : new GatewayTCPConnectionFactory(mPushClient); + } + + abstract public Connection getConnection(String hostAndPort); + + abstract public boolean send(String hostAndPort, Function creator, Consumer sender); + + abstract public boolean broadcast(Function creator, Consumer sender); + +} diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayTCPConnectionFactory.java b/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayTCPConnectionFactory.java new file mode 100644 index 00000000..0b6910b1 --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayTCPConnectionFactory.java @@ -0,0 +1,219 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.gateway.connection; + +import com.google.common.collect.Maps; +import com.google.common.eventbus.AllowConcurrentEvents; +import com.google.common.eventbus.Subscribe; +import com.google.common.net.HostAndPort; +import com.mpush.api.connection.Connection; +import com.mpush.api.event.ConnectionConnectEvent; +import com.mpush.api.service.Listener; +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.api.srd.ServiceNode; +import com.mpush.client.MPushClient; +import com.mpush.client.gateway.GatewayClient; +import com.mpush.common.message.BaseMessage; +import com.mpush.tools.event.EventBus; +import io.netty.channel.ChannelFuture; +import io.netty.util.AttributeKey; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import static com.mpush.api.srd.ServiceNames.GATEWAY_SERVER; +import static com.mpush.tools.config.CC.mp.net.gateway_client_num; + +/** + * Created by yxx on 2016/5/17. + * + * @author ohun@live.cn + */ +public class GatewayTCPConnectionFactory extends GatewayConnectionFactory { + private final AttributeKey attrKey = AttributeKey.valueOf("host_port"); + private final Map> connections = Maps.newConcurrentMap(); + + private ServiceDiscovery discovery; + private GatewayClient gatewayClient; + + private MPushClient mPushClient; + + public GatewayTCPConnectionFactory(MPushClient mPushClient) { + this.mPushClient = mPushClient; + } + + @Override + protected void doStart(Listener listener) throws Throwable { + EventBus.register(this); + + gatewayClient = new GatewayClient(mPushClient); + gatewayClient.start().join(); + discovery = ServiceDiscoveryFactory.create(); + discovery.subscribe(GATEWAY_SERVER, this); + discovery.lookup(GATEWAY_SERVER).forEach(this::syncAddConnection); + listener.onSuccess(); + } + + @Override + public void onServiceAdded(String path, ServiceNode node) { + asyncAddConnection(node); + } + + @Override + public void onServiceUpdated(String path, ServiceNode node) { + removeClient(node); + asyncAddConnection(node); + } + + @Override + public void onServiceRemoved(String path, ServiceNode node) { + removeClient(node); + logger.warn("Gateway Server zkNode={} was removed.", node); + } + + @Override + public void doStop(Listener listener) throws Throwable { + connections.values().forEach(l -> l.forEach(Connection::close)); + if (gatewayClient != null) { + gatewayClient.stop().join(); + } + discovery.unsubscribe(GATEWAY_SERVER, this); + } + + @Override + public Connection getConnection(String hostAndPort) { + List connections = this.connections.get(hostAndPort); + if (connections == null || connections.isEmpty()) {//如果为空, 查询下zk, 做一次补偿, 防止zk丢消息 + synchronized (hostAndPort.intern()) {//同一个host要同步执行, 防止创建很多链接;一定要调用intern + connections = this.connections.get(hostAndPort); + if (connections == null || connections.isEmpty()) {//二次检查 + discovery.lookup(GATEWAY_SERVER) + .stream() + .filter(n -> hostAndPort.equals(n.hostAndPort())) + .forEach(this::syncAddConnection); + if (connections == null || connections.isEmpty()) {//如果还是没有链接, 就直接返null失败 + return null; + } + } + } + } + + int L = connections.size(); + + Connection connection; + if (L == 1) { + connection = connections.get(0); + } else { + connection = connections.get((int) (Math.random() * L % L)); + } + + if (connection.isConnected()) { + return connection; + } + + reconnect(connection, hostAndPort); + return getConnection(hostAndPort); + } + + @Override + public boolean send(String hostAndPort, Function creator, Consumer sender) { + Connection connection = getConnection(hostAndPort); + if (connection == null) return false;// gateway server 找不到,直接返回推送失败 + + sender.accept(creator.apply(connection)); + return true; + } + + @Override + public boolean broadcast(Function creator, Consumer sender) { + if (connections.isEmpty()) return false; + connections + .values() + .stream() + .filter(connections -> connections.size() > 0) + .forEach(connections -> sender.accept(creator.apply(connections.get(0)))); + return true; + } + + private void reconnect(Connection connection, String hostAndPort) { + HostAndPort h_p = HostAndPort.fromString(hostAndPort); + connections.get(hostAndPort).remove(connection); + connection.close(); + addConnection(h_p.getHost(), h_p.getPort(), false); + } + + private void removeClient(ServiceNode node) { + if (node != null) { + List clients = connections.remove(getHostAndPort(node.getHost(), node.getPort())); + if (clients != null) { + clients.forEach(Connection::close); + } + } + } + + private void asyncAddConnection(ServiceNode node) { + for (int i = 0; i < gateway_client_num; i++) { + addConnection(node.getHost(), node.getPort(), false); + } + } + + private void syncAddConnection(ServiceNode node) { + for (int i = 0; i < gateway_client_num; i++) { + addConnection(node.getHost(), node.getPort(), true); + } + } + + private void addConnection(String host, int port, boolean sync) { + ChannelFuture future = gatewayClient.connect(host, port); + future.channel().attr(attrKey).set(getHostAndPort(host, port)); + future.addListener(f -> { + if (!f.isSuccess()) { + logger.error("create gateway connection failure, host={}, port={}", host, port, f.cause()); + } + }); + if (sync) future.awaitUninterruptibly(); + } + + @Subscribe + @AllowConcurrentEvents + void on(ConnectionConnectEvent event) { + Connection connection = event.connection; + String hostAndPort = connection.getChannel().attr(attrKey).getAndSet(null); + if (hostAndPort == null) { + InetSocketAddress address = (InetSocketAddress) connection.getChannel().remoteAddress(); + hostAndPort = getHostAndPort(address.getAddress().getHostAddress(), address.getPort()); + } + connections.computeIfAbsent(hostAndPort, key -> new ArrayList<>(gateway_client_num)).add(connection); + logger.info("one gateway client connect success, hostAndPort={}, conn={}", hostAndPort, connection); + } + + private static String getHostAndPort(String host, int port) { + return host + ":" + port; + } + + public GatewayClient getGatewayClient() { + return gatewayClient; + } +} diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayUDPConnectionFactory.java b/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayUDPConnectionFactory.java new file mode 100644 index 00000000..5aaefb33 --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/gateway/connection/GatewayUDPConnectionFactory.java @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.gateway.connection; + +import com.google.common.collect.Maps; +import com.mpush.api.connection.Connection; +import com.mpush.api.service.Listener; +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.api.srd.ServiceNode; +import com.mpush.client.MPushClient; +import com.mpush.client.gateway.GatewayUDPConnector; +import com.mpush.common.message.BaseMessage; +import com.mpush.tools.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import static com.mpush.api.srd.ServiceNames.GATEWAY_SERVER; +import static com.mpush.tools.config.CC.mp.net.gateway_server_multicast; +import static com.mpush.tools.config.CC.mp.net.gateway_server_port; + +/** + * Created by yxx on 2016/5/17. + * + * @author ohun@live.cn + */ +public class GatewayUDPConnectionFactory extends GatewayConnectionFactory { + + private final Logger logger = LoggerFactory.getLogger(GatewayUDPConnectionFactory.class); + + private final Map ip_address = Maps.newConcurrentMap(); + + private final InetSocketAddress multicastRecipient = new InetSocketAddress(gateway_server_multicast, gateway_server_port); + + private final GatewayUDPConnector gatewayUDPConnector; + + public GatewayUDPConnectionFactory(MPushClient mPushClient) { + gatewayUDPConnector = new GatewayUDPConnector(mPushClient); + } + + @Override + protected void doStart(Listener listener) throws Throwable { + Utils.newThread("udp-client", () -> gatewayUDPConnector.start(listener)).start(); + ServiceDiscovery discovery = ServiceDiscoveryFactory.create(); + discovery.subscribe(GATEWAY_SERVER, this); + discovery.lookup(GATEWAY_SERVER).forEach(this::addConnection); + } + + @Override + public void onServiceAdded(String path, ServiceNode node) { + addConnection(node); + } + + @Override + public void onServiceUpdated(String path, ServiceNode node) { + addConnection(node); + } + + @Override + public void onServiceRemoved(String path, ServiceNode node) { + ip_address.remove(node.hostAndPort()); + logger.warn("Gateway Server zkNode={} was removed.", node); + } + + private void addConnection(ServiceNode node) { + ip_address.put(node.hostAndPort(), new InetSocketAddress(node.getHost(), node.getPort())); + } + + @Override + public void doStop(Listener listener) throws Throwable { + ip_address.clear(); + gatewayUDPConnector.stop(); + } + + @Override + public Connection getConnection(String hostAndPort) { + return gatewayUDPConnector.getConnection(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean send(String hostAndPort, Function creator, Consumer sender) { + InetSocketAddress recipient = ip_address.get(hostAndPort); + if (recipient == null) return false;// gateway server 找不到,直接返回推送失败 + + M message = creator.apply(gatewayUDPConnector.getConnection()); + message.setRecipient(recipient); + sender.accept(message); + return true; + } + + @Override + public boolean broadcast(Function creator, Consumer sender) { + M message = creator.apply(gatewayUDPConnector.getConnection()); + message.setRecipient(multicastRecipient); + sender.accept(message); + return true; + } + + public GatewayUDPConnector getGatewayUDPConnector() { + return gatewayUDPConnector; + } +} diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayClientChannelHandler.java b/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayClientChannelHandler.java new file mode 100644 index 00000000..395d574a --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayClientChannelHandler.java @@ -0,0 +1,87 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.gateway.handler; + + +import com.mpush.api.message.PacketReceiver; +import com.mpush.api.connection.Connection; +import com.mpush.api.connection.ConnectionManager; +import com.mpush.api.event.ConnectionCloseEvent; +import com.mpush.api.event.ConnectionConnectEvent; +import com.mpush.api.protocol.Packet; +import com.mpush.netty.connection.NettyConnection; +import com.mpush.tools.event.EventBus; +import com.mpush.tools.log.Logs; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by ohun on 2015/12/19. + * + * @author ohun@live.cn + */ +@ChannelHandler.Sharable +public final class GatewayClientChannelHandler extends ChannelInboundHandlerAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(GatewayClientChannelHandler.class); + + private final ConnectionManager connectionManager; + + private final PacketReceiver receiver; + + public GatewayClientChannelHandler(ConnectionManager connectionManager, PacketReceiver receiver) { + this.connectionManager = connectionManager; + this.receiver = receiver; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Logs.CONN.info("receive gateway packet={}, channel={}", msg, ctx.channel()); + Packet packet = (Packet) msg; + receiver.onReceive(packet, connectionManager.get(ctx.channel())); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + Connection connection = connectionManager.get(ctx.channel()); + Logs.CONN.error("client caught ex, conn={}", connection); + LOGGER.error("caught an ex, channel={}, conn={}", ctx.channel(), connection, cause); + ctx.close(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + Logs.CONN.info("client connected conn={}", ctx.channel()); + Connection connection = new NettyConnection(); + connection.init(ctx.channel(), false); + connectionManager.add(connection); + EventBus.post(new ConnectionConnectEvent(connection)); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + Connection connection = connectionManager.removeAndClose(ctx.channel()); + EventBus.post(new ConnectionCloseEvent(connection)); + Logs.CONN.info("client disconnected conn={}", connection); + } +} \ No newline at end of file diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayErrorHandler.java b/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayErrorHandler.java new file mode 100644 index 00000000..e86f4275 --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayErrorHandler.java @@ -0,0 +1,73 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.gateway.handler; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; +import com.mpush.api.protocol.Packet; +import com.mpush.client.MPushClient; +import com.mpush.client.push.PushRequest; +import com.mpush.client.push.PushRequestBus; +import com.mpush.common.handler.BaseMessageHandler; +import com.mpush.common.message.ErrorMessage; +import com.mpush.tools.log.Logs; + +import static com.mpush.common.ErrorCode.OFFLINE; +import static com.mpush.common.ErrorCode.PUSH_CLIENT_FAILURE; +import static com.mpush.common.ErrorCode.ROUTER_CHANGE; + +/** + * Created by ohun on 16/10/21. + * + * @author ohun@live.cn (夜色) + */ +public final class GatewayErrorHandler extends BaseMessageHandler { + + private final PushRequestBus pushRequestBus; + + public GatewayErrorHandler(MPushClient mPushClient) { + this.pushRequestBus = mPushClient.getPushRequestBus(); + } + + @Override + public ErrorMessage decode(Packet packet, Connection connection) { + return new ErrorMessage(packet, connection); + } + + @Override + public void handle(ErrorMessage message) { + if (message.cmd == Command.GATEWAY_PUSH.cmd) { + PushRequest request = pushRequestBus.getAndRemove(message.getSessionId()); + if (request == null) { + Logs.PUSH.warn("receive a gateway response, but request has timeout. message={}", message); + return; + } + + Logs.PUSH.warn("receive an error gateway response, message={}", message); + if (message.code == OFFLINE.errorCode) {//用户离线 + request.onOffline(); + } else if (message.code == PUSH_CLIENT_FAILURE.errorCode) {//下发到客户端失败 + request.onFailure(); + } else if (message.code == ROUTER_CHANGE.errorCode) {//用户路由信息更改 + request.onRedirect(); + } + } + } +} diff --git a/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayOKHandler.java b/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayOKHandler.java new file mode 100644 index 00000000..03a36207 --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/gateway/handler/GatewayOKHandler.java @@ -0,0 +1,64 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.gateway.handler; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; +import com.mpush.api.protocol.Packet; +import com.mpush.client.MPushClient; +import com.mpush.client.push.PushRequest; +import com.mpush.client.push.PushRequestBus; +import com.mpush.common.handler.BaseMessageHandler; +import com.mpush.common.message.OkMessage; +import com.mpush.common.push.GatewayPushResult; +import com.mpush.tools.log.Logs; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by ohun on 16/10/21. + * + * @author ohun@live.cn (夜色) + */ +public final class GatewayOKHandler extends BaseMessageHandler { + + private PushRequestBus pushRequestBus; + + public GatewayOKHandler(MPushClient mPushClient) { + this.pushRequestBus = mPushClient.getPushRequestBus(); + } + + @Override + public OkMessage decode(Packet packet, Connection connection) { + return new OkMessage(packet, connection); + } + + @Override + public void handle(OkMessage message) { + if (message.cmd == Command.GATEWAY_PUSH.cmd) { + PushRequest request = pushRequestBus.getAndRemove(message.getSessionId()); + if (request == null) { + Logs.PUSH.warn("receive a gateway response, but request has timeout. message={}", message); + return; + } + request.onSuccess(GatewayPushResult.fromJson(message.data));//推送成功 + } + } +} diff --git a/mpush-client/src/main/java/com/mpush/client/push/PushClient.java b/mpush-client/src/main/java/com/mpush/client/push/PushClient.java index 9b69df55..01288f09 100644 --- a/mpush-client/src/main/java/com/mpush/client/push/PushClient.java +++ b/mpush-client/src/main/java/com/mpush/client/push/PushClient.java @@ -19,91 +19,97 @@ package com.mpush.client.push; -import com.mpush.api.Constants; -import com.mpush.api.connection.Connection; +import com.mpush.api.MPushContext; +import com.mpush.api.push.PushContext; +import com.mpush.api.push.PushException; +import com.mpush.api.push.PushResult; import com.mpush.api.push.PushSender; import com.mpush.api.service.BaseService; import com.mpush.api.service.Listener; -import com.mpush.cache.redis.manager.RedisManager; -import com.mpush.client.gateway.GatewayClientFactory; -import com.mpush.common.router.ConnectionRouterManager; +import com.mpush.api.spi.common.CacheManagerFactory; +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.client.MPushClient; +import com.mpush.client.gateway.connection.GatewayConnectionFactory; +import com.mpush.common.router.CachedRemoteRouterManager; import com.mpush.common.router.RemoteRouter; -import com.mpush.zk.ZKClient; -import com.mpush.zk.listener.ZKServerNodeWatcher; -import java.util.Collection; -import java.util.Objects; import java.util.Set; import java.util.concurrent.FutureTask; -import static com.mpush.zk.ZKPath.GATEWAY_SERVER; +public final class PushClient extends BaseService implements PushSender { -/*package*/ class PushClient extends BaseService implements PushSender { - private static final int DEFAULT_TIMEOUT = 3000; - private final GatewayClientFactory factory = GatewayClientFactory.I; - private final ConnectionRouterManager routerManager = ConnectionRouterManager.I; + private MPushClient mPushClient; - public void send(String content, Collection userIds, Callback callback) { - send(content.getBytes(Constants.UTF_8), userIds, callback); - } + private PushRequestBus pushRequestBus; - @Override - public FutureTask send(String content, String userId, Callback callback) { - return send(content.getBytes(Constants.UTF_8), userId, callback); - } + private CachedRemoteRouterManager cachedRemoteRouterManager; - @Override - public void send(byte[] content, Collection userIds, Callback callback) { - for (String userId : userIds) { - send(content, userId, callback); + private GatewayConnectionFactory gatewayConnectionFactory; + + private FutureTask send0(PushContext ctx) { + if (ctx.isBroadcast()) { + return PushRequest.build(mPushClient, ctx).broadcast(); + } else { + Set remoteRouters = cachedRemoteRouterManager.lookupAll(ctx.getUserId()); + if (remoteRouters == null || remoteRouters.isEmpty()) { + return PushRequest.build(mPushClient, ctx).onOffline(); + } + FutureTask task = null; + for (RemoteRouter remoteRouter : remoteRouters) { + task = PushRequest.build(mPushClient, ctx).send(remoteRouter); + } + return task; } } @Override - public FutureTask send(byte[] content, String userId, Callback callback) { - Set routers = routerManager.lookupAll(userId); - if (routers == null || routers.isEmpty()) { - return PushRequest - .build(this) - .setCallback(callback) - .setUserId(userId) - .setContent(content) - .setTimeout(DEFAULT_TIMEOUT) - .offline(); - + public FutureTask send(PushContext ctx) { + if (ctx.isBroadcast()) { + return send0(ctx.setUserId(null)); + } else if (ctx.getUserId() != null) { + return send0(ctx); + } else if (ctx.getUserIds() != null) { + FutureTask task = null; + for (String userId : ctx.getUserIds()) { + task = send0(ctx.setUserId(userId)); + } + return task; + } else { + throw new PushException("param error."); } - FutureTask task = null; - for (RemoteRouter router : routers) { - task = PushRequest - .build(this) - .setCallback(callback) - .setUserId(userId) - .setContent(content) - .setTimeout(DEFAULT_TIMEOUT) - .send(router); - } - return task; - } - - public Connection getGatewayConnection(String host) { - return factory.getConnection(host); } @Override protected void doStart(Listener listener) throws Throwable { - ZKClient.I.start(listener); - RedisManager.I.init(); - ZKServerNodeWatcher.build(GATEWAY_SERVER, factory).beginWatch(); + if (mPushClient == null) { + mPushClient = new MPushClient(); + } + + pushRequestBus = mPushClient.getPushRequestBus(); + cachedRemoteRouterManager = mPushClient.getCachedRemoteRouterManager(); + gatewayConnectionFactory = mPushClient.getGatewayConnectionFactory(); + + ServiceDiscoveryFactory.create().syncStart(); + CacheManagerFactory.create().init(); + pushRequestBus.syncStart(); + gatewayConnectionFactory.start(listener); } @Override protected void doStop(Listener listener) throws Throwable { - factory.clear(); - ZKClient.I.stop(listener); + ServiceDiscoveryFactory.create().syncStop(); + CacheManagerFactory.create().destroy(); + pushRequestBus.syncStop(); + gatewayConnectionFactory.stop(listener); } @Override public boolean isRunning() { return started.get(); } + + @Override + public void setMPushContext(MPushContext context) { + this.mPushClient = ((MPushClient) context); + } } diff --git a/mpush-client/src/main/java/com/mpush/client/push/PushClientFactory.java b/mpush-client/src/main/java/com/mpush/client/push/PushClientFactory.java index 5e91639d..3fbc3a6c 100644 --- a/mpush-client/src/main/java/com/mpush/client/push/PushClientFactory.java +++ b/mpush-client/src/main/java/com/mpush/client/push/PushClientFactory.java @@ -20,23 +20,28 @@ package com.mpush.client.push; import com.mpush.api.push.PushSender; +import com.mpush.api.spi.Spi; import com.mpush.api.spi.client.PusherFactory; +import com.mpush.client.MPushClient; /** * Created by yxx on 2016/5/18. * * @author ohun@live.cn */ +@Spi public class PushClientFactory implements PusherFactory { - private static PushClient CLIENT; + private volatile PushClient client; @Override public PushSender get() { - if (CLIENT == null) { + if (client == null) { synchronized (PushClientFactory.class) { - CLIENT = new PushClient(); + if (client == null) { + client = new PushClient(); + } } } - return CLIENT; + return client; } } diff --git a/mpush-client/src/main/java/com/mpush/client/push/PushRequest.java b/mpush-client/src/main/java/com/mpush/client/push/PushRequest.java index 3df50970..76e761c5 100644 --- a/mpush-client/src/main/java/com/mpush/client/push/PushRequest.java +++ b/mpush-client/src/main/java/com/mpush/client/push/PushRequest.java @@ -19,16 +19,21 @@ package com.mpush.client.push; -import com.mpush.api.connection.Connection; -import com.mpush.api.push.PushSender; +import com.mpush.api.Constants; +import com.mpush.api.push.*; import com.mpush.api.router.ClientLocation; +import com.mpush.client.MPushClient; +import com.mpush.client.gateway.connection.GatewayConnectionFactory; import com.mpush.common.message.gateway.GatewayPushMessage; -import com.mpush.common.router.ConnectionRouterManager; +import com.mpush.common.push.GatewayPushResult; +import com.mpush.common.router.CachedRemoteRouterManager; import com.mpush.common.router.RemoteRouter; +import com.mpush.tools.Jsons; import com.mpush.tools.common.TimeLine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -40,94 +45,114 @@ * * @author ohun@live.cn */ -public class PushRequest extends FutureTask { +public final class PushRequest extends FutureTask { private static final Logger LOGGER = LoggerFactory.getLogger(PushRequest.class); - private static final Callable NONE = () -> Boolean.FALSE; + private static final Callable NONE = () -> new PushResult(PushResult.CODE_FAILURE); private enum Status {init, success, failure, offline, timeout} private final AtomicReference status = new AtomicReference<>(Status.init); private final TimeLine timeLine = new TimeLine("Push-Time-Line"); - private final PushClient client; + private final MPushClient mPushClient; - private PushSender.Callback callback; + private AckModel ackModel; + private Set tags; + private String condition; + private PushCallback callback; private String userId; private byte[] content; - private long timeout; + private int timeout; private ClientLocation location; + private int sessionId; + private String taskId; private Future future; + private PushResult result; - private void sendToConnServer(RemoteRouter router) { + private void sendToConnServer(RemoteRouter remoteRouter) { timeLine.addTimePoint("lookup-remote"); - if (router == null) { + if (remoteRouter != null) { + location = remoteRouter.getRouteValue(); + } + + if (remoteRouter == null || remoteRouter.isOffline()) { //1.1没有查到说明用户已经下线 offline(); return; } - timeLine.addTimePoint("get-gateway-conn"); - - + timeLine.addTimePoint("check-gateway-conn"); //2.通过网关连接,把消息发送到所在机器 - location = router.getRouteValue(); - Connection gatewayConn = client.getGatewayConnection(location.getHost()); - if (gatewayConn == null) { + boolean success = mPushClient.getGatewayConnectionFactory().send( + location.getHostAndPort(), + connection -> GatewayPushMessage + .build(connection) + .setUserId(userId) + .setContent(content) + .setClientType(location.getClientType()) + .setTimeout(timeout - 500) + .setTags(tags) + .addFlag(ackModel.flag) + , + pushMessage -> { + timeLine.addTimePoint("send-to-gateway-begin"); + pushMessage.sendRaw(f -> { + timeLine.addTimePoint("send-to-gateway-end"); + if (f.isSuccess()) { + LOGGER.debug("send to gateway server success, location={}, conn={}", location, f.channel()); + } else { + LOGGER.error("send to gateway server failure, location={}, conn={}", location, f.channel(), f.cause()); + failure(); + } + }); + PushRequest.this.content = null;//释放内存 + sessionId = pushMessage.getSessionId(); + future = mPushClient.getPushRequestBus().put(sessionId, PushRequest.this); + } + ); + + if (!success) { LOGGER.error("get gateway connection failure, location={}", location); failure(); - return; } - - timeLine.addTimePoint("send-to-gateway-begin"); - - GatewayPushMessage pushMessage = new GatewayPushMessage(userId, location.getClientType(), content, gatewayConn); - timeLine.addTimePoint("put-request-bus"); - future = PushRequestBus.I.put(pushMessage.getSessionId(), this); - - pushMessage.sendRaw(f -> { - timeLine.addTimePoint("send-to-gateway-end"); - if (f.isSuccess()) { - } else { - failure(); - } - }); } private void submit(Status status) { if (this.status.compareAndSet(Status.init, status)) {//防止重复调用 - if (future != null) future.cancel(true); - if (callback != null) { - PushRequestBus.I.asyncCall(this); - } else { - LOGGER.warn("callback is null"); + boolean isTimeoutEnd = status == Status.timeout;//任务是否超时结束 + + if (future != null && !isTimeoutEnd) {//是超时结束任务不用再取消一次 + future.cancel(true);//取消超时任务 + } + + this.timeLine.end();//结束时间流统计 + super.set(getResult());//设置同步调用的返回结果 + + if (callback != null) {//回调callback + if (isTimeoutEnd) {//超时结束时,当前线程已经是线程池里的线程,直接调用callback + callback.onResult(getResult()); + } else {//非超时结束时,当前线程为Netty线程池,要异步执行callback + mPushClient.getPushRequestBus().asyncCall(this);//会执行run方法 + } } - super.set(this.status.get() == Status.success); } - timeLine.end(); - LOGGER.info("push request {} end, userId={}, content={}, location={}, timeLine={}" - , status, userId, content, location, timeLine); + LOGGER.info("push request {} end, {}, {}, {}", status, userId, location, timeLine); } + /** + * run方法会有两个地方的线程调用 + * 1. 任务超时时会调用,见PushRequestBus.I.put(sessionId, PushRequest.this); + * 2. 异步执行callback的时候,见PushRequestBus.I.asyncCall(this); + */ @Override public void run() { - switch (status.get()) { - case success: - callback.onSuccess(userId, location); - break; - case failure: - callback.onFailure(userId, location); - break; - case offline: - callback.onOffline(userId, location); - break; - case timeout: - callback.onTimeout(userId, location); - break; - case init://从定时任务过来的,超时时间到了 - submit(Status.timeout); - break; + //判断任务是否超时,如果超时了此时状态是init,否则应该是其他状态, 因为从submit方法过来的状态都不是init + if (status.get() == Status.init) { + timeout(); + } else { + callback.onResult(getResult()); } } @@ -136,54 +161,139 @@ public boolean cancel(boolean mayInterruptIfRunning) { throw new UnsupportedOperationException(); } - public FutureTask send(RemoteRouter router) { + public FutureTask send(RemoteRouter router) { timeLine.begin(); sendToConnServer(router); return this; } - public void redirect() { - timeLine.addTimePoint("redirect"); - LOGGER.warn("user route has changed, userId={}, location={}", userId, location); - ConnectionRouterManager.I.invalidateLocalCache(userId); - if (status.get() == Status.init) {//表示任务还没有完成,还可以重新发送 - RemoteRouter route = ConnectionRouterManager.I.lookup(userId, location.getClientType()); - send(route); + public FutureTask broadcast() { + timeLine.begin(); + + boolean success = mPushClient.getGatewayConnectionFactory().broadcast( + connection -> GatewayPushMessage + .build(connection) + .setUserId(userId) + .setContent(content) + .setTags(tags) + .setCondition(condition) + .setTaskId(taskId) + .addFlag(ackModel.flag), + + pushMessage -> { + pushMessage.sendRaw(f -> { + if (f.isSuccess()) { + LOGGER.debug("send broadcast to gateway server success, userId={}, conn={}", userId, f.channel()); + } else { + failure(); + LOGGER.error("send broadcast to gateway server failure, userId={}, conn={}", userId, f.channel(), f.cause()); + } + }); + + if (pushMessage.taskId == null) { + sessionId = pushMessage.getSessionId(); + future = mPushClient.getPushRequestBus().put(sessionId, PushRequest.this); + } else { + success(); + } + } + ); + + if (!success) { + LOGGER.error("get gateway connection failure when broadcast."); + failure(); } + + return this; } - public FutureTask offline() { - ConnectionRouterManager.I.invalidateLocalCache(userId); + private void offline() { + mPushClient.getCachedRemoteRouterManager().invalidateLocalCache(userId); submit(Status.offline); - return this; } - public void timeout() { - submit(Status.timeout); + private void timeout() { + if (mPushClient.getPushRequestBus().getAndRemove(sessionId) != null) { + submit(Status.timeout); + } } - public void success() { + private void success() { submit(Status.success); } - public void failure() { + private void failure() { submit(Status.failure); } + public void onFailure() { + failure(); + } + + public void onRedirect() { + timeLine.addTimePoint("redirect"); + LOGGER.warn("user route has changed, userId={}, location={}", userId, location); + mPushClient.getCachedRemoteRouterManager().invalidateLocalCache(userId); + if (status.get() == Status.init) {//表示任务还没有完成,还可以重新发送 + RemoteRouter remoteRouter = mPushClient.getCachedRemoteRouterManager().lookup(userId, location.getClientType()); + send(remoteRouter); + } + } + + public FutureTask onOffline() { + offline(); + return this; + } + + public void onSuccess(GatewayPushResult result) { + if (result != null) timeLine.addTimePoints(result.timePoints); + submit(Status.success); + } + public long getTimeout() { return timeout; } - public PushRequest(PushClient client) { + public PushRequest(MPushClient mPushClient) { super(NONE); - this.client = client; + this.mPushClient = mPushClient; + } + + public static PushRequest build(MPushClient mPushClient, PushContext ctx) { + byte[] content = ctx.getContext(); + PushMsg msg = ctx.getPushMsg(); + if (msg != null) { + String json = Jsons.toJson(msg); + if (json != null) { + content = json.getBytes(Constants.UTF_8); + } + } + + Objects.requireNonNull(content, "push content can not be null."); + + return new PushRequest(mPushClient) + .setAckModel(ctx.getAckModel()) + .setUserId(ctx.getUserId()) + .setTags(ctx.getTags()) + .setCondition(ctx.getCondition()) + .setTaskId(ctx.getTaskId()) + .setContent(content) + .setTimeout(ctx.getTimeout()) + .setCallback(ctx.getCallback()); + } - public static PushRequest build(PushClient client) { - return new PushRequest(client); + private PushResult getResult() { + if (result == null) { + result = new PushResult(status.get().ordinal()) + .setUserId(userId) + .setLocation(location) + .setTimeLine(timeLine.getTimePoints()); + } + return result; } - public PushRequest setCallback(PushSender.Callback callback) { + public PushRequest setCallback(PushCallback callback) { this.callback = callback; return this; } @@ -198,15 +308,35 @@ public PushRequest setContent(byte[] content) { return this; } - public PushRequest setTimeout(long timeout) { + public PushRequest setTimeout(int timeout) { this.timeout = timeout; return this; } + public PushRequest setAckModel(AckModel ackModel) { + this.ackModel = ackModel; + return this; + } + + public PushRequest setTags(Set tags) { + this.tags = tags; + return this; + } + + public PushRequest setCondition(String condition) { + this.condition = condition; + return this; + } + + public PushRequest setTaskId(String taskId) { + this.taskId = taskId; + return this; + } + @Override public String toString() { return "PushRequest{" + - "content='" + content + '\'' + + "content='" + (content == null ? -1 : content.length) + '\'' + ", userId='" + userId + '\'' + ", timeout=" + timeout + ", location=" + location + diff --git a/mpush-client/src/main/java/com/mpush/client/push/PushRequestBus.java b/mpush-client/src/main/java/com/mpush/client/push/PushRequestBus.java index fbb846c0..8fef43de 100644 --- a/mpush-client/src/main/java/com/mpush/client/push/PushRequestBus.java +++ b/mpush-client/src/main/java/com/mpush/client/push/PushRequestBus.java @@ -19,35 +19,29 @@ package com.mpush.client.push; -import com.mpush.api.push.PushException; -import com.mpush.tools.thread.PoolThreadFactory; -import com.mpush.tools.thread.pool.ThreadPoolManager; -import io.netty.util.internal.chmv8.ConcurrentHashMapV8; +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; +import com.mpush.client.MPushClient; +import com.mpush.monitor.service.ThreadPoolManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.concurrent.*; -import static com.mpush.tools.thread.ThreadNames.T_PUSH_REQ_TIMER; - /** * Created by ohun on 2015/12/30. * * @author ohun@live.cn */ -public class PushRequestBus { - public static final PushRequestBus I = new PushRequestBus(); +public class PushRequestBus extends BaseService { private final Logger logger = LoggerFactory.getLogger(PushRequestBus.class); - private final Map reqQueue = new ConcurrentHashMapV8<>(1024); - private final Executor executor = ThreadPoolManager.I.getPushCallbackExecutor(); - private final ScheduledExecutorService scheduledExecutor; + private final Map reqQueue = new ConcurrentHashMap<>(1024); + private ScheduledExecutorService scheduledExecutor; + private final MPushClient mPushClient; - private PushRequestBus() { - scheduledExecutor = new ScheduledThreadPoolExecutor(1, new PoolThreadFactory(T_PUSH_REQ_TIMER), (r, e) -> { - logger.error("one push request was rejected, request=" + r); - throw new PushException("one push request was rejected. request=" + r); - }); + public PushRequestBus(MPushClient mPushClient) { + this.mPushClient = mPushClient; } public Future put(int sessionId, PushRequest request) { @@ -60,6 +54,20 @@ public PushRequest getAndRemove(int sessionId) { } public void asyncCall(Runnable runnable) { - executor.execute(runnable); + scheduledExecutor.execute(runnable); + } + + @Override + protected void doStart(Listener listener) throws Throwable { + scheduledExecutor = mPushClient.getThreadPoolManager().getPushClientTimer(); + listener.onSuccess(); + } + + @Override + protected void doStop(Listener listener) throws Throwable { + if (scheduledExecutor != null) { + scheduledExecutor.shutdown(); + } + listener.onSuccess(); } } diff --git a/mpush-client/src/main/java/com/mpush/client/user/UserStatusChangeListener.java b/mpush-client/src/main/java/com/mpush/client/user/UserStatusChangeListener.java new file mode 100644 index 00000000..b14f8c6c --- /dev/null +++ b/mpush-client/src/main/java/com/mpush/client/user/UserStatusChangeListener.java @@ -0,0 +1,51 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.client.user; + +import com.mpush.api.spi.common.MQClientFactory; +import com.mpush.api.spi.common.MQMessageReceiver; +import com.mpush.tools.Utils; +import com.mpush.tools.config.ConfigTools; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static com.mpush.api.event.Topics.OFFLINE_CHANNEL; +import static com.mpush.api.event.Topics.ONLINE_CHANNEL; + +/** + * Created by ohun on 2016/1/4. + * + * @author ohun@live.cn + */ +public class UserStatusChangeListener implements MQMessageReceiver { + + private static final Logger LOGGER = LoggerFactory.getLogger(UserStatusChangeListener.class); + + //只需要一台机器注册online、offline 消息通道 + public UserStatusChangeListener() { + MQClientFactory.create().subscribe(ONLINE_CHANNEL, this); + MQClientFactory.create().subscribe(OFFLINE_CHANNEL, this); + } + + @Override + public void receive(String channel, Object message) { + + } +} diff --git a/mpush-client/src/main/resources/META-INF/services/com.mpush.api.spi.common.ExecutorFactory b/mpush-client/src/main/resources/META-INF/services/com.mpush.api.spi.common.ExecutorFactory new file mode 100644 index 00000000..232a2bfe --- /dev/null +++ b/mpush-client/src/main/resources/META-INF/services/com.mpush.api.spi.common.ExecutorFactory @@ -0,0 +1 @@ +com.mpush.client.ClientExecutorFactory \ No newline at end of file diff --git a/mpush-common/pom.xml b/mpush-common/pom.xml index d97f639b..32e09191 100644 --- a/mpush-common/pom.xml +++ b/mpush-common/pom.xml @@ -4,34 +4,23 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml 4.0.0 - ${mpush.groupId} mpush-common - ${mpush.version} - mpush-common jar + mpush-common + MPUSH消息推送系统基础模块 + https://github.com/mpusher/mpush - ${mpush.groupId} - mpush-api - - - ${mpush.groupId} + ${project.groupId} mpush-tools - - ${mpush.groupId} - mpush-zk - - - ${mpush.groupId} - mpush-cache - diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisKey.java b/mpush-common/src/main/java/com/mpush/common/CacheKeys.java similarity index 50% rename from mpush-cache/src/main/java/com/mpush/cache/redis/RedisKey.java rename to mpush-common/src/main/java/com/mpush/common/CacheKeys.java index b4b82e8a..2e8baed6 100644 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/RedisKey.java +++ b/mpush-common/src/main/java/com/mpush/common/CacheKeys.java @@ -17,40 +17,40 @@ * ohun@live.cn (夜色) */ -package com.mpush.cache.redis; +package com.mpush.common; -public final class RedisKey { +public final class CacheKeys { - private static final String USER_PREFIX = "mp_uc_"; + private static final String USER_PREFIX = "mp:ur:";//用户路由 - private static final String SESSION_PREFIX = "mp_s_"; + private static final String SESSION_PREFIX = "mp:rs:";//可复用session - private static final String FAST_CONNECTION_DEVICE_PREFIX = "mp_f_c_d_"; + private static final String FAST_CONNECTION_DEVICE_PREFIX = "mp:fcd:"; - private static final String USER_ONLINE_KEY = "mp_u_ol_"; + private static final String ONLINE_USER_LIST_KEY_PREFIX = "mp:oul:";//在线用户列表 - private static final String CONN_NUM_ = "mp_cn_"; + public static final String SESSION_AES_KEY = "mp:sa"; + public static final String SESSION_AES_SEQ_KEY = "mp:sas"; + public static final String PUSH_TASK_PREFIX = "mp:pt"; - public static final String getUserKey(String userId) { + public static String getUserRouteKey(String userId) { return USER_PREFIX + userId; } - public static final String getSessionKey(String sessionId) { + public static String getSessionKey(String sessionId) { return SESSION_PREFIX + sessionId; } - //for fast connection test - public static final String getDeviceIdKey(String deviceId) { + public static String getDeviceIdKey(String deviceId) { return FAST_CONNECTION_DEVICE_PREFIX + deviceId; } - public static final String getUserOnlineKey(String extranetAddress) { - return USER_ONLINE_KEY + extranetAddress; + public static String getOnlineUserListKey(String publicIP) { + return ONLINE_USER_LIST_KEY_PREFIX + publicIP; } -// public static final String getConnNum(String extranetAddress) { -// return CONN_NUM_ + extranetAddress; -// } - + public static String getPushTaskKey(String taskId) { + return PUSH_TASK_PREFIX + taskId; + } } diff --git a/mpush-common/src/main/java/com/mpush/common/CommonExecutorFactory.java b/mpush-common/src/main/java/com/mpush/common/CommonExecutorFactory.java new file mode 100644 index 00000000..76def7e1 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/CommonExecutorFactory.java @@ -0,0 +1,97 @@ +/* + * (C) Copyright 2015-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common; + +import com.mpush.api.spi.common.ExecutorFactory; +import com.mpush.tools.config.CC; +import com.mpush.tools.log.Logs; +import com.mpush.tools.thread.NamedPoolThreadFactory; +import com.mpush.tools.thread.pool.DefaultExecutor; +import com.mpush.tools.thread.pool.DumpThreadRejectedHandler; +import com.mpush.tools.thread.pool.ThreadPoolConfig; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.mpush.tools.config.CC.mp.thread.pool.ack_timer; +import static com.mpush.tools.config.CC.mp.thread.pool.push_client; +import static com.mpush.tools.thread.ThreadNames.T_ARK_REQ_TIMER; +import static com.mpush.tools.thread.ThreadNames.T_EVENT_BUS; +import static com.mpush.tools.thread.ThreadNames.T_PUSH_CLIENT_TIMER; + +/** + * Created by ohun on 2017/7/15. + * + * @author ohun@live.cn (夜色) + */ +public class CommonExecutorFactory implements ExecutorFactory { + protected Executor get(ThreadPoolConfig config) { + String name = config.getName(); + int corePoolSize = config.getCorePoolSize(); + int maxPoolSize = config.getMaxPoolSize(); + int keepAliveSeconds = config.getKeepAliveSeconds(); + BlockingQueue queue = config.getQueue(); + + return new DefaultExecutor(corePoolSize + , maxPoolSize + , keepAliveSeconds + , TimeUnit.SECONDS + , queue + , new NamedPoolThreadFactory(name) + , new DumpThreadRejectedHandler(config)); + } + + @Override + public Executor get(String name) { + final ThreadPoolConfig config; + switch (name) { + case EVENT_BUS: + config = ThreadPoolConfig + .build(T_EVENT_BUS) + .setCorePoolSize(CC.mp.thread.pool.event_bus.min) + .setMaxPoolSize(CC.mp.thread.pool.event_bus.max) + .setKeepAliveSeconds(TimeUnit.SECONDS.toSeconds(10)) + .setQueueCapacity(CC.mp.thread.pool.event_bus.queue_size) + .setRejectedPolicy(ThreadPoolConfig.REJECTED_POLICY_CALLER_RUNS); + break; + case PUSH_CLIENT: { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(push_client + , new NamedPoolThreadFactory(T_PUSH_CLIENT_TIMER), (r, e) -> r.run() // run caller thread + ); + executor.setRemoveOnCancelPolicy(true); + return executor; + } + case ACK_TIMER: { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(ack_timer, + new NamedPoolThreadFactory(T_ARK_REQ_TIMER), + (r, e) -> Logs.PUSH.error("one ack context was rejected, context=" + r) + ); + executor.setRemoveOnCancelPolicy(true); + return executor; + } + default: + throw new IllegalArgumentException("no executor for " + name); + } + + return get(config); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/ErrorCode.java b/mpush-common/src/main/java/com/mpush/common/ErrorCode.java index 6bcc470f..81ef719b 100644 --- a/mpush-common/src/main/java/com/mpush/common/ErrorCode.java +++ b/mpush-common/src/main/java/com/mpush/common/ErrorCode.java @@ -28,6 +28,7 @@ public enum ErrorCode { OFFLINE(1, "user offline"), PUSH_CLIENT_FAILURE(2, "push to client failure"), ROUTER_CHANGE(3, "router change"), + ACK_TIMEOUT(4, "ack timeout"), DISPATCH_ERROR(100, "handle message error"), UNSUPPORTED_CMD(101, "unsupported command"), UNKNOWN(-1, "unknown"); diff --git a/mpush-common/src/main/java/com/mpush/common/MessageDispatcher.java b/mpush-common/src/main/java/com/mpush/common/MessageDispatcher.java index 4cfda9b0..06cd5e34 100644 --- a/mpush-common/src/main/java/com/mpush/common/MessageDispatcher.java +++ b/mpush-common/src/main/java/com/mpush/common/MessageDispatcher.java @@ -19,19 +19,21 @@ package com.mpush.common; -import com.mpush.api.MessageHandler; -import com.mpush.api.PacketReceiver; +import com.mpush.api.message.MessageHandler; +import com.mpush.api.message.PacketReceiver; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; import com.mpush.common.message.ErrorMessage; import com.mpush.tools.common.Profiler; +import com.mpush.tools.log.Logs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; import static com.mpush.common.ErrorCode.DISPATCH_ERROR; import static com.mpush.common.ErrorCode.UNSUPPORTED_CMD; @@ -42,23 +44,51 @@ * @author ohun@live.cn */ public final class MessageDispatcher implements PacketReceiver { - public static final Logger LOGGER = LoggerFactory.getLogger(MessageDispatcher.class); + public static final int POLICY_REJECT = 2; + public static final int POLICY_LOG = 1; + public static final int POLICY_IGNORE = 0; + private static final Logger LOGGER = LoggerFactory.getLogger(MessageDispatcher.class); private final Map handlers = new HashMap<>(); + private final int unsupportedPolicy; + + public MessageDispatcher() { + unsupportedPolicy = POLICY_REJECT; + } + + public MessageDispatcher(int unsupportedPolicy) { + this.unsupportedPolicy = unsupportedPolicy; + } public void register(Command command, MessageHandler handler) { handlers.put(command.cmd, handler); } + public void register(Command command, Supplier handlerSupplier, boolean enabled) { + if (enabled && !handlers.containsKey(command.cmd)) { + register(command, handlerSupplier.get()); + } + } + + public void register(Command command, Supplier handlerSupplier) { + this.register(command, handlerSupplier, true); + } + + public MessageHandler unRegister(Command command) { + return handlers.remove(command.cmd); + } + @Override public void onReceive(Packet packet, Connection connection) { MessageHandler handler = handlers.get(packet.cmd); if (handler != null) { + Profiler.enter("time cost on [dispatch]"); try { - Profiler.enter("start handle:" + handler.getClass().getSimpleName()); handler.handle(packet, connection); } catch (Throwable throwable) { LOGGER.error("dispatch message ex, packet={}, connect={}, body={}" , packet, connection, Arrays.toString(packet.body), throwable); + Logs.CONN.error("dispatch message ex, packet={}, connect={}, body={}, error={}" + , packet, connection, Arrays.toString(packet.body), throwable.getMessage()); ErrorMessage .from(packet, connection) .setErrorCode(DISPATCH_ERROR) @@ -67,12 +97,16 @@ public void onReceive(Packet packet, Connection connection) { Profiler.release(); } } else { - LOGGER.error("dispatch message failure unsupported cmd, packet={}, connect={}, body={}" - , packet, connection); - ErrorMessage - .from(packet, connection) - .setErrorCode(UNSUPPORTED_CMD) - .close(); + if (unsupportedPolicy > POLICY_IGNORE) { + Logs.CONN.error("dispatch message failure, cmd={} unsupported, packet={}, connect={}, body={}" + , Command.toCMD(packet.cmd), packet, connection); + if (unsupportedPolicy == POLICY_REJECT) { + ErrorMessage + .from(packet, connection) + .setErrorCode(UNSUPPORTED_CMD) + .close(); + } + } } } } diff --git a/mpush-common/src/main/java/com/mpush/common/ServerNodes.java b/mpush-common/src/main/java/com/mpush/common/ServerNodes.java new file mode 100644 index 00000000..9c102017 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/ServerNodes.java @@ -0,0 +1,65 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common; + +import com.mpush.api.srd.CommonServiceNode; +import com.mpush.api.srd.ServiceNames; +import com.mpush.api.srd.ServiceNode; +import com.mpush.tools.config.CC; +import com.mpush.tools.config.ConfigTools; + +import static com.mpush.api.srd.ServiceNames.ATTR_PUBLIC_IP; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public class ServerNodes { + + public static ServiceNode cs() { + CommonServiceNode node = new CommonServiceNode(); + node.setHost(ConfigTools.getConnectServerRegisterIp()); + node.setPort(CC.mp.net.connect_server_port); + node.setPersistent(false); + node.setServiceName(ServiceNames.CONN_SERVER); + node.setAttrs(CC.mp.net.connect_server_register_attr); + return node; + } + + public static ServiceNode ws() { + CommonServiceNode node = new CommonServiceNode(); + node.setHost(ConfigTools.getConnectServerRegisterIp()); + node.setPort(CC.mp.net.ws_server_port); + node.setPersistent(false); + node.setServiceName(ServiceNames.WS_SERVER); + //node.addAttr(ATTR_PUBLIC_IP, ConfigTools.getPublicIp()); + return node; + } + + public static ServiceNode gs() { + CommonServiceNode node = new CommonServiceNode(); + node.setHost(ConfigTools.getGatewayServerRegisterIp()); + node.setPort(CC.mp.net.gateway_server_port); + node.setPersistent(false); + node.setServiceName(ServiceNames.GATEWAY_SERVER); + return node; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/condition/AwaysPassCondition.java b/mpush-common/src/main/java/com/mpush/common/condition/AwaysPassCondition.java new file mode 100644 index 00000000..0e731577 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/condition/AwaysPassCondition.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.condition; + +import com.mpush.api.common.Condition; + +import java.util.Map; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class AwaysPassCondition implements Condition { + public static final Condition I = new AwaysPassCondition(); + + @Override + public boolean test(Map env) { + return true; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/condition/ScriptCondition.java b/mpush-common/src/main/java/com/mpush/common/condition/ScriptCondition.java new file mode 100644 index 00000000..22a8758a --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/condition/ScriptCondition.java @@ -0,0 +1,50 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.condition; + +import com.mpush.api.common.Condition; + +import javax.script.*; +import java.util.Map; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class ScriptCondition implements Condition { + private static final ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + private static final ScriptEngine jsEngine = scriptEngineManager.getEngineByName("js"); + + private final String script; + + public ScriptCondition(String script) { + this.script = script; + } + + @Override + public boolean test(Map env) { + try { + return (Boolean) jsEngine.eval(script, new SimpleBindings(env)); + } catch (Exception e) { + return false; + } + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/condition/TagsCondition.java b/mpush-common/src/main/java/com/mpush/common/condition/TagsCondition.java new file mode 100644 index 00000000..93b77313 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/condition/TagsCondition.java @@ -0,0 +1,45 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.condition; + +import com.mpush.api.common.Condition; + +import java.util.Map; +import java.util.Set; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class TagsCondition implements Condition { + private final Set tagList; + + public TagsCondition(Set tags) { + this.tagList = tags; + } + + @Override + public boolean test(Map env) { + //2.按标签过滤,目前只有include, 后续会增加exclude + String tags = (String) env.get("tags"); + return tags != null && tagList.stream().anyMatch(tags::contains); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/handler/BaseMessageHandler.java b/mpush-common/src/main/java/com/mpush/common/handler/BaseMessageHandler.java index 181e3d5c..4a47ba69 100644 --- a/mpush-common/src/main/java/com/mpush/common/handler/BaseMessageHandler.java +++ b/mpush-common/src/main/java/com/mpush/common/handler/BaseMessageHandler.java @@ -20,10 +20,10 @@ package com.mpush.common.handler; -import com.mpush.api.Message; -import com.mpush.api.MessageHandler; +import com.mpush.api.message.MessageHandler; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; +import com.mpush.common.message.BaseMessage; import com.mpush.tools.common.Profiler; /** @@ -31,20 +31,40 @@ * * @author ohun@live.cn */ -public abstract class BaseMessageHandler implements MessageHandler { +public abstract class BaseMessageHandler implements MessageHandler { + + + public abstract T decode(Packet packet, Connection connection); public abstract void handle(T message); public void handle(Packet packet, Connection connection) { + Profiler.enter("time cost on [message decode]"); T t = decode(packet, connection); + if (t != null) t.decodeBody(); + Profiler.release(); + if (t != null) { - try { - Profiler.enter("start handle"); - handle(t); - } finally { - Profiler.release(); - } + Profiler.enter("time cost on [handle]"); + handle(t); + Profiler.release(); } } + + /*@SuppressWarnings("unchecked") + private final Class mClass = Reflects.getSuperClassGenericType(this.getClass(), 0); + + protected T decode0(Packet packet, Connection connection) { + if (packet.hasFlag(Packet.FLAG_JSON_STRING_BODY)) { + String body = packet.getBody(); + T t = Jsons.fromJson(body, mClass); + if (t != null) { + t.setConnection(connection); + t.setPacket(packet); + } + return t; + } + return decode(packet, connection); + }*/ } diff --git a/mpush-common/src/main/java/com/mpush/common/memory/PacketFactory.java b/mpush-common/src/main/java/com/mpush/common/memory/PacketFactory.java new file mode 100644 index 00000000..9fbe30ce --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/memory/PacketFactory.java @@ -0,0 +1,40 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.memory; + +import com.mpush.api.protocol.Command; +import com.mpush.api.protocol.Packet; +import com.mpush.api.protocol.UDPPacket; +import com.mpush.tools.config.CC; + +/** + * Created by ohun on 16/10/22. + * + * @author ohun@live.cn (夜色) + */ +public interface PacketFactory { + PacketFactory FACTORY = CC.mp.net.udpGateway() ? UDPPacket::new : Packet::new; + + static Packet get(Command command) { + return FACTORY.create(command); + } + + Packet create(Command command); +} \ No newline at end of file diff --git a/mpush-common/src/main/java/com/mpush/common/message/AckMessage.java b/mpush-common/src/main/java/com/mpush/common/message/AckMessage.java new file mode 100644 index 00000000..61f452aa --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/message/AckMessage.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.message; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; +import com.mpush.api.protocol.Packet; +import io.netty.buffer.ByteBuf; + +/** + * Created by ohun on 16/9/5. + * + * @author ohun@live.cn (夜色) + */ +public final class AckMessage extends BaseMessage { + + + public AckMessage(Packet packet, Connection connection) { + super(packet, connection); + } + + @Override + public void decode(byte[] body) { + + } + + @Override + public byte[] encode() { + return null; + } + + + public static AckMessage from(BaseMessage src) { + return new AckMessage(new Packet(Command.ACK, src.getSessionId()), src.connection); + } + + @Override + public String toString() { + return "AckMessage{" + + "packet=" + packet + + '}'; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/message/BaseMessage.java b/mpush-common/src/main/java/com/mpush/common/message/BaseMessage.java index 8c3bd221..608265c2 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/BaseMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/BaseMessage.java @@ -19,14 +19,19 @@ package com.mpush.common.message; -import com.mpush.api.Message; +import com.mpush.api.message.Message; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; import com.mpush.api.protocol.Packet; +import com.mpush.tools.Jsons; import com.mpush.tools.common.IOUtils; +import com.mpush.tools.common.Profiler; import com.mpush.tools.config.CC; import io.netty.channel.ChannelFutureListener; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; /** @@ -35,41 +40,76 @@ * @author ohun@live.cn */ public abstract class BaseMessage implements Message { + private static final byte STATUS_DECODED = 1; + private static final byte STATUS_ENCODED = 2; private static final AtomicInteger ID_SEQ = new AtomicInteger(); - protected final Packet packet; - protected final Connection connection; + transient protected Packet packet; + transient protected Connection connection; + transient private byte status = 0; public BaseMessage(Packet packet, Connection connection) { this.packet = packet; this.connection = connection; - decodeBody(); } - protected void decodeBody() { - if (packet.body != null && packet.body.length > 0) { - //1.解密 - byte[] tmp = packet.body; - if (packet.hasFlag(Packet.FLAG_CRYPTO)) { - if (connection.getSessionContext().cipher != null) { - tmp = connection.getSessionContext().cipher.decrypt(tmp); + @Override + public void decodeBody() { + if ((status & STATUS_DECODED) == 0) { + status |= STATUS_DECODED; + + if (packet.getBodyLength() > 0) { + if (packet.hasFlag(Packet.FLAG_JSON_BODY)) { + decodeJsonBody0(); + } else { + decodeBinaryBody0(); } } - //2.解压 - if (packet.hasFlag(Packet.FLAG_COMPRESS)) { - tmp = IOUtils.decompress(tmp); + + } + } + + @Override + public void encodeBody() { + if ((status & STATUS_ENCODED) == 0) { + status |= STATUS_ENCODED; + + if (packet.hasFlag(Packet.FLAG_JSON_BODY)) { + encodeJsonBody0(); + } else { + encodeBinaryBody0(); } + } + + } - if (tmp.length == 0) { - throw new RuntimeException("message decode ex"); + private void decodeBinaryBody0() { + //1.解密 + byte[] tmp = packet.body; + if (packet.hasFlag(Packet.FLAG_CRYPTO)) { + if (connection.getSessionContext().cipher != null) { + tmp = connection.getSessionContext().cipher.decrypt(tmp); } + } + //2.解压 + if (packet.hasFlag(Packet.FLAG_COMPRESS)) { + tmp = IOUtils.decompress(tmp); + } - packet.body = tmp; - decode(packet.body); + if (tmp.length == 0) { + throw new RuntimeException("message decode ex"); } + + packet.body = tmp; + Profiler.enter("time cost on [body decode]"); + decode(packet.body); + Profiler.release(); + packet.body = null;// 释放内存 } - protected void encodeBody() { + private void encodeBinaryBody0() { + Profiler.enter("time cost on [body encode]"); byte[] tmp = encode(); + Profiler.release(); if (tmp != null && tmp.length > 0) { //1.压缩 if (tmp.length > CC.mp.core.compress_threshold) { @@ -93,12 +133,45 @@ protected void encodeBody() { } } + private void decodeJsonBody0() { + Map body = packet.getBody(); + decodeJsonBody(body); + } + + private void encodeJsonBody0() { + packet.setBody(encodeJsonBody()); + } + + private void encodeJsonStringBody0() { + packet.setBody(encodeJsonStringBody()); + } + + protected String encodeJsonStringBody() { + return Jsons.toJson(this); + } + + private void encodeBodyRaw() { + if ((status & STATUS_ENCODED) == 0) { + status |= STATUS_ENCODED; + + if (packet.hasFlag(Packet.FLAG_JSON_BODY)) { + encodeJsonBody0(); + } else { + packet.body = encode(); + } + } + } + public abstract void decode(byte[] body); public abstract byte[] encode(); - public Packet createResponse() { - return new Packet(packet.cmd, packet.sessionId); + protected void decodeJsonBody(Map body) { + + } + + protected Map encodeJsonBody() { + return null; } @Override @@ -119,7 +192,7 @@ public void send(ChannelFutureListener listener) { @Override public void sendRaw(ChannelFutureListener listener) { - packet.body = encode(); + encodeBodyRaw(); connection.send(packet, listener); } @@ -143,6 +216,27 @@ public int getSessionId() { return packet.sessionId; } + public BaseMessage setRecipient(InetSocketAddress recipient) { + packet.setRecipient(recipient); + return this; + } + + public void setPacket(Packet packet) { + this.packet = packet; + } + + public void setConnection(Connection connection) { + this.connection = connection; + } + + public ScheduledExecutorService getExecutor() { + return connection.getChannel().eventLoop(); + } + + public void runInRequestThread(Runnable runnable) { + connection.getChannel().eventLoop().execute(runnable); + } + @Override public abstract String toString(); } diff --git a/mpush-common/src/main/java/com/mpush/common/message/BindUserMessage.java b/mpush-common/src/main/java/com/mpush/common/message/BindUserMessage.java index 18a10f6a..aaa5df34 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/BindUserMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/BindUserMessage.java @@ -24,6 +24,8 @@ import com.mpush.api.protocol.Packet; import io.netty.buffer.ByteBuf; +import java.util.Map; + /** * Created by ohun on 2015/12/28. * @@ -31,8 +33,8 @@ */ public final class BindUserMessage extends ByteBufMessage { public String userId; - public String alias; public String tags; + public String data; public BindUserMessage(Connection connection) { super(new Packet(Command.BIND, genSessionId()), connection); @@ -45,21 +47,28 @@ public BindUserMessage(Packet message, Connection connection) { @Override public void decode(ByteBuf body) { userId = decodeString(body); - alias = decodeString(body); + data = decodeString(body); tags = decodeString(body); } @Override public void encode(ByteBuf body) { encodeString(body, userId); - encodeString(body, alias); + encodeString(body, data); encodeString(body, tags); } + @Override + public void decodeJsonBody(Map body) { + userId = (String) body.get("userId"); + tags = (String) body.get("tags"); + data = (String) body.get("data"); + } + @Override public String toString() { return "BindUserMessage{" + - "alias='" + alias + '\'' + + "data='" + data + '\'' + ", userId='" + userId + '\'' + ", tags='" + tags + '\'' + ", packet=" + packet + diff --git a/mpush-common/src/main/java/com/mpush/common/message/ByteBufMessage.java b/mpush-common/src/main/java/com/mpush/common/message/ByteBufMessage.java index bb7ecb83..d149ddf9 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/ByteBufMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/ByteBufMessage.java @@ -43,11 +43,15 @@ public void decode(byte[] body) { @Override public byte[] encode() { - ByteBuf body = Unpooled.buffer(); - encode(body); - byte[] bytes = new byte[body.readableBytes()]; - body.readBytes(bytes); - return bytes; + ByteBuf body = connection.getChannel().alloc().heapBuffer(); + try { + encode(body); + byte[] bytes = new byte[body.readableBytes()]; + body.readBytes(bytes); + return bytes; + } finally { + body.release(); + } } public abstract void decode(ByteBuf body); diff --git a/mpush-common/src/main/java/com/mpush/common/message/ErrorMessage.java b/mpush-common/src/main/java/com/mpush/common/message/ErrorMessage.java index 9126e3aa..90a4f794 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/ErrorMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/ErrorMessage.java @@ -25,6 +25,9 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFutureListener; +import java.util.HashMap; +import java.util.Map; + import static com.mpush.api.protocol.Command.ERROR; /** @@ -63,13 +66,22 @@ public void encode(ByteBuf body) { encodeString(body, data); } + @Override + protected Map encodeJsonBody() { + Map body = new HashMap<>(4); + if (cmd > 0) body.put("cmd", cmd); + if (code > 0) body.put("code", code); + if (reason != null) body.put("reason", reason); + if (data != null) body.put("data", data); + return body; + } + public static ErrorMessage from(BaseMessage src) { - return new ErrorMessage(src.packet.cmd, new Packet(ERROR - , src.packet.sessionId), src.connection); + return new ErrorMessage(src.packet.cmd, src.packet.response(ERROR), src.connection); } public static ErrorMessage from(Packet src, Connection connection) { - return new ErrorMessage(src.cmd, new Packet(ERROR, src.sessionId), connection); + return new ErrorMessage(src.cmd, src.response(ERROR), connection); } diff --git a/mpush-common/src/main/java/com/mpush/common/message/FastConnectOkMessage.java b/mpush-common/src/main/java/com/mpush/common/message/FastConnectOkMessage.java index 917f206c..85cb9e29 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/FastConnectOkMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/FastConnectOkMessage.java @@ -20,9 +20,12 @@ package com.mpush.common.message; import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; import io.netty.buffer.ByteBuf; +import static com.mpush.api.protocol.Command.FAST_CONNECT; + /** * Created by ohun on 2015/12/28. * @@ -36,7 +39,7 @@ public FastConnectOkMessage(Packet message, Connection connection) { } public static FastConnectOkMessage from(BaseMessage src) { - return new FastConnectOkMessage(src.createResponse(), src.connection); + return new FastConnectOkMessage(src.packet.response(FAST_CONNECT), src.connection); } @Override diff --git a/mpush-common/src/main/java/com/mpush/common/message/HandshakeMessage.java b/mpush-common/src/main/java/com/mpush/common/message/HandshakeMessage.java index 02d20c5b..cabd2ac4 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/HandshakeMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/HandshakeMessage.java @@ -24,6 +24,7 @@ import io.netty.buffer.ByteBuf; import java.util.Arrays; +import java.util.Map; import static com.mpush.api.protocol.Command.HANDSHAKE; @@ -76,6 +77,14 @@ public void encode(ByteBuf body) { encodeLong(body, timestamp); } + @Override + public void decodeJsonBody(Map body) { + deviceId = (String) body.get("deviceId"); + osName = (String) body.get("osName"); + osVersion = (String) body.get("osVersion"); + clientVersion = (String) body.get("clientVersion"); + } + @Override public String toString() { return "HandshakeMessage{" + diff --git a/mpush-common/src/main/java/com/mpush/common/message/HandshakeOkMessage.java b/mpush-common/src/main/java/com/mpush/common/message/HandshakeOkMessage.java index 26fa9ca3..fe9ef9af 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/HandshakeOkMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/HandshakeOkMessage.java @@ -20,11 +20,14 @@ package com.mpush.common.message; import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; import io.netty.buffer.ByteBuf; import java.util.Arrays; +import static com.mpush.api.protocol.Command.HANDSHAKE; + /** * Created by ohun on 2015/12/27. * @@ -57,7 +60,7 @@ public void encode(ByteBuf body) { } public static HandshakeOkMessage from(BaseMessage src) { - return new HandshakeOkMessage(src.createResponse(), src.connection); + return new HandshakeOkMessage(src.packet.response(HANDSHAKE), src.connection); } public HandshakeOkMessage setServerKey(byte[] serverKey) { diff --git a/mpush-common/src/main/java/com/mpush/common/message/HttpRequestMessage.java b/mpush-common/src/main/java/com/mpush/common/message/HttpRequestMessage.java index 0102f5c4..bd93b1a5 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/HttpRequestMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/HttpRequestMessage.java @@ -32,7 +32,7 @@ * * @author ohun@live.cn */ -public class HttpRequestMessage extends ByteBufMessage { +public final class HttpRequestMessage extends ByteBufMessage { public byte method; public String uri; public Map headers; diff --git a/mpush-common/src/main/java/com/mpush/common/message/HttpResponseMessage.java b/mpush-common/src/main/java/com/mpush/common/message/HttpResponseMessage.java index 4fde32d6..6185b5b0 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/HttpResponseMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/HttpResponseMessage.java @@ -20,6 +20,7 @@ package com.mpush.common.message; import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; import com.mpush.tools.Utils; import io.netty.buffer.ByteBuf; @@ -27,12 +28,14 @@ import java.util.HashMap; import java.util.Map; +import static com.mpush.api.protocol.Command.HTTP_PROXY; + /** * Created by ohun on 2016/2/15. * * @author ohun@live.cn */ -public class HttpResponseMessage extends ByteBufMessage { +public final class HttpResponseMessage extends ByteBufMessage { public int statusCode; public String reasonPhrase; public Map headers = new HashMap<>(); @@ -59,7 +62,7 @@ public void encode(ByteBuf body) { } public static HttpResponseMessage from(HttpRequestMessage src) { - return new HttpResponseMessage(src.createResponse(), src.connection); + return new HttpResponseMessage(src.packet.response(HTTP_PROXY), src.connection); } public HttpResponseMessage setStatusCode(int statusCode) { diff --git a/mpush-common/src/main/java/com/mpush/common/message/KickUserMessage.java b/mpush-common/src/main/java/com/mpush/common/message/KickUserMessage.java index 15571038..3e4216e4 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/KickUserMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/KickUserMessage.java @@ -20,10 +20,15 @@ package com.mpush.common.message; import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.JsonPacket; import com.mpush.api.protocol.Packet; import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; + import static com.mpush.api.protocol.Command.KICK; +import static com.mpush.api.protocol.Command.PUSH; /** * Created by ohun on 2015/12/29. @@ -34,14 +39,18 @@ public class KickUserMessage extends ByteBufMessage { public String deviceId; public String userId; - public KickUserMessage(Connection connection) { - super(new Packet(KICK), connection); - } - public KickUserMessage(Packet message, Connection connection) { super(message, connection); } + public static KickUserMessage build(Connection connection) { + if (connection.getSessionContext().isSecurity()) { + return new KickUserMessage(new Packet(KICK), connection); + } else { + return new KickUserMessage(new JsonPacket(KICK), connection); + } + } + @Override public void decode(ByteBuf body) { deviceId = decodeString(body); @@ -54,6 +63,14 @@ public void encode(ByteBuf body) { encodeString(body, userId); } + @Override + protected Map encodeJsonBody() { + Map body = new HashMap<>(2); + body.put("deviceId", deviceId); + body.put("userId", userId); + return body; + } + @Override public String toString() { return "KickUserMessage{" + diff --git a/mpush-common/src/main/java/com/mpush/common/message/OkMessage.java b/mpush-common/src/main/java/com/mpush/common/message/OkMessage.java index 5edcb2d9..f3b4788a 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/OkMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/OkMessage.java @@ -23,6 +23,9 @@ import com.mpush.api.protocol.Packet; import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; + import static com.mpush.api.protocol.Command.OK; /** @@ -58,9 +61,17 @@ public void encode(ByteBuf body) { encodeString(body, data); } + @Override + public Map encodeJsonBody() { + Map body = new HashMap<>(3); + if (cmd > 0) body.put("cmd", cmd); + if (code > 0) body.put("code", code); + if (data != null) body.put("data", data); + return body; + } + public static OkMessage from(BaseMessage src) { - return new OkMessage(src.packet.cmd, new Packet(OK - , src.packet.sessionId), src.connection); + return new OkMessage(src.packet.cmd, src.packet.response(OK), src.connection); } public OkMessage setCode(byte code) { diff --git a/mpush-common/src/main/java/com/mpush/common/message/PushMessage.java b/mpush-common/src/main/java/com/mpush/common/message/PushMessage.java index b93a252b..84daf0f9 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/PushMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/PushMessage.java @@ -21,9 +21,13 @@ import com.mpush.api.Constants; import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.JsonPacket; import com.mpush.api.protocol.Packet; +import io.netty.channel.ChannelFutureListener; -import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import static com.mpush.api.protocol.Command.PUSH; @@ -40,9 +44,12 @@ public PushMessage(Packet packet, Connection connection) { super(packet, connection); } - public PushMessage(byte[] content, Connection connection) { - super(new Packet(PUSH, genSessionId()), connection); - this.content = content; + public static PushMessage build(Connection connection) { + if (connection.getSessionContext().isSecurity()) { + return new PushMessage(new Packet(PUSH, genSessionId()), connection); + } else { + return new PushMessage(new JsonPacket(PUSH, genSessionId()), connection); + } } @Override @@ -55,6 +62,43 @@ public byte[] encode() { return content; } + @Override + public void decodeJsonBody(Map body) { + String content = (String) body.get("content"); + if (content != null) { + this.content = content.getBytes(Constants.UTF_8); + } + } + + @Override + public Map encodeJsonBody() { + if (content != null) { + return Collections.singletonMap("content", new String(content, Constants.UTF_8)); + } + return null; + } + + public boolean autoAck() { + return packet.hasFlag(Packet.FLAG_AUTO_ACK); + } + + public boolean needAck() { + return packet.hasFlag(Packet.FLAG_BIZ_ACK) || packet.hasFlag(Packet.FLAG_AUTO_ACK); + } + + public PushMessage setContent(byte[] content) { + this.content = content; + return this; + } + + + + @Override + public void send(ChannelFutureListener listener) { + super.send(listener); + this.content = null;//释放内存 + } + @Override public String toString() { return "PushMessage{" + diff --git a/mpush-common/src/main/java/com/mpush/common/message/gateway/GatewayKickUserMessage.java b/mpush-common/src/main/java/com/mpush/common/message/gateway/GatewayKickUserMessage.java new file mode 100644 index 00000000..ba620867 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/message/gateway/GatewayKickUserMessage.java @@ -0,0 +1,146 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.message.gateway; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Packet; +import com.mpush.common.memory.PacketFactory; +import com.mpush.common.message.ByteBufMessage; +import com.mpush.common.router.KickRemoteMsg; +import io.netty.buffer.ByteBuf; + +import static com.mpush.api.protocol.Command.GATEWAY_KICK; + +/** + * Created by ohun on 16/10/23. + * + * @author ohun@live.cn (夜色) + */ +public final class GatewayKickUserMessage extends ByteBufMessage implements KickRemoteMsg { + public String userId; + public String deviceId; + public String connId; + public int clientType; + public String targetServer; + public int targetPort; + + + public GatewayKickUserMessage(Packet message, Connection connection) { + super(message, connection); + } + + public static GatewayKickUserMessage build(Connection connection) { + Packet packet = PacketFactory.get(GATEWAY_KICK); + packet.sessionId = genSessionId(); + return new GatewayKickUserMessage(packet, connection); + } + + @Override + public void decode(ByteBuf body) { + userId = decodeString(body); + deviceId = decodeString(body); + connId = decodeString(body); + clientType = decodeInt(body); + targetServer = decodeString(body); + targetPort = decodeInt(body); + } + + @Override + public void encode(ByteBuf body) { + encodeString(body, userId); + encodeString(body, deviceId); + encodeString(body, connId); + encodeInt(body, clientType); + encodeString(body, targetServer); + encodeInt(body, targetPort); + } + + public GatewayKickUserMessage setUserId(String userId) { + this.userId = userId; + return this; + } + + public GatewayKickUserMessage setDeviceId(String deviceId) { + this.deviceId = deviceId; + return this; + } + + public GatewayKickUserMessage setConnId(String connId) { + this.connId = connId; + return this; + } + + public GatewayKickUserMessage setClientType(int clientType) { + this.clientType = clientType; + return this; + } + + public GatewayKickUserMessage setTargetServer(String targetServer) { + this.targetServer = targetServer; + return this; + } + + public GatewayKickUserMessage setTargetPort(int targetPort) { + this.targetPort = targetPort; + return this; + } + + @Override + public String getUserId() { + return userId; + } + + @Override + public String getDeviceId() { + return deviceId; + } + + @Override + public String getConnId() { + return connId; + } + + @Override + public int getClientType() { + return clientType; + } + + @Override + public String getTargetServer() { + return targetServer; + } + + @Override + public int getTargetPort() { + return targetPort; + } + + @Override + public String toString() { + return "GatewayKickUserMessage{" + + "userId='" + userId + '\'' + + ", deviceId='" + deviceId + '\'' + + ", connId='" + connId + '\'' + + ", clientType=" + clientType + + ", targetServer='" + targetServer + '\'' + + ", targetPort=" + targetPort + + '}'; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/message/gateway/GatewayPushMessage.java b/mpush-common/src/main/java/com/mpush/common/message/gateway/GatewayPushMessage.java index ead4f160..eaa5b21b 100644 --- a/mpush-common/src/main/java/com/mpush/common/message/gateway/GatewayPushMessage.java +++ b/mpush-common/src/main/java/com/mpush/common/message/gateway/GatewayPushMessage.java @@ -19,12 +19,20 @@ package com.mpush.common.message.gateway; +import com.alibaba.fastjson.TypeReference; +import com.mpush.api.common.Condition; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; +import com.mpush.api.spi.push.IPushMessage; +import com.mpush.common.condition.*; +import com.mpush.common.memory.PacketFactory; import com.mpush.common.message.ByteBufMessage; +import com.mpush.tools.Jsons; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFutureListener; +import java.util.Set; + import static com.mpush.api.protocol.Command.GATEWAY_PUSH; /** @@ -32,34 +40,157 @@ * * @author ohun@live.cn */ -public class GatewayPushMessage extends ByteBufMessage { +public final class GatewayPushMessage extends ByteBufMessage implements IPushMessage { public String userId; public int clientType; + public int timeout; public byte[] content; - public GatewayPushMessage(String userId, int clientType, byte[] content, Connection connection) { - super(new Packet(GATEWAY_PUSH, genSessionId()), connection); - this.userId = userId; - this.clientType = clientType; - this.content = content; - } + public String taskId; + public Set tags; + public String condition; public GatewayPushMessage(Packet message, Connection connection) { super(message, connection); } + public static GatewayPushMessage build(Connection connection) { + Packet packet = PacketFactory.get(GATEWAY_PUSH); + packet.sessionId = genSessionId(); + return new GatewayPushMessage(packet, connection); + } + @Override public void decode(ByteBuf body) { userId = decodeString(body); clientType = decodeInt(body); + timeout = decodeInt(body); content = decodeBytes(body); + taskId = decodeString(body); + tags = decodeSet(body); + condition = decodeString(body); } @Override public void encode(ByteBuf body) { encodeString(body, userId); encodeInt(body, clientType); + encodeInt(body, timeout); encodeBytes(body, content); + encodeString(body, taskId); + encodeSet(body, tags); + encodeString(body, condition); + } + + private Set decodeSet(ByteBuf body) { + String json = decodeString(body); + if (json == null) return null; + return Jsons.fromJson(json, new TypeReference>() { + }.getType()); + } + + private void encodeSet(ByteBuf body, Set field) { + String json = field == null ? null : Jsons.toJson(field); + encodeString(body, json); + } + + public GatewayPushMessage setUserId(String userId) { + this.userId = userId; + return this; + } + + public GatewayPushMessage setContent(byte[] content) { + this.content = content; + return this; + } + + public GatewayPushMessage setClientType(int clientType) { + this.clientType = clientType; + return this; + } + + public GatewayPushMessage addFlag(byte flag) { + packet.addFlag(flag); + return this; + } + + public GatewayPushMessage setTimeout(int timeout) { + this.timeout = timeout; + return this; + } + + public GatewayPushMessage setTags(Set tags) { + this.tags = tags; + return this; + } + + public GatewayPushMessage setCondition(String condition) { + this.condition = condition; + return this; + } + + public GatewayPushMessage setTaskId(String taskId) { + this.taskId = taskId; + return this; + } + + @Override + public boolean isBroadcast() { + return userId == null; + } + + @Override + public String getUserId() { + return userId; + } + + @Override + public int getClientType() { + return clientType; + } + + @Override + public int getTimeoutMills() { + return timeout; + } + + @Override + public String getTaskId() { + return taskId; + } + + @Override + public byte[] getContent() { + return content; + } + + @Override + public boolean isNeedAck() { + return packet.hasFlag(Packet.FLAG_BIZ_ACK) || packet.hasFlag(Packet.FLAG_AUTO_ACK); + } + + @Override + public byte getFlags() { + return packet.flags; + } + + @Override + public Condition getCondition() { + if (condition != null) { + return new ScriptCondition(condition); + } + if (tags != null) { + return new TagsCondition(tags); + } + return AwaysPassCondition.I; + } + + + @Override + public void finalized() { + this.content = null; + this.condition = null; + this.tags = null; } @Override @@ -76,8 +207,9 @@ public void send(ChannelFutureListener listener) { public String toString() { return "GatewayPushMessage{" + "userId='" + userId + '\'' + - "clientType='" + clientType + '\'' + - ", content='" + content.length + '\'' + + ", clientType='" + clientType + '\'' + + ", timeout='" + timeout + '\'' + + ", content='" + (content == null ? 0 : content.length) + '\'' + '}'; } } diff --git a/mpush-common/src/main/java/com/mpush/common/net/HttpProxyDnsMappingManager.java b/mpush-common/src/main/java/com/mpush/common/net/HttpProxyDnsMappingManager.java index f1ad04ea..0a6170a7 100644 --- a/mpush-common/src/main/java/com/mpush/common/net/HttpProxyDnsMappingManager.java +++ b/mpush-common/src/main/java/com/mpush/common/net/HttpProxyDnsMappingManager.java @@ -23,26 +23,35 @@ import com.google.common.collect.Maps; import com.mpush.api.service.BaseService; import com.mpush.api.service.Listener; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.ServiceDiscoveryFactory; import com.mpush.api.spi.net.DnsMapping; import com.mpush.api.spi.net.DnsMappingManager; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.api.srd.ServiceListener; +import com.mpush.api.srd.ServiceNode; import com.mpush.tools.Jsons; import com.mpush.tools.config.CC; +import com.mpush.tools.thread.NamedPoolThreadFactory; +import com.mpush.tools.thread.ThreadNames; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import static com.mpush.api.srd.ServiceNames.DNS_MAPPING; import static com.mpush.tools.Utils.checkHealth; -public class HttpProxyDnsMappingManager extends BaseService implements DnsMappingManager, Runnable { +@Spi(order = 1) +public class HttpProxyDnsMappingManager extends BaseService implements DnsMappingManager, Runnable, ServiceListener { private final Logger logger = LoggerFactory.getLogger(HttpProxyDnsMappingManager.class); - public HttpProxyDnsMappingManager() { - } + protected final Map> mappings = Maps.newConcurrentMap(); private final Map> all = Maps.newConcurrentMap(); private Map> available = Maps.newConcurrentMap(); @@ -51,10 +60,17 @@ public HttpProxyDnsMappingManager() { @Override protected void doStart(Listener listener) throws Throwable { + ServiceDiscovery discovery = ServiceDiscoveryFactory.create(); + discovery.subscribe(DNS_MAPPING, this); + discovery.lookup(DNS_MAPPING).forEach(this::add); + if (all.size() > 0) { - executorService = Executors.newSingleThreadScheduledExecutor(); + executorService = Executors.newSingleThreadScheduledExecutor( + new NamedPoolThreadFactory(ThreadNames.T_HTTP_DNS_TIMER) + ); executorService.scheduleAtFixedRate(this, 1, 20, TimeUnit.SECONDS); //20秒 定时扫描dns } + listener.onSuccess(); } @Override @@ -62,14 +78,13 @@ protected void doStop(Listener listener) throws Throwable { if (executorService != null) { executorService.shutdown(); } + listener.onSuccess(); } @Override public void init() { - logger.error("start init dnsMapping"); all.putAll(CC.mp.http.dns_mapping); available.putAll(CC.mp.http.dns_mapping); - logger.error("end init dnsMapping"); } @Override @@ -86,8 +101,13 @@ public Map> getAll() { } public DnsMapping lookup(String origin) { - if (available.isEmpty()) return null; - List list = available.get(origin); + List list = mappings.get(origin); + + if (list == null || list.isEmpty()) { + if (available.isEmpty()) return null; + list = available.get(origin); + } + if (list == null || list.isEmpty()) return null; int L = list.size(); if (L == 1) return list.get(0); @@ -96,23 +116,41 @@ public DnsMapping lookup(String origin) { @Override public void run() { - logger.debug("start dns mapping checkHealth"); + logger.debug("do dns mapping checkHealth ..."); Map> all = this.getAll(); Map> available = Maps.newConcurrentMap(); - for (Map.Entry> entry : all.entrySet()) { - String key = entry.getKey(); - List value = entry.getValue(); - List nowValue = Lists.newArrayList(); - for (DnsMapping temp : value) { - boolean isOk = checkHealth(temp.getIp(), temp.getPort()); - if (isOk) { - nowValue.add(temp); + all.forEach((key, dnsMappings) -> { + List okList = Lists.newArrayList(); + dnsMappings.forEach(dnsMapping -> { + if (checkHealth(dnsMapping.getIp(), dnsMapping.getPort())) { + okList.add(dnsMapping); } else { - logger.error("dns can not reachable:" + Jsons.toJson(temp)); + logger.warn("dns can not reachable:" + Jsons.toJson(dnsMapping)); } - } - available.put(key, nowValue); - } + }); + available.put(key, okList); + }); this.update(available); } + + @Override + public void onServiceAdded(String path, ServiceNode node) { + add(node); + } + + @Override + public void onServiceUpdated(String path, ServiceNode node) { + add(node); + } + + @Override + public void onServiceRemoved(String path, ServiceNode node) { + mappings.computeIfAbsent(node.getAttr("origin"), k -> new ArrayList<>()) + .remove(new DnsMapping(node.getHost(), node.getPort())); + } + + private void add(ServiceNode node){ + mappings.computeIfAbsent(node.getAttr("origin"), k -> new ArrayList<>()) + .add(new DnsMapping(node.getHost(), node.getPort())); + } } diff --git a/mpush-common/src/main/java/com/mpush/common/push/GatewayPushResult.java b/mpush-common/src/main/java/com/mpush/common/push/GatewayPushResult.java new file mode 100644 index 00000000..8e921602 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/push/GatewayPushResult.java @@ -0,0 +1,52 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.push; + +import com.mpush.common.message.gateway.GatewayPushMessage; +import com.mpush.tools.Jsons; + +/** + * Created by ohun on 2016/12/29. + * + * @author ohun@live.cn (夜色) + */ +public final class GatewayPushResult { + public String userId; + public Integer clientType; + public Object[] timePoints; + + public GatewayPushResult() { + } + + public GatewayPushResult(String userId, Integer clientType, Object[] timePoints) { + this.userId = userId; + this.timePoints = timePoints; + if (clientType > 0) this.clientType = clientType; + } + + public static String toJson(GatewayPushMessage message, Object[] timePoints) { + return Jsons.toJson(new GatewayPushResult(message.userId, message.clientType, timePoints)); + } + + public static GatewayPushResult fromJson(String json) { + if (json == null) return null; + return Jsons.fromJson(json, GatewayPushResult.class); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/push/RedisBroadcastController.java b/mpush-common/src/main/java/com/mpush/common/push/RedisBroadcastController.java new file mode 100644 index 00000000..af2a3bbe --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/push/RedisBroadcastController.java @@ -0,0 +1,103 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.push; + +import com.mpush.api.push.BroadcastController; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; +import com.mpush.common.CacheKeys; + +import java.util.List; + +/** + * Created by ohun on 16/10/25. + * + * @author ohun@live.cn (夜色) + */ +public final class RedisBroadcastController implements BroadcastController { + private static final String TASK_DONE_FIELD = "d"; + private static final String TASK_SEND_COUNT_FIELD = "sc"; + private static final String TASK_CANCEL_FIELD = "c"; + private static final String TASK_QPS_FIELD = "q"; + private static final String TASK_SUCCESS_USER_ID = "sui"; + + private static CacheManager cacheManager = CacheManagerFactory.create(); + + private final String taskId; + private final String taskKey; + private final String taskSuccessUIDKey; + + public RedisBroadcastController(String taskId) { + this.taskId = taskId; + this.taskKey = CacheKeys.getPushTaskKey(taskId); + this.taskSuccessUIDKey = taskId + ':' + TASK_SUCCESS_USER_ID; + } + + @Override + public String taskId() { + return taskId; + } + + @Override + public int qps() { + Integer count = cacheManager.hget(taskKey, TASK_QPS_FIELD, Integer.TYPE); + return count == null ? 1000 : count; + } + + @Override + public void updateQps(int qps) { + cacheManager.hset(taskKey, TASK_QPS_FIELD, qps); + } + + @Override + public boolean isDone() { + return Boolean.TRUE.equals(cacheManager.hget(taskKey, TASK_DONE_FIELD, Boolean.class)); + } + + @Override + public int sendCount() { + Integer count = cacheManager.hget(taskKey, TASK_SEND_COUNT_FIELD, Integer.TYPE); + return count == null ? 0 : count; + } + + @Override + public void cancel() { + cacheManager.hset(taskKey, TASK_CANCEL_FIELD, 1); + } + + @Override + public boolean isCancelled() { + Integer status = cacheManager.hget(taskKey, TASK_CANCEL_FIELD, Integer.TYPE); + return status != null && status == 1; + } + + @Override + public int incSendCount(int count) { + return (int) cacheManager.hincrBy(taskKey, TASK_SEND_COUNT_FIELD, count); + } + + public void success(String... userIds) { + cacheManager.lpush(taskSuccessUIDKey, userIds); + } + + public List successUserIds() { + return cacheManager.lrange(taskSuccessUIDKey, 0, -1, String.class); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/qps/ExactFlowControl.java b/mpush-common/src/main/java/com/mpush/common/qps/ExactFlowControl.java new file mode 100644 index 00000000..314e01d8 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/qps/ExactFlowControl.java @@ -0,0 +1,91 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.qps; + +import com.mpush.tools.common.RollingNumber; + +import java.util.concurrent.TimeUnit; + +import static com.mpush.tools.common.RollingNumber.Event.SUCCESS; + +/** + * Created by ohun on 2016/12/23. + * + * @author ohun@live.cn (夜色) + */ +public final class ExactFlowControl implements FlowControl { + private static final long DELAY_100_MS = TimeUnit.MILLISECONDS.toNanos(1); + private final RollingNumber rollingNumber; + private final int qps_pre_10_mills; + private final long start0 = System.nanoTime(); + + public ExactFlowControl(int qps) { + int timeInMilliseconds = 1000;// 1s + int numberOfBuckets = 100;//把1s 分成 100份,10ms一份, 要计算处 每10ms内允许的最大数量qps_pre_10_mills + + int _10_mills = timeInMilliseconds / numberOfBuckets;//=10 + + double real_qps_pre_10_mills = (qps / 1000f) * _10_mills; + + if (real_qps_pre_10_mills < 1) {//qps < 100; + numberOfBuckets = 1; + real_qps_pre_10_mills = qps; + } + + this.qps_pre_10_mills = (int) real_qps_pre_10_mills; + this.rollingNumber = new RollingNumber(timeInMilliseconds, numberOfBuckets); + } + + @Override + public void reset() { + + } + + @Override + public int total() { + return (int) rollingNumber.getCumulativeSum(SUCCESS); + } + + @Override + public boolean checkQps() throws OverFlowException { + if (rollingNumber.getValueOfLatestBucket(SUCCESS) < qps_pre_10_mills) { + rollingNumber.increment(SUCCESS); + return true; + } + return false; + } + + @Override + public long getDelay() { + return DELAY_100_MS; + } + + @Override + public int qps() { + return (int) rollingNumber.getRollingSum(SUCCESS); + } + + @Override + public String report() { + return String.format("total:%d, count:%d, qps:%d, avg_qps:%d", + total(), rollingNumber.getValueOfLatestBucket(SUCCESS), qps(), + TimeUnit.SECONDS.toNanos(total()) / (System.nanoTime() - start0)); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/qps/FastFlowControl.java b/mpush-common/src/main/java/com/mpush/common/qps/FastFlowControl.java new file mode 100644 index 00000000..33cf1342 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/qps/FastFlowControl.java @@ -0,0 +1,92 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.qps; + +import java.util.concurrent.TimeUnit; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class FastFlowControl implements FlowControl { + private final int limit; + private final int maxLimit; + private final long duration; + private final long start0 = System.nanoTime(); + private int count; + private int total; + private long start; + + + public FastFlowControl(int limit, int maxLimit, int duration) { + this.limit = limit; + this.maxLimit = maxLimit; + this.duration = TimeUnit.MILLISECONDS.toNanos(duration); + } + + public FastFlowControl(int qps) { + this(qps, Integer.MAX_VALUE, 1000); + } + + @Override + public void reset() { + count = 0; + start = System.nanoTime(); + } + + @Override + public int total() { + return total; + } + + @Override + public boolean checkQps() { + if (count < limit) { + count++; + total++; + return true; + } + + if (total > maxLimit) throw new OverFlowException(true); + + if (System.nanoTime() - start > duration) { + reset(); + total++; + return true; + } + return false; + } + + @Override + public long getDelay() { + return duration - (System.nanoTime() - start); + } + + @Override + public String report() { + return String.format("total:%d, count:%d, qps:%d", total, count, qps()); + } + + @Override + public int qps() { + return (int) (TimeUnit.SECONDS.toNanos(total) / (System.nanoTime() - start0)); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/qps/FlowControl.java b/mpush-common/src/main/java/com/mpush/common/qps/FlowControl.java new file mode 100644 index 00000000..f8c9251b --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/qps/FlowControl.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.qps; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public interface FlowControl { + + void reset(); + + int total(); + + /** + * 判断瞬时qps是否超出设定的流量 + * + * @return true/false + * @throws OverFlowException 超出最大限制,会直接抛出异常 + */ + boolean checkQps() throws OverFlowException; + + default void end(Object results) { + } + + /** + * 超出流控的任务,应该延迟执行的时间(ns) + * + * @return 单位纳秒 + */ + long getDelay(); + + /** + * 任务从开始到现在的平均qps + * + * @return 平均qps + */ + int qps(); + + String report(); + +} diff --git a/mpush-common/src/main/java/com/mpush/common/qps/GlobalFlowControl.java b/mpush-common/src/main/java/com/mpush/common/qps/GlobalFlowControl.java new file mode 100644 index 00000000..cde61c06 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/qps/GlobalFlowControl.java @@ -0,0 +1,91 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.qps; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class GlobalFlowControl implements FlowControl { + private final int limit; + private final int maxLimit; + private final long duration; + private final AtomicInteger count = new AtomicInteger(); + private final AtomicInteger total = new AtomicInteger(); + private final long start0 = System.nanoTime(); + private volatile long start; + + public GlobalFlowControl(int qps) { + this(qps, Integer.MAX_VALUE, 1000); + } + + public GlobalFlowControl(int limit, int maxLimit, int duration) { + this.limit = limit; + this.maxLimit = maxLimit; + this.duration = TimeUnit.MILLISECONDS.toNanos(duration); + } + + @Override + public void reset() { + count.set(0); + start = System.nanoTime(); + } + + @Override + public int total() { + return total.get(); + } + + @Override + public boolean checkQps() { + if (count.incrementAndGet() < limit) { + total.incrementAndGet(); + return true; + } + + if (maxLimit > 0 && total.get() > maxLimit) throw new OverFlowException(true); + + if (System.nanoTime() - start > duration) { + reset(); + total.incrementAndGet(); + return true; + } + return false; + } + + @Override + public long getDelay() { + return duration - (System.nanoTime() - start); + } + + @Override + public int qps() { + return (int) (TimeUnit.SECONDS.toNanos(total.get()) / (System.nanoTime() - start0)); + } + + @Override + public String report() { + return String.format("total:%d, count:%d, qps:%d", total.get(), count.get(), qps()); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/qps/OverFlowException.java b/mpush-common/src/main/java/com/mpush/common/qps/OverFlowException.java new file mode 100644 index 00000000..034edf38 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/qps/OverFlowException.java @@ -0,0 +1,47 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.qps; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class OverFlowException extends RuntimeException { + + private boolean overMaxLimit = false; + + public OverFlowException() { + super(null, null, false, false); + } + + public OverFlowException(boolean overMaxLimit) { + super(null, null, false, false); + this.overMaxLimit = overMaxLimit; + } + + public OverFlowException(String message) { + super(message, null, false, false); + } + + public boolean isOverMaxLimit() { + return overMaxLimit; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/qps/RedisFlowControl.java b/mpush-common/src/main/java/com/mpush/common/qps/RedisFlowControl.java new file mode 100644 index 00000000..2341cd75 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/qps/RedisFlowControl.java @@ -0,0 +1,121 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.qps; + +import com.mpush.api.push.BroadcastController; +import com.mpush.common.push.RedisBroadcastController; + +import java.util.concurrent.TimeUnit; + +/** + * Created by ohun on 16/10/25. + * + * @author ohun@live.cn (夜色) + */ +public final class RedisFlowControl implements FlowControl { + + private final BroadcastController controller; + private final long start0 = System.nanoTime(); + private final long duration = TimeUnit.SECONDS.toNanos(1); + private final int maxLimit; + private int limit; + private int count; + private int total; + private long start; + + public RedisFlowControl(String taskId) { + this(taskId, Integer.MAX_VALUE); + } + + public RedisFlowControl(String taskId, int maxLimit) { + this.controller = new RedisBroadcastController(taskId); + this.limit = controller.qps(); + this.maxLimit = maxLimit; + } + + @Override + public void reset() { + count = 0; + start = System.nanoTime(); + } + + @Override + public int total() { + return total; + } + + @Override + public boolean checkQps() throws OverFlowException { + if (count < limit) { + count++; + total++; + return true; + } + + if (total() > maxLimit) { + throw new OverFlowException(true); + } + + if (System.nanoTime() - start > duration) { + reset(); + total++; + return true; + } + + if (controller.isCancelled()) { + throw new OverFlowException(true); + } else { + limit = controller.qps(); + } + return false; + } + + @Override + public void end(Object result) { + int t = total; + if (total > 0) { + total = 0; + controller.incSendCount(t); + } + + if (result != null && (result instanceof String[])) { + controller.success((String[]) result); + } + } + + @Override + public long getDelay() { + return duration - (System.nanoTime() - start); + } + + @Override + public String report() { + return String.format("total:%d, count:%d, qps:%d", total, count, qps()); + } + + @Override + public int qps() { + return (int) (TimeUnit.SECONDS.toNanos(total) / (System.nanoTime() - start0)); + } + + public BroadcastController getController() { + return controller; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/router/ConnectionRouterManager.java b/mpush-common/src/main/java/com/mpush/common/router/CachedRemoteRouterManager.java similarity index 73% rename from mpush-common/src/main/java/com/mpush/common/router/ConnectionRouterManager.java rename to mpush-common/src/main/java/com/mpush/common/router/CachedRemoteRouterManager.java index fec83fde..e1d1ed3e 100644 --- a/mpush-common/src/main/java/com/mpush/common/router/ConnectionRouterManager.java +++ b/mpush-common/src/main/java/com/mpush/common/router/CachedRemoteRouterManager.java @@ -21,17 +21,16 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.mpush.api.router.ClientType; import java.util.Set; import java.util.concurrent.TimeUnit; /** * Created by ohun on 2016/1/4. + * + * @author ohun@live.cn (夜色) */ -public final class ConnectionRouterManager extends RemoteRouterManager { - public static final ConnectionRouterManager I = new ConnectionRouterManager(); - // TODO: 2015/12/30 可以增加一层本地缓存,防止疯狂查询redis, 但是要注意失效问题及数据不一致问题 +public final class CachedRemoteRouterManager extends RemoteRouterManager { private final Cache> cache = CacheBuilder .newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) @@ -42,19 +41,19 @@ public final class ConnectionRouterManager extends RemoteRouterManager { public Set lookupAll(String userId) { Set cached = cache.getIfPresent(userId); if (cached != null) return cached; - Set router = super.lookupAll(userId); - if (router != null) { - cache.put(userId, router); + Set remoteRouters = super.lookupAll(userId); + if (remoteRouters != null) { + cache.put(userId, remoteRouters); } - return router; + return remoteRouters; } @Override public RemoteRouter lookup(String userId, int clientType) { Set cached = this.lookupAll(userId); - for (RemoteRouter router : cached) { - if (router.getRouteValue().getClientType() == clientType) { - return router; + for (RemoteRouter remoteRouter : cached) { + if (remoteRouter.getRouteValue().getClientType() == clientType) { + return remoteRouter; } } return null; diff --git a/mpush-api/src/main/java/com/mpush/api/router/ClientType.java b/mpush-common/src/main/java/com/mpush/common/router/ClientType.java similarity index 79% rename from mpush-api/src/main/java/com/mpush/api/router/ClientType.java rename to mpush-common/src/main/java/com/mpush/common/router/ClientType.java index 14216be5..d6755bc7 100644 --- a/mpush-api/src/main/java/com/mpush/api/router/ClientType.java +++ b/mpush-common/src/main/java/com/mpush/common/router/ClientType.java @@ -17,7 +17,7 @@ * ohun@live.cn (夜色) */ -package com.mpush.api.router; +package com.mpush.common.router; import java.util.Arrays; @@ -41,18 +41,18 @@ public enum ClientType { } public boolean contains(String osName) { - return Arrays.stream(os).anyMatch(s -> s.equalsIgnoreCase(osName)); - } - - public static boolean isSameClient(String osName1, String osName2) { - if (osName1.equals(osName2)) return true; - return find(osName1).contains(osName2); + return Arrays.stream(os).anyMatch(osName::contains); } public static ClientType find(String osName) { for (ClientType type : values()) { - if (type.contains(osName)) return type; + if (type.contains(osName.toLowerCase())) return type; } return UNKNOWN; } + + public static boolean isSameClient(String osNameA, String osNameB) { + if (osNameA.equals(osNameB)) return true; + return find(osNameA).contains(osNameB); + } } diff --git a/mpush-common/src/main/java/com/mpush/common/router/DefaultClientClassifier.java b/mpush-common/src/main/java/com/mpush/common/router/DefaultClientClassifier.java new file mode 100644 index 00000000..07aaf8d9 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/router/DefaultClientClassifier.java @@ -0,0 +1,43 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.router; + +import com.mpush.api.router.ClientClassifier; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.router.ClientClassifierFactory; + +/** + * Created by ohun on 16/10/26. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 1) +public final class DefaultClientClassifier implements ClientClassifier, ClientClassifierFactory { + + @Override + public int getClientType(String osName) { + return ClientType.find(osName).type; + } + + @Override + public ClientClassifier get() { + return this; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/router/KickRemoteMsg.java b/mpush-common/src/main/java/com/mpush/common/router/KickRemoteMsg.java new file mode 100644 index 00000000..687ec0d5 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/router/KickRemoteMsg.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.router; + +import com.mpush.common.ServerNodes; +import com.mpush.tools.config.ConfigTools; + + +/** + * Created by ohun on 16/10/23. + * + * @author ohun@live.cn (夜色) + */ +public interface KickRemoteMsg { + String getUserId(); + + String getDeviceId(); + + String getConnId(); + + int getClientType(); + + String getTargetServer(); + + int getTargetPort(); + + default boolean isTargetMachine(String host, int port) { + return this.getTargetPort() == port + && this.getTargetServer().equals(host); + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/router/MQKickRemoteMsg.java b/mpush-common/src/main/java/com/mpush/common/router/MQKickRemoteMsg.java new file mode 100644 index 00000000..3cea97f9 --- /dev/null +++ b/mpush-common/src/main/java/com/mpush/common/router/MQKickRemoteMsg.java @@ -0,0 +1,106 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.common.router; + +/** + * Created by ohun on 16/10/23. + * + * @author ohun@live.cn (夜色) + */ +public class MQKickRemoteMsg implements KickRemoteMsg { + private String userId; + private String deviceId; + private String connId; + private int clientType; + private String targetServer; + private int targetPort; + + public MQKickRemoteMsg setUserId(String userId) { + this.userId = userId; + return this; + } + + public MQKickRemoteMsg setDeviceId(String deviceId) { + this.deviceId = deviceId; + return this; + } + + public MQKickRemoteMsg setConnId(String connId) { + this.connId = connId; + return this; + } + + public MQKickRemoteMsg setClientType(int clientType) { + this.clientType = clientType; + return this; + } + + public MQKickRemoteMsg setTargetServer(String targetServer) { + this.targetServer = targetServer; + return this; + } + + public MQKickRemoteMsg setTargetPort(int targetPort) { + this.targetPort = targetPort; + return this; + } + + @Override + public String getUserId() { + return userId; + } + + @Override + public String getDeviceId() { + return deviceId; + } + + @Override + public String getConnId() { + return connId; + } + + @Override + public int getClientType() { + return clientType; + } + + @Override + public String getTargetServer() { + return targetServer; + } + + @Override + public int getTargetPort() { + return targetPort; + } + + @Override + public String toString() { + return "KickRemoteMsg{" + + "userId='" + userId + '\'' + + ", deviceId='" + deviceId + '\'' + + ", connId='" + connId + '\'' + + ", clientType='" + clientType + '\'' + + ", targetServer='" + targetServer + '\'' + + ", targetPort=" + targetPort + + '}'; + } +} diff --git a/mpush-common/src/main/java/com/mpush/common/router/RemoteRouter.java b/mpush-common/src/main/java/com/mpush/common/router/RemoteRouter.java index d7d9efed..d3a0b1ac 100644 --- a/mpush-common/src/main/java/com/mpush/common/router/RemoteRouter.java +++ b/mpush-common/src/main/java/com/mpush/common/router/RemoteRouter.java @@ -34,6 +34,14 @@ public RemoteRouter(ClientLocation clientLocation) { this.clientLocation = clientLocation; } + public boolean isOnline(){ + return clientLocation.isOnline(); + } + + public boolean isOffline(){ + return clientLocation.isOffline(); + } + @Override public ClientLocation getRouteValue() { return clientLocation; diff --git a/mpush-common/src/main/java/com/mpush/common/router/RemoteRouterManager.java b/mpush-common/src/main/java/com/mpush/common/router/RemoteRouterManager.java index ef71818d..4fd76327 100644 --- a/mpush-common/src/main/java/com/mpush/common/router/RemoteRouterManager.java +++ b/mpush-common/src/main/java/com/mpush/common/router/RemoteRouterManager.java @@ -19,15 +19,16 @@ package com.mpush.common.router; +import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; import com.mpush.api.event.ConnectionCloseEvent; import com.mpush.api.router.ClientLocation; -import com.mpush.api.router.ClientType; import com.mpush.api.router.RouterManager; -import com.mpush.cache.redis.RedisKey; -import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; +import com.mpush.common.CacheKeys; import com.mpush.tools.event.EventConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,41 +46,50 @@ public class RemoteRouterManager extends EventConsumer implements RouterManager { public static final Logger LOGGER = LoggerFactory.getLogger(RemoteRouterManager.class); + private final CacheManager cacheManager = CacheManagerFactory.create(); + @Override public RemoteRouter register(String userId, RemoteRouter router) { - LOGGER.info("register remote router success userId={}, router={}", userId, router); - String key = RedisKey.getUserKey(userId); + String key = CacheKeys.getUserRouteKey(userId); String field = Integer.toString(router.getRouteValue().getClientType()); - ClientLocation old = RedisManager.I.hget(key, field, ClientLocation.class); - if (old != null) { - RedisManager.I.hdel(key, field); - } - RedisManager.I.hset(key, field, router.getRouteValue()); + ClientLocation old = cacheManager.hget(key, field, ClientLocation.class); + cacheManager.hset(key, field, router.getRouteValue()); + LOGGER.info("register remote router success userId={}, newRouter={}, oldRoute={}", userId, router, old); return old == null ? null : new RemoteRouter(old); } + /** + * 目前的实现方式是非原子操作(get:set),可能会有并发问题,虽然概率很低 + * 后续考虑采用lua脚本,实现原子操作 + * + * @param userId 用户ID + * @param clientType 客户端类型 + * @return 删除路由是否成功 + */ @Override public boolean unRegister(String userId, int clientType) { - String key = RedisKey.getUserKey(userId); + String key = CacheKeys.getUserRouteKey(userId); String field = Integer.toString(clientType); - RedisManager.I.hdel(key, field); - LOGGER.info("unRegister remote router success userId={}, clientType={}", userId, clientType); + ClientLocation location = cacheManager.hget(key, field, ClientLocation.class); + if (location == null || location.isOffline()) return true; + cacheManager.hset(key, field, location.offline()); + LOGGER.info("unRegister remote router success userId={}, route={}", userId, location); return true; } @Override public Set lookupAll(String userId) { - String key = RedisKey.getUserKey(userId); - Map values = RedisManager.I.hgetAll(key, ClientLocation.class); + String key = CacheKeys.getUserRouteKey(userId); + Map values = cacheManager.hgetAll(key, ClientLocation.class); if (values == null || values.isEmpty()) return Collections.emptySet(); return values.values().stream().map(RemoteRouter::new).collect(Collectors.toSet()); } @Override public RemoteRouter lookup(String userId, int clientType) { - String key = RedisKey.getUserKey(userId); + String key = CacheKeys.getUserRouteKey(userId); String field = Integer.toString(clientType); - ClientLocation location = RedisManager.I.hget(key, field, ClientLocation.class); + ClientLocation location = cacheManager.hget(key, field, ClientLocation.class); LOGGER.info("lookup remote router userId={}, router={}", userId, location); return location == null ? null : new RemoteRouter(location); } @@ -90,21 +100,22 @@ public RemoteRouter lookup(String userId, int clientType) { * @param event */ @Subscribe + @AllowConcurrentEvents void on(ConnectionCloseEvent event) { Connection connection = event.connection; if (connection == null) return; SessionContext context = connection.getSessionContext(); String userId = context.userId; if (userId == null) return; - String key = RedisKey.getUserKey(userId); + String key = CacheKeys.getUserRouteKey(userId); String field = Integer.toString(context.getClientType()); - ClientLocation location = RedisManager.I.hget(key, field, ClientLocation.class); - if (location == null) return; + ClientLocation location = cacheManager.hget(key, field, ClientLocation.class); + if (location == null || location.isOffline()) return; String connId = connection.getId(); //2.检测下,是否是同一个链接, 如果客户端重连,老的路由会被新的链接覆盖 if (connId.equals(location.getConnId())) { - RedisManager.I.hdel(key, field); + cacheManager.hset(key, field, location.offline()); LOGGER.info("clean disconnected remote route, userId={}, route={}", userId, location); } else { LOGGER.info("clean disconnected remote route, not clean:userId={}, route={}", userId, location); diff --git a/mpush-common/src/main/java/com/mpush/common/router/UserChangeListener.java b/mpush-common/src/main/java/com/mpush/common/router/UserChangeListener.java deleted file mode 100644 index e619ddf4..00000000 --- a/mpush-common/src/main/java/com/mpush/common/router/UserChangeListener.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.common.router; - -import com.mpush.cache.redis.listener.ListenerDispatcher; -import com.mpush.cache.redis.listener.MessageListener; -import com.mpush.cache.redis.manager.RedisManager; -import com.mpush.tools.Utils; -import com.mpush.tools.event.EventConsumer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Created by ohun on 2016/1/4. - * - * @author ohun@live.cn - */ -public class UserChangeListener extends EventConsumer implements MessageListener { - - private static final Logger LOGGER = LoggerFactory.getLogger(UserChangeListener.class); - - public static final String ONLINE_CHANNEL = "/mpush/online/"; - - public static final String OFFLINE_CHANNEL = "/mpush/offline/"; - - //只需要一台机器注册online、offline 消息通道 - public UserChangeListener() { - if ("127.0.0.1".equals(Utils.getLocalIp())) { - ListenerDispatcher.I.subscribe(getOnlineChannel(), this); - ListenerDispatcher.I.subscribe(getOfflineChannel(), this); - } else { - LOGGER.error("UserChangeListener is not localhost,required:{}, but:{}", "127.0.0.1", Utils.getLocalIp()); - } - } - - public String getOnlineChannel() { - return ONLINE_CHANNEL; - } - - public String getOfflineChannel() { - return OFFLINE_CHANNEL; - } - - public void userOnline(String userId) { - RedisManager.I.publish(getOnlineChannel(), userId); - } - - public void userOffline(String userId) { - RedisManager.I.publish(getOnlineChannel(), userId); - } - - @Override - public void onMessage(String channel, String message) { - } -} diff --git a/mpush-common/src/main/java/com/mpush/common/security/RsaCipherFactory.java b/mpush-common/src/main/java/com/mpush/common/security/RsaCipherFactory.java index 441f5006..acf57a44 100644 --- a/mpush-common/src/main/java/com/mpush/common/security/RsaCipherFactory.java +++ b/mpush-common/src/main/java/com/mpush/common/security/RsaCipherFactory.java @@ -20,6 +20,7 @@ package com.mpush.common.security; import com.mpush.api.connection.Cipher; +import com.mpush.api.spi.Spi; import com.mpush.api.spi.core.CipherFactory; /** @@ -27,6 +28,7 @@ * * @author ohun@live.cn */ +@Spi public class RsaCipherFactory implements CipherFactory { private static final RsaCipher RSA_CIPHER = RsaCipher.create(); diff --git a/mpush-common/src/main/java/com/mpush/common/user/UserManager.java b/mpush-common/src/main/java/com/mpush/common/user/UserManager.java index 7a9d8372..ed18f6f7 100644 --- a/mpush-common/src/main/java/com/mpush/common/user/UserManager.java +++ b/mpush-common/src/main/java/com/mpush/common/user/UserManager.java @@ -19,50 +19,93 @@ package com.mpush.common.user; -import com.mpush.cache.redis.RedisKey; -import com.mpush.cache.redis.manager.RedisManager; -import com.mpush.tools.config.ConfigManager; +import com.mpush.api.Constants; +import com.mpush.api.router.ClientLocation; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; +import com.mpush.api.spi.common.MQClient; +import com.mpush.api.spi.common.MQClientFactory; +import com.mpush.common.CacheKeys; +import com.mpush.common.router.CachedRemoteRouterManager; +import com.mpush.common.router.MQKickRemoteMsg; +import com.mpush.common.router.RemoteRouter; +import com.mpush.common.router.RemoteRouterManager; +import com.mpush.tools.config.ConfigTools; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Set; //查询使用 public final class UserManager { private static final Logger LOGGER = LoggerFactory.getLogger(UserManager.class); - public static final UserManager I = new UserManager(); - private final String ONLINE_KEY = RedisKey.getUserOnlineKey(ConfigManager.I.getPublicIp()); + private final String onlineUserListKey = CacheKeys.getOnlineUserListKey(ConfigTools.getPublicIp()); - public UserManager() { - clearUserOnlineData(); + private final CacheManager cacheManager = CacheManagerFactory.create(); + + private final MQClient mqClient = MQClientFactory.create(); + + private final RemoteRouterManager remoteRouterManager; + + public UserManager(RemoteRouterManager remoteRouterManager) { + this.remoteRouterManager = remoteRouterManager; + } + + public void kickUser(String userId) { + kickUser(userId, -1); + } + + public void kickUser(String userId, int clientType) { + Set remoteRouters = remoteRouterManager.lookupAll(userId); + if (remoteRouters != null) { + for (RemoteRouter remoteRouter : remoteRouters) { + ClientLocation location = remoteRouter.getRouteValue(); + if (clientType == -1 || location.getClientType() == clientType) { + MQKickRemoteMsg message = new MQKickRemoteMsg() + .setUserId(userId) + .setClientType(location.getClientType()) + .setConnId(location.getConnId()) + .setDeviceId(location.getDeviceId()) + .setTargetServer(location.getHost()) + .setTargetPort(location.getPort()); + mqClient.publish(Constants.getKickChannel(location.getHostAndPort()), message); + } + } + } } public void clearUserOnlineData() { - RedisManager.I.del(ONLINE_KEY); + cacheManager.del(onlineUserListKey); } - public void recordUserOnline(String userId) { - RedisManager.I.zAdd(ONLINE_KEY, userId); + public void addToOnlineList(String userId) { + cacheManager.zAdd(onlineUserListKey, userId); LOGGER.info("user online {}", userId); } - public void recordUserOffline(String userId) { - RedisManager.I.zRem(ONLINE_KEY, userId); + public void remFormOnlineList(String userId) { + cacheManager.zRem(onlineUserListKey, userId); LOGGER.info("user offline {}", userId); } - //在线用户 + //在线用户数量 public long getOnlineUserNum() { - Long value = RedisManager.I.zCard(ONLINE_KEY); + Long value = cacheManager.zCard(onlineUserListKey); + return value == null ? 0 : value; + } + + //在线用户数量 + public long getOnlineUserNum(String publicIP) { + String online_key = CacheKeys.getOnlineUserListKey(publicIP); + Long value = cacheManager.zCard(online_key); return value == null ? 0 : value; } //在线用户列表 - public List getOnlineUserList(int start, int size) { - if (size < 10) { - size = 10; - } - return RedisManager.I.zrange(ONLINE_KEY, start, size - 1, String.class); + public List getOnlineUserList(String publicIP, int start, int end) { + String key = CacheKeys.getOnlineUserListKey(publicIP); + return cacheManager.zrange(key, start, end, String.class); } } diff --git a/mpush-common/src/main/resources/META-INF/services/com.mpush.api.spi.router.ClientClassifierFactory b/mpush-common/src/main/resources/META-INF/services/com.mpush.api.spi.router.ClientClassifierFactory new file mode 100644 index 00000000..a3b2f119 --- /dev/null +++ b/mpush-common/src/main/resources/META-INF/services/com.mpush.api.spi.router.ClientClassifierFactory @@ -0,0 +1 @@ +com.mpush.common.router.DefaultClientClassifier \ No newline at end of file diff --git a/mpush-core/pom.xml b/mpush-core/pom.xml index 174efd1a..ed413586 100644 --- a/mpush-core/pom.xml +++ b/mpush-core/pom.xml @@ -2,25 +2,34 @@ + + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} + mpush-core - ${mpush-core-version} - mpush-core jar + mpush-core + MPUSH消息推送系统核心模块 + https://github.com/mpusher/mpush + - ${mpush.groupId} + ${project.groupId} mpush-netty - ${mpush.groupId} + ${project.groupId} mpush-common + + ${project.groupId} + mpush-monitor + diff --git a/mpush-core/src/main/java/com/mpush/core/MPushServer.java b/mpush-core/src/main/java/com/mpush/core/MPushServer.java new file mode 100644 index 00000000..c8ba1f49 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/MPushServer.java @@ -0,0 +1,181 @@ +/* + * (C) Copyright 2015-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core; + +import com.mpush.api.MPushContext; +import com.mpush.api.common.Monitor; +import com.mpush.api.spi.common.*; +import com.mpush.api.srd.ServiceDiscovery; +import com.mpush.api.srd.ServiceNode; +import com.mpush.api.srd.ServiceRegistry; +import com.mpush.common.ServerNodes; +import com.mpush.common.user.UserManager; +import com.mpush.core.ack.AckTaskQueue; +import com.mpush.core.push.PushCenter; +import com.mpush.core.router.RouterCenter; +import com.mpush.core.server.*; +import com.mpush.core.session.ReusableSessionManager; +import com.mpush.monitor.service.MonitorService; +import com.mpush.netty.http.HttpClient; +import com.mpush.netty.http.NettyHttpClient; +import com.mpush.tools.event.EventBus; +import com.mpush.monitor.service.ThreadPoolManager; + +import static com.mpush.tools.config.CC.mp.net.tcpGateway; + +/** + * Created by ohun on 2017/6/14. + * + * @author ohun@live.cn (夜色) + */ +public final class MPushServer implements MPushContext { + + private ServiceNode connServerNode; + private ServiceNode gatewayServerNode; + private ServiceNode websocketServerNode; + + private ConnectionServer connectionServer; + private WebsocketServer websocketServer; + private GatewayServer gatewayServer; + private AdminServer adminServer; + private GatewayUDPConnector udpGatewayServer; + + private HttpClient httpClient; + + private PushCenter pushCenter; + + private ReusableSessionManager reusableSessionManager; + + private RouterCenter routerCenter; + + private MonitorService monitorService; + + + public MPushServer() { + connServerNode = ServerNodes.cs(); + gatewayServerNode = ServerNodes.gs(); + websocketServerNode = ServerNodes.ws(); + + monitorService = new MonitorService(); + EventBus.create(monitorService.getThreadPoolManager().getEventBusExecutor()); + + reusableSessionManager = new ReusableSessionManager(); + + pushCenter = new PushCenter(this); + + routerCenter = new RouterCenter(this); + + connectionServer = new ConnectionServer(this); + + websocketServer = new WebsocketServer(this); + + adminServer = new AdminServer(this); + + if (tcpGateway()) { + gatewayServer = new GatewayServer(this); + } else { + udpGatewayServer = new GatewayUDPConnector(this); + } + } + + public boolean isTargetMachine(String host, int port) { + return port == gatewayServerNode.getPort() && gatewayServerNode.getHost().equals(host); + } + + public ServiceNode getConnServerNode() { + return connServerNode; + } + + public ServiceNode getGatewayServerNode() { + return gatewayServerNode; + } + + public ServiceNode getWebsocketServerNode() { + return websocketServerNode; + } + + public ConnectionServer getConnectionServer() { + return connectionServer; + } + + public GatewayServer getGatewayServer() { + return gatewayServer; + } + + public AdminServer getAdminServer() { + return adminServer; + } + + public GatewayUDPConnector getUdpGatewayServer() { + return udpGatewayServer; + } + + public WebsocketServer getWebsocketServer() { + return websocketServer; + } + + public HttpClient getHttpClient() { + if (httpClient == null) { + synchronized (this) { + if (httpClient == null) { + httpClient = new NettyHttpClient(); + } + } + } + return httpClient; + } + + public PushCenter getPushCenter() { + return pushCenter; + } + + public ReusableSessionManager getReusableSessionManager() { + return reusableSessionManager; + } + + public RouterCenter getRouterCenter() { + return routerCenter; + } + + @Override + public MonitorService getMonitor() { + return monitorService; + } + + @Override + public ServiceDiscovery getDiscovery() { + return ServiceDiscoveryFactory.create(); + } + + @Override + public ServiceRegistry getRegistry() { + return ServiceRegistryFactory.create(); + } + + @Override + public CacheManager getCacheManager() { + return CacheManagerFactory.create(); + } + + @Override + public MQClient getMQClient() { + return MQClientFactory.create(); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/ServerExecutorFactory.java b/mpush-core/src/main/java/com/mpush/core/ServerExecutorFactory.java new file mode 100644 index 00000000..192b55cb --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/ServerExecutorFactory.java @@ -0,0 +1,77 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core; + +import com.mpush.api.push.PushException; +import com.mpush.api.spi.Spi; +import com.mpush.common.CommonExecutorFactory; +import com.mpush.tools.config.CC; +import com.mpush.tools.log.Logs; +import com.mpush.tools.thread.NamedPoolThreadFactory; +import com.mpush.tools.thread.pool.ThreadPoolConfig; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.mpush.tools.config.CC.mp.thread.pool.ack_timer; +import static com.mpush.tools.config.CC.mp.thread.pool.push_task; +import static com.mpush.tools.thread.ThreadNames.*; + +/** + * 此线程池可伸缩,线程空闲一定时间后回收,新请求重新创建线程 + */ +@Spi(order = 1) +public final class ServerExecutorFactory extends CommonExecutorFactory { + + @Override + public Executor get(String name) { + final ThreadPoolConfig config; + switch (name) { + case MQ: + config = ThreadPoolConfig + .build(T_MQ) + .setCorePoolSize(CC.mp.thread.pool.mq.min) + .setMaxPoolSize(CC.mp.thread.pool.mq.max) + .setKeepAliveSeconds(TimeUnit.SECONDS.toSeconds(10)) + .setQueueCapacity(CC.mp.thread.pool.mq.queue_size) + .setRejectedPolicy(ThreadPoolConfig.REJECTED_POLICY_CALLER_RUNS); + break; + case PUSH_TASK: + return new ScheduledThreadPoolExecutor(push_task, new NamedPoolThreadFactory(T_PUSH_CENTER_TIMER), + (r, e) -> { + throw new PushException("one push task was rejected. task=" + r); + } + ); + case ACK_TIMER: { + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(ack_timer, + new NamedPoolThreadFactory(T_ARK_REQ_TIMER), + (r, e) -> Logs.PUSH.error("one ack context was rejected, context=" + r) + ); + executor.setRemoveOnCancelPolicy(true); + return executor; + } + default: + return super.get(name); + } + + return get(config); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/ack/AckCallback.java b/mpush-core/src/main/java/com/mpush/core/ack/AckCallback.java new file mode 100644 index 00000000..b8e2ca6e --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/ack/AckCallback.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.ack; + +/** + * Created by ohun on 16/9/6. + * + * @author ohun@live.cn (夜色) + */ +public interface AckCallback { + void onSuccess(AckTask context); + + void onTimeout(AckTask context); +} diff --git a/mpush-core/src/main/java/com/mpush/core/ack/AckTask.java b/mpush-core/src/main/java/com/mpush/core/ack/AckTask.java new file mode 100644 index 00000000..2ad96ff4 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/ack/AckTask.java @@ -0,0 +1,92 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.ack; + +import java.util.concurrent.Future; + +/** + * Created by ohun on 16/9/5. + * + * @author ohun@live.cn (夜色) + */ +public final class AckTask implements Runnable { + private final int ackMessageId; + private AckTaskQueue ackTaskQueue; + private AckCallback callback; + private Future timeoutFuture; + + public AckTask(int ackMessageId) { + this.ackMessageId = ackMessageId; + } + + public static AckTask from(int ackMessageId) { + return new AckTask(ackMessageId); + } + + public AckTask setAckTaskQueue(AckTaskQueue ackTaskQueue) { + this.ackTaskQueue = ackTaskQueue; + return this; + } + + public void setFuture(Future future) { + this.timeoutFuture = future; + } + + public AckTask setCallback(AckCallback callback) { + this.callback = callback; + return this; + } + + public int getAckMessageId() { + return ackMessageId; + } + + + private boolean tryDone() { + return timeoutFuture.cancel(true); + } + + public void onResponse() { + if (tryDone()) { + callback.onSuccess(this); + callback = null; + } + } + + public void onTimeout() { + AckTask context = ackTaskQueue.getAndRemove(ackMessageId); + if (context != null && tryDone()) { + callback.onTimeout(this); + callback = null; + } + } + + @Override + public String toString() { + return "{" + + ", ackMessageId=" + ackMessageId + + '}'; + } + + @Override + public void run() { + onTimeout(); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/ack/AckTaskQueue.java b/mpush-core/src/main/java/com/mpush/core/ack/AckTaskQueue.java new file mode 100644 index 00000000..a8fdc837 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/ack/AckTaskQueue.java @@ -0,0 +1,79 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.ack; + +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; +import com.mpush.core.MPushServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Created by ohun on 16/9/5. + * + * @author ohun@live.cn (夜色) + */ +public final class AckTaskQueue extends BaseService { + private static final int DEFAULT_TIMEOUT = 3000; + + private final Logger logger = LoggerFactory.getLogger(AckTaskQueue.class); + + private final ConcurrentMap queue = new ConcurrentHashMap<>(); + private ScheduledExecutorService scheduledExecutor; + private MPushServer mPushServer; + + public AckTaskQueue(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } + + public void add(AckTask task, int timeout) { + queue.put(task.getAckMessageId(), task); + task.setAckTaskQueue(this); + task.setFuture(scheduledExecutor.schedule(task,//使用 task.getExecutor() 并没更快 + timeout > 0 ? timeout : DEFAULT_TIMEOUT, + TimeUnit.MILLISECONDS + )); + + logger.debug("one ack task add to queue, task={}, timeout={}", task, timeout); + } + + public AckTask getAndRemove(int sessionId) { + return queue.remove(sessionId); + } + + @Override + protected void doStart(Listener listener) throws Throwable { + scheduledExecutor = mPushServer.getMonitor().getThreadPoolManager().getAckTimer(); + super.doStart(listener); + } + + @Override + protected void doStop(Listener listener) throws Throwable { + if (scheduledExecutor != null) { + scheduledExecutor.shutdown(); + } + super.doStop(listener); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/handler/AckHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/AckHandler.java new file mode 100644 index 00000000..3f9fb916 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/handler/AckHandler.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.handler; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Packet; +import com.mpush.common.handler.BaseMessageHandler; +import com.mpush.common.message.AckMessage; +import com.mpush.core.MPushServer; +import com.mpush.core.ack.AckTask; +import com.mpush.core.ack.AckTaskQueue; +import com.mpush.tools.log.Logs; + +/** + * Created by ohun on 16/9/5. + * + * @author ohun@live.cn (夜色) + */ +public final class AckHandler extends BaseMessageHandler { + + private final AckTaskQueue ackTaskQueue; + + public AckHandler(MPushServer mPushServer) { + this.ackTaskQueue = mPushServer.getPushCenter().getAckTaskQueue(); + } + + + @Override + public AckMessage decode(Packet packet, Connection connection) { + return new AckMessage(packet, connection); + } + + @Override + public void handle(AckMessage message) { + AckTask task = ackTaskQueue.getAndRemove(message.getSessionId()); + if (task == null) {//ack 超时了 + Logs.PUSH.info("receive client ack, but task timeout message={}", message); + return; + } + + task.onResponse();//成功收到客户的ACK响应 + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/handler/AdminHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/AdminHandler.java index bdc19126..911f46f6 100644 --- a/mpush-core/src/main/java/com/mpush/core/handler/AdminHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/handler/AdminHandler.java @@ -20,55 +20,122 @@ package com.mpush.core.handler; import com.google.common.base.Strings; -import com.mpush.api.push.PushSender; -import com.mpush.api.service.Listener; import com.mpush.common.router.RemoteRouter; import com.mpush.common.user.UserManager; +import com.mpush.core.MPushServer; import com.mpush.core.router.RouterCenter; -import com.mpush.core.server.AdminServer; import com.mpush.tools.Jsons; -import com.mpush.tools.Utils; +import com.mpush.tools.common.Profiler; import com.mpush.tools.config.CC; -import com.mpush.tools.config.ConfigManager; -import com.mpush.zk.ZKClient; -import com.mpush.zk.ZKPath; -import com.mpush.zk.node.ZKServerNode; +import com.mpush.tools.config.ConfigTools; import com.typesafe.config.ConfigRenderOptions; import io.netty.channel.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.PrintWriter; -import java.io.Serializable; import java.io.StringWriter; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Set; -import java.util.concurrent.TimeUnit; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; @ChannelHandler.Sharable public final class AdminHandler extends SimpleChannelInboundHandler { private static final Logger LOGGER = LoggerFactory.getLogger(AdminHandler.class); - private static final String EOL = "\r\n"; - private static AdminServer adminServer; + private final LocalDateTime startTime = LocalDateTime.now(); + + private final Map optionHandlers = new HashMap<>(); + + private final OptionHandler unsupported_handler = (_1, _2) -> "unsupported option"; + + private final MPushServer mPushServer; + + public AdminHandler(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } + + public void init() { + register("help", (ctx, args) -> + "Option Description" + EOL + + "------ -----------" + EOL + + "help show help" + EOL + + "quit exit console mode" + EOL + + "shutdown stop mpush server" + EOL + + "restart restart mpush server" + EOL + + "zk: query zk node" + EOL + + "count: count conn num or online user count" + EOL + + "route: show user route info" + EOL + + "push:, push test msg to client" + EOL + + "conf:[key] show config info" + EOL + + "monitor:[mxBean] show system monitor" + EOL + + "profile:<1,0> enable/disable profile" + EOL + ); + + register("quit", (ctx, args) -> "have a good day!"); + + register("shutdown", (ctx, args) -> { + new Thread(() -> System.exit(0)).start(); + return "try close connect server..."; + }); + + register("count", (ctx, args) -> { + switch (args) { + case "conn": + return mPushServer.getConnectionServer().getConnectionManager().getConnNum(); + case "online": { + return mPushServer.getRouterCenter().getUserEventConsumer().getUserManager().getOnlineUserNum(); + } + + } + return "[" + args + "] unsupported, try help."; + }); + + register("route", (ctx, args) -> { + if (Strings.isNullOrEmpty(args)) return "please input userId"; + Set routers = mPushServer.getRouterCenter().getRemoteRouterManager().lookupAll(args); + if (routers.isEmpty()) return "user [" + args + "] offline now."; + return Jsons.toJson(routers); + }); + + register("conf", (ctx, args) -> { + if (Strings.isNullOrEmpty(args)) { + return CC.cfg.root().render(ConfigRenderOptions.concise().setFormatted(true)); + } + if (CC.cfg.hasPath(args)) { + return CC.cfg.getAnyRef(args).toString(); + } + return "key [" + args + "] not find in config"; + }); + + register("profile", (ctx, args) -> { + if (args == null || "0".equals(args)) { + Profiler.enable(false); + return "Profiler disabled"; + } else { + Profiler.enable(true); + return "Profiler enabled"; + } + }); + } + - public AdminHandler(AdminServer adminServer) { - this.adminServer = adminServer; + private void register(String option, OptionHandler handler) { + optionHandlers.put(option, handler); } + @Override - protected void messageReceived(ChannelHandlerContext ctx, String request) throws Exception { - Command command = Command.help; + protected void channelRead0(ChannelHandlerContext ctx, String request) throws Exception { + String option = "help"; String arg = null; String[] args = null; if (request != null) { String[] cmd_args = request.split(" "); - command = Command.toCmd(cmd_args[0].trim()); + option = cmd_args[0].trim().toLowerCase(); if (cmd_args.length == 2) { arg = cmd_args[1]; } else if (cmd_args.length > 2) { @@ -76,9 +143,9 @@ protected void messageReceived(ChannelHandlerContext ctx, String request) throws } } try { - Object result = args != null ? command.handler(ctx, args) : command.handler(ctx, arg); + Object result = optionHandlers.getOrDefault(option, unsupported_handler).handle(ctx, arg); ChannelFuture future = ctx.writeAndFlush(result + EOL + EOL); - if (command == Command.quit) { + if (option.equals("quit")) { future.addListener(ChannelFutureListener.CLOSE); } } catch (Throwable throwable) { @@ -87,11 +154,13 @@ protected void messageReceived(ChannelHandlerContext ctx, String request) throws throwable.printStackTrace(new PrintWriter(writer)); ctx.writeAndFlush(writer.toString()); } + LOGGER.info("receive admin command={}", request); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - ctx.write("welcome to " + Utils.getInetAddress() + "!" + EOL); + ctx.write("Welcome to MPush Console [" + ConfigTools.getLocalIp() + "]!" + EOL); + ctx.write("since " + startTime + " has running " + startTime.until(LocalDateTime.now(), ChronoUnit.HOURS) + "(h)" + EOL + EOL); ctx.write("It is " + new Date() + " now." + EOL + EOL); ctx.flush(); } @@ -101,166 +170,7 @@ public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } - public enum Command { - help { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - StringBuilder buf = new StringBuilder(); - buf.append("Option Description" + EOL); - buf.append("------ -----------" + EOL); - buf.append("help show help" + EOL); - buf.append("quit exit console mode" + EOL); - buf.append("shutdown stop mpush server" + EOL); - buf.append("restart restart mpush server" + EOL); - buf.append("zk: query zk node" + EOL); - buf.append("count: count conn num or online user count" + EOL); - buf.append("route: show user route info" + EOL); - buf.append("push:, push test msg to client" + EOL); - buf.append("conf:[key] show config info" + EOL); - buf.append("monitor:[mxBean] show system monitor" + EOL); - return buf.toString(); - } - }, - quit { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - return "have a good day!"; - } - }, - shutdown { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - ctx.writeAndFlush("try close connect server..."); - adminServer.getConnectionServer().stop(new Listener() { - @Override - public void onSuccess(Object... args) { - ctx.writeAndFlush("connect server close success" + EOL); - adminServer.stop(null);//这个一定要在System.exit之前调用,不然jvm 会卡死 @see com.mpush.bootstrap.Main#addHook - System.exit(0); - } - - @Override - public void onFailure(Throwable cause) { - ctx.writeAndFlush("connect server close failure, msg=" + cause.getLocalizedMessage()); - } - }); - return null; - } - }, - restart { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - return "unsupported"; - } - }, - zk { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - switch (args) { - case "redis": - return ZKClient.I.get(ZKPath.REDIS_SERVER.getRootPath()); - case "cs": - return getNodeData(ZKPath.CONNECT_SERVER); - case "gs": - return getNodeData(ZKPath.GATEWAY_SERVER); - - } - return "[" + args + "] unsupported, try help."; - } - - private String getNodeData(ZKPath path) { - List rawData = ZKClient.I.getChildrenKeys(path.getRootPath()); - StringBuilder sb = new StringBuilder(); - for (String raw : rawData) { - sb.append(ZKClient.I.get(path.getFullPath(raw))).append('\n'); - } - return sb.toString(); - } - }, - count { - @Override - public Serializable handler(ChannelHandlerContext ctx, String args) { - switch (args) { - case "conn": - return adminServer.getConnectionServer().getConnectionManager().getConnections().size(); - case "online": { - return UserManager.I.getOnlineUserNum(); - } - - } - return "[" + args + "] unsupported, try help."; - } - }, - route { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - if (Strings.isNullOrEmpty(args)) return "please input userId"; - Set routers = RouterCenter.I.getRemoteRouterManager().lookupAll(args); - if (routers.isEmpty()) return "user [" + args + "] offline now."; - return Jsons.toJson(routers); - } - }, - push { - @Override - public String handler(ChannelHandlerContext ctx, String... args) throws Exception { - Boolean success = PushSender.create().send(args[1], args[0], null).get(5, TimeUnit.SECONDS); - - return success.toString(); - } - }, - conf { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - if (Strings.isNullOrEmpty(args)) { - return CC.cfg.root().render(ConfigRenderOptions.concise().setFormatted(true)); - } - if (CC.cfg.hasPath(args)) { - return CC.cfg.getAnyRef(args).toString(); - } - return "key [" + args + "] not find in config"; - } - }, - rcs { - @Override - public String handler(ChannelHandlerContext ctx, String args) { - - List rawData = ZKClient.I.getChildrenKeys(ZKPath.CONNECT_SERVER.getRootPath()); - boolean removeSuccess = false; - String localIp = ConfigManager.I.getLocalIp(); - for (String raw : rawData) { - String dataPath = ZKPath.CONNECT_SERVER.getFullPath(raw); - String data = ZKClient.I.get(dataPath); - ZKServerNode serverNode = Jsons.fromJson(data, ZKServerNode.class); - if (serverNode.getIp().equals(localIp)) { - ZKClient.I.remove(dataPath); - LOGGER.info("delete connection server success:{}", data); - removeSuccess = true; - } else { - LOGGER.info("delete connection server failed: required host:{}, but:{}", serverNode.getIp(), Utils.getInetAddress()); - } - } - if (removeSuccess) { - return "removeAndClose success."; - } else { - return "removeAndClose false."; - } - } - }; - - public Object handler(ChannelHandlerContext ctx, String... args) throws Exception { - return "unsupported"; - } - - public Object handler(ChannelHandlerContext ctx, String args) throws Exception { - return "unsupported"; - } - - public static Command toCmd(String cmd) { - try { - return Command.valueOf(cmd); - } catch (Exception e) { - } - return help; - } + public interface OptionHandler { + Object handle(ChannelHandlerContext ctx, String args) throws Exception; } } diff --git a/mpush-core/src/main/java/com/mpush/core/handler/BindUserHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/BindUserHandler.java index b02c3adf..6b90470a 100644 --- a/mpush-core/src/main/java/com/mpush/core/handler/BindUserHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/handler/BindUserHandler.java @@ -22,12 +22,22 @@ import com.google.common.base.Strings; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; +import com.mpush.api.event.UserOfflineEvent; import com.mpush.api.event.UserOnlineEvent; +import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.handler.BindValidator; +import com.mpush.api.spi.handler.BindValidatorFactory; import com.mpush.common.handler.BaseMessageHandler; import com.mpush.common.message.BindUserMessage; import com.mpush.common.message.ErrorMessage; import com.mpush.common.message.OkMessage; +import com.mpush.common.router.RemoteRouter; +import com.mpush.common.router.RemoteRouterManager; +import com.mpush.core.MPushServer; +import com.mpush.core.router.LocalRouter; +import com.mpush.core.router.LocalRouterManager; import com.mpush.core.router.RouterCenter; import com.mpush.tools.event.EventBus; import com.mpush.tools.log.Logs; @@ -38,6 +48,14 @@ * @author ohun@live.cn */ public final class BindUserHandler extends BaseMessageHandler { + private final BindValidator bindValidator = BindValidatorFactory.create(); + + private RouterCenter routerCenter; + + public BindUserHandler(MPushServer mPushServer) { + this.routerCenter = mPushServer.getRouterCenter(); + this.bindValidator.init(mPushServer); + } @Override public BindUserMessage decode(Packet packet, Connection connection) { @@ -46,30 +64,121 @@ public BindUserMessage decode(Packet packet, Connection connection) { @Override public void handle(BindUserMessage message) { + if (message.getPacket().cmd == Command.BIND.cmd) { + bind(message); + } else { + unbind(message); + } + } + + private void bind(BindUserMessage message) { if (Strings.isNullOrEmpty(message.userId)) { ErrorMessage.from(message).setReason("invalid param").close(); - Logs.Conn.info("bind user failure for invalid param, session={}", message.getConnection().getSessionContext()); + Logs.CONN.error("bind user failure for invalid param, conn={}", message.getConnection()); return; } //1.绑定用户时先看下是否握手成功 SessionContext context = message.getConnection().getSessionContext(); if (context.handshakeOk()) { - //2.如果握手成功,就把用户链接信息注册到路由中心,本地和远程各一份 - boolean success = RouterCenter.I.register(message.userId, message.getConnection()); + //处理重复绑定问题 + if (context.userId != null) { + if (message.userId.equals(context.userId)) { + context.tags = message.tags; + OkMessage.from(message).setData("bind success").sendRaw(); + Logs.CONN.info("rebind user success, userId={}, session={}", message.userId, context); + return; + } else { + unbind(message); + } + } + + //验证用户身份 + boolean success = bindValidator.validate(message.userId, message.data); + if (success) { + //2.如果握手成功,就把用户链接信息注册到路由中心,本地和远程各一份 + success = routerCenter.register(message.userId, message.getConnection()); + } + if (success) { context.userId = message.userId; - EventBus.I.post(new UserOnlineEvent(message.getConnection(), message.userId)); - OkMessage.from(message).setData("bind success").send(); - Logs.Conn.info("bind user success, userId={}, session={}", message.userId, context); + context.tags = message.tags; + EventBus.post(new UserOnlineEvent(message.getConnection(), message.userId)); + OkMessage.from(message).setData("bind success").sendRaw(); + Logs.CONN.info("bind user success, userId={}, session={}", message.userId, context); } else { //3.注册失败再处理下,防止本地注册成功,远程注册失败的情况,只有都成功了才叫成功 - RouterCenter.I.unRegister(message.userId, context.getClientType()); + routerCenter.unRegister(message.userId, context.getClientType()); ErrorMessage.from(message).setReason("bind failed").close(); - Logs.Conn.info("bind user failure, userId={}, session={}", message.userId, context); + Logs.CONN.info("bind user failure, userId={}, session={}", message.userId, context); } } else { ErrorMessage.from(message).setReason("not handshake").close(); - Logs.Conn.info("bind user failure for not handshake, userId={}, session={}", message.userId, context); + Logs.CONN.error("bind user failure not handshake, userId={}, conn={}", message.userId, message.getConnection()); + } + } + + /** + * 目前是以用户维度来存储路由信息的,所以在删除路由信息时要判断下是否是同一个设备 + * 后续可以修改为按设备来存储路由信息。 + * + * @param message + */ + private void unbind(BindUserMessage message) { + if (Strings.isNullOrEmpty(message.userId)) { + ErrorMessage.from(message).setReason("invalid param").close(); + Logs.CONN.error("unbind user failure invalid param, session={}", message.getConnection().getSessionContext()); + return; + } + //1.解绑用户时先看下是否握手成功 + SessionContext context = message.getConnection().getSessionContext(); + if (context.handshakeOk()) { + //2.先删除远程路由, 必须是同一个设备才允许解绑 + boolean unRegisterSuccess = true; + int clientType = context.getClientType(); + String userId = context.userId; + RemoteRouterManager remoteRouterManager = routerCenter.getRemoteRouterManager(); + RemoteRouter remoteRouter = remoteRouterManager.lookup(userId, clientType); + if (remoteRouter != null) { + String deviceId = remoteRouter.getRouteValue().getDeviceId(); + if (context.deviceId.equals(deviceId)) {//判断是否是同一个设备 + unRegisterSuccess = remoteRouterManager.unRegister(userId, clientType); + } + } + //3.删除本地路由信息 + LocalRouterManager localRouterManager = routerCenter.getLocalRouterManager(); + LocalRouter localRouter = localRouterManager.lookup(userId, clientType); + if (localRouter != null) { + String deviceId = localRouter.getRouteValue().getSessionContext().deviceId; + if (context.deviceId.equals(deviceId)) {//判断是否是同一个设备 + unRegisterSuccess = localRouterManager.unRegister(userId, clientType) && unRegisterSuccess; + } + } + + //4.路由删除成功,广播用户下线事件 + if (unRegisterSuccess) { + context.userId = null; + context.tags = null; + EventBus.post(new UserOfflineEvent(message.getConnection(), userId)); + OkMessage.from(message).setData("unbind success").sendRaw(); + Logs.CONN.info("unbind user success, userId={}, session={}", userId, context); + } else { + ErrorMessage.from(message).setReason("unbind failed").sendRaw(); + Logs.CONN.error("unbind user failure, unRegister router failure, userId={}, session={}", userId, context); + } + } else { + ErrorMessage.from(message).setReason("not handshake").close(); + Logs.CONN.error("unbind user failure not handshake, userId={}, session={}", message.userId, context); + } + } + + + @Spi(order = 1) + public static class DefaultBindValidatorFactory implements BindValidatorFactory { + private final BindValidator validator = (userId, data) -> true; + + @Override + public BindValidator get() { + return validator; } } } diff --git a/mpush-core/src/main/java/com/mpush/core/handler/ClientPushHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/ClientPushHandler.java new file mode 100644 index 00000000..bd73c315 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/handler/ClientPushHandler.java @@ -0,0 +1,61 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.handler; + + +import com.mpush.api.message.MessageHandler; +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Packet; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.handler.PushHandlerFactory; +import com.mpush.common.handler.BaseMessageHandler; +import com.mpush.common.message.AckMessage; +import com.mpush.common.message.PushMessage; +import com.mpush.tools.log.Logs; + +/** + * Created by ohun on 2015/12/30. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 1) +public final class ClientPushHandler extends BaseMessageHandler implements PushHandlerFactory { + + @Override + public PushMessage decode(Packet packet, Connection connection) { + return new PushMessage(packet, connection); + } + + @Override + public void handle(PushMessage message) { + Logs.PUSH.info("receive client push message={}", message); + + if (message.autoAck()) { + AckMessage.from(message).sendRaw(); + Logs.PUSH.info("send ack for push message={}", message); + } + //biz code write here + } + + @Override + public MessageHandler get() { + return this; + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/handler/FastConnectHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/FastConnectHandler.java index bdf0abba..d650f7a9 100644 --- a/mpush-core/src/main/java/com/mpush/core/handler/FastConnectHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/handler/FastConnectHandler.java @@ -25,9 +25,11 @@ import com.mpush.common.message.ErrorMessage; import com.mpush.common.message.FastConnectMessage; import com.mpush.common.message.FastConnectOkMessage; +import com.mpush.core.MPushServer; import com.mpush.core.session.ReusableSession; import com.mpush.core.session.ReusableSessionManager; -import com.mpush.tools.config.ConfigManager; +import com.mpush.tools.common.Profiler; +import com.mpush.tools.config.ConfigTools; import com.mpush.tools.log.Logs; /** @@ -36,6 +38,11 @@ * @author ohun@live.cn */ public final class FastConnectHandler extends BaseMessageHandler { + private final ReusableSessionManager reusableSessionManager; + + public FastConnectHandler(MPushServer mPushServer) { + this.reusableSessionManager = mPushServer.getReusableSessionManager(); + } @Override public FastConnectMessage decode(Packet packet, Connection connection) { @@ -45,30 +52,32 @@ public FastConnectMessage decode(Packet packet, Connection connection) { @Override public void handle(FastConnectMessage message) { //从缓存中心查询session - ReusableSession session = ReusableSessionManager.INSTANCE.querySession(message.sessionId); - + Profiler.enter("time cost on [query session]"); + ReusableSession session = reusableSessionManager.querySession(message.sessionId); + Profiler.release(); if (session == null) { //1.没查到说明session已经失效了 ErrorMessage.from(message).setReason("session expired").send(); - Logs.Conn.info("fast connect failure, session is expired, sessionId={}, deviceId={}" - , message.sessionId, message.deviceId); + Logs.CONN.warn("fast connect failure, session is expired, sessionId={}, deviceId={}, conn={}" + , message.sessionId, message.deviceId, message.getConnection().getChannel()); } else if (!session.context.deviceId.equals(message.deviceId)) { //2.非法的设备, 当前设备不是上次生成session时的设备 ErrorMessage.from(message).setReason("invalid device").send(); - Logs.Conn.info("fast connect failure, not the same device, deviceId={}, session={}" - , message.deviceId, session.context); + Logs.CONN.warn("fast connect failure, not the same device, deviceId={}, session={}, conn={}" + , message.deviceId, session.context, message.getConnection().getChannel()); } else { //3.校验成功,重新计算心跳,完成快速重连 - int heartbeat = ConfigManager.I.getHeartbeat(message.minHeartbeat, message.maxHeartbeat); + int heartbeat = ConfigTools.getHeartbeat(message.minHeartbeat, message.maxHeartbeat); session.context.setHeartbeat(heartbeat); message.getConnection().setSessionContext(session.context); - + Profiler.enter("time cost on [send FastConnectOkMessage]"); FastConnectOkMessage .from(message) .setHeartbeat(heartbeat) - .send(); - Logs.Conn.info("fast connect success, session={}", session.context); + .sendRaw(); + Profiler.release(); + Logs.CONN.info("fast connect success, session={}", session.context); } } } diff --git a/mpush-core/src/main/java/com/mpush/core/handler/GatewayKickUserHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/GatewayKickUserHandler.java new file mode 100644 index 00000000..4ecb03b8 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/handler/GatewayKickUserHandler.java @@ -0,0 +1,51 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.handler; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Packet; +import com.mpush.common.handler.BaseMessageHandler; +import com.mpush.common.message.gateway.GatewayKickUserMessage; +import com.mpush.core.router.RouterCenter; +import com.mpush.core.router.RouterChangeListener; + +/** + * Created by ohun on 16/10/23. + * + * @author ohun@live.cn (夜色) + */ +public final class GatewayKickUserHandler extends BaseMessageHandler { + + private final RouterCenter routerCenter; + + public GatewayKickUserHandler(RouterCenter routerCenter) { + this.routerCenter = routerCenter; + } + + @Override + public GatewayKickUserMessage decode(Packet packet, Connection connection) { + return new GatewayKickUserMessage(packet, connection); + } + + @Override + public void handle(GatewayKickUserMessage message) { + routerCenter.getRouterChangeListener().onReceiveKickRemoteMsg(message); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/handler/GatewayPushHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/GatewayPushHandler.java index dc7cfe78..03cd2297 100644 --- a/mpush-core/src/main/java/com/mpush/core/handler/GatewayPushHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/handler/GatewayPushHandler.java @@ -22,17 +22,8 @@ import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; import com.mpush.common.handler.BaseMessageHandler; -import com.mpush.common.message.ErrorMessage; -import com.mpush.common.message.OkMessage; -import com.mpush.common.message.PushMessage; import com.mpush.common.message.gateway.GatewayPushMessage; -import com.mpush.common.router.RemoteRouter; -import com.mpush.core.router.LocalRouter; -import com.mpush.core.router.RouterCenter; -import com.mpush.tools.Utils; -import com.mpush.tools.log.Logs; - -import static com.mpush.common.ErrorCode.*; +import com.mpush.core.push.PushCenter; /** * Created by ohun on 2015/12/30. @@ -41,124 +32,19 @@ */ public final class GatewayPushHandler extends BaseMessageHandler { + private final PushCenter pushCenter; + + public GatewayPushHandler(PushCenter pushCenter) { + this.pushCenter = pushCenter; + } + @Override public GatewayPushMessage decode(Packet packet, Connection connection) { return new GatewayPushMessage(packet, connection); } - /** - * 处理PushClient发送过来的Push推送请求 - *

- * 查寻路由策略,先查本地路由,本地不存在,查远程,(注意:有可能远程查到也是本机IP) - *

- * 正常情况本地路由应该存在,如果不存在或链接失效,有以下几种情况: - *

- * 1.客户端重连,并且链接到了其他机器 - * 2.客户端下线,本地路由失效,远程路由还未清除 - * 3.PushClient使用了本地缓存,但缓存数据已经和实际情况不一致了 - *

- * 对于三种情况的处理方式是, 再重新查寻下远程路由: - * 1.如果发现远程路由是本机,直接删除,因为此时的路由已失效 (解决场景2) - * 2.如果用户真在另一台机器,让PushClient清理下本地缓存后,重新推送 (解决场景1,3) - *

- * - * @param message - */ @Override public void handle(GatewayPushMessage message) { - if (!checkLocal(message)) { - checkRemote(message); - } - } - - /** - * 检查本地路由,如果存在并且链接可用直接推送 - * 否则要检查下远程路由 - * - * @param message - * @return - */ - private boolean checkLocal(final GatewayPushMessage message) { - String userId = message.userId; - int deviceId = message.clientType; - LocalRouter router = RouterCenter.I.getLocalRouterManager().lookup(userId, deviceId); - - //1.如果本机不存在,再查下远程,看用户是否登陆到其他机器 - if (router == null) return false; - - Connection connection = router.getRouteValue(); - - //2.如果链接失效,先删除本地失效的路由,再查下远程路由,看用户是否登陆到其他机器 - if (!connection.isConnected()) { - - Logs.PUSH.info("gateway push, router in local but disconnect, message={}", message, connection); - - //删除已经失效的本地路由 - RouterCenter.I.getLocalRouterManager().unRegister(userId, deviceId); - - return false; - } - - //3.链接可用,直接下发消息到手机客户端 - PushMessage pushMessage = new PushMessage(message.content, connection); - - pushMessage.send(future -> { - if (future.isSuccess()) { - //推送成功 - OkMessage.from(message).setData(userId + ',' + deviceId).send(); - - Logs.PUSH.info("gateway push message to client success, message={}", message); - - } else { - //推送失败 - ErrorMessage.from(message).setErrorCode(PUSH_CLIENT_FAILURE).setData(userId + ',' + deviceId).send(); - - Logs.PUSH.info("gateway push message to client failure, message={}", message); - } - }); - return true; - } - - /** - * 检测远程路由, - * 如果不存在直接返回用户已经下线 - * 如果是本机直接删除路由信息 - * 如果是其他机器让PushClient重推 - * - * @param message - */ - private void checkRemote(GatewayPushMessage message) { - String userId = message.userId; - int clientType = message.clientType; - RemoteRouter router = RouterCenter.I.getRemoteRouterManager().lookup(userId, clientType); - - // 1.如果远程路由信息也不存在, 说明用户此时不在线, - if (router == null) { - - ErrorMessage.from(message).setErrorCode(OFFLINE).setData(userId + ',' + clientType).send(); - - Logs.PUSH.info("gateway push, router not exists user offline, message={}", message); - - return; - } - - //2.如果查出的远程机器是当前机器,说明路由已经失效,此时用户已下线,需要删除失效的缓存 - if (Utils.getLocalIp().equals(router.getRouteValue().getHost())) { - - ErrorMessage.from(message).setErrorCode(OFFLINE).setData(userId + ',' + clientType).send(); - - //删除失效的远程缓存 - RouterCenter.I.getRemoteRouterManager().unRegister(userId, clientType); - - Logs.PUSH.info("gateway push error remote is local, userId={}, clientType={}, router={}", userId, clientType, router); - - return; - } - - //3.否则说明用户已经跑到另外一台机器上了;路由信息发生更改,让PushClient重推 - ErrorMessage.from(message).setErrorCode(ROUTER_CHANGE).setData(userId + ',' + clientType).send(); - - Logs.PUSH.info("gateway push, router in remote userId={}, clientType={}, router={}", userId, clientType, router); - + pushCenter.push(message); } } diff --git a/mpush-core/src/main/java/com/mpush/core/handler/HandshakeHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/HandshakeHandler.java index 17879376..1dba82e9 100644 --- a/mpush-core/src/main/java/com/mpush/core/handler/HandshakeHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/handler/HandshakeHandler.java @@ -22,7 +22,6 @@ import com.google.common.base.Strings; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; -import com.mpush.api.event.HandshakeEvent; import com.mpush.api.protocol.Packet; import com.mpush.common.handler.BaseMessageHandler; import com.mpush.common.message.ErrorMessage; @@ -30,10 +29,10 @@ import com.mpush.common.message.HandshakeOkMessage; import com.mpush.common.security.AesCipher; import com.mpush.common.security.CipherBox; +import com.mpush.core.MPushServer; import com.mpush.core.session.ReusableSession; import com.mpush.core.session.ReusableSessionManager; -import com.mpush.tools.config.ConfigManager; -import com.mpush.tools.event.EventBus; +import com.mpush.tools.config.ConfigTools; import com.mpush.tools.log.Logs; /** @@ -43,6 +42,12 @@ */ public final class HandshakeHandler extends BaseMessageHandler { + private final ReusableSessionManager reusableSessionManager; + + public HandshakeHandler(MPushServer mPushServer) { + this.reusableSessionManager = mPushServer.getReusableSessionManager(); + } + @Override public HandshakeMessage decode(Packet packet, Connection connection) { return new HandshakeMessage(packet, connection); @@ -50,7 +55,14 @@ public HandshakeMessage decode(Packet packet, Connection connection) { @Override public void handle(HandshakeMessage message) { + if (message.getConnection().getSessionContext().isSecurity()) { + doSecurity(message); + } else { + doInsecurity(message); + } + } + private void doSecurity(HandshakeMessage message) { byte[] iv = message.iv;//AES密钥向量16位 byte[] clientKey = message.clientKey;//客户端随机数16位 byte[] serverKey = CipherBox.I.randomAESKey();//服务端随机数16位 @@ -61,14 +73,15 @@ public void handle(HandshakeMessage message) { || iv.length != CipherBox.I.getAesKeyLength() || clientKey.length != CipherBox.I.getAesKeyLength()) { ErrorMessage.from(message).setReason("Param invalid").close(); - Logs.Conn.info("handshake failure, message={}", message.toString()); + Logs.CONN.error("handshake failure, message={}, conn={}", message, message.getConnection()); return; } //2.重复握手判断 SessionContext context = message.getConnection().getSessionContext(); if (message.deviceId.equals(context.deviceId)) { - Logs.Conn.info("handshake failure, repeat handshake, session={}", message.getConnection().getSessionContext()); + ErrorMessage.from(message).setReason("repeat handshake").send(); + Logs.CONN.warn("handshake failure, repeat handshake, conn={}", message.getConnection()); return; } @@ -76,10 +89,10 @@ public void handle(HandshakeMessage message) { context.changeCipher(new AesCipher(clientKey, iv)); //4.生成可复用session, 用于快速重连 - ReusableSession session = ReusableSessionManager.INSTANCE.genSession(context); + ReusableSession session = reusableSessionManager.genSession(context); //5.计算心跳时间 - int heartbeat = ConfigManager.I.getHeartbeat(message.minHeartbeat, message.maxHeartbeat); + int heartbeat = ConfigTools.getHeartbeat(message.minHeartbeat, message.maxHeartbeat); //6.响应握手成功消息 HandshakeOkMessage @@ -101,10 +114,39 @@ public void handle(HandshakeMessage message) { .setHeartbeat(heartbeat); //9.保存可复用session到Redis, 用于快速重连 - ReusableSessionManager.INSTANCE.cacheSession(session); + reusableSessionManager.cacheSession(session); + + Logs.CONN.info("handshake success, conn={}", message.getConnection()); + } + + private void doInsecurity(HandshakeMessage message) { + + //1.校验客户端消息字段 + if (Strings.isNullOrEmpty(message.deviceId)) { + ErrorMessage.from(message).setReason("Param invalid").close(); + Logs.CONN.error("handshake failure, message={}, conn={}", message, message.getConnection()); + return; + } + + //2.重复握手判断 + SessionContext context = message.getConnection().getSessionContext(); + if (message.deviceId.equals(context.deviceId)) { + ErrorMessage.from(message).setReason("repeat handshake").send(); + Logs.CONN.warn("handshake failure, repeat handshake, conn={}", message.getConnection()); + return; + } + + //6.响应握手成功消息 + HandshakeOkMessage.from(message).send(); + + //8.保存client信息到当前连接 + context.setOsName(message.osName) + .setOsVersion(message.osVersion) + .setClientVersion(message.clientVersion) + .setDeviceId(message.deviceId) + .setHeartbeat(Integer.MAX_VALUE); + + Logs.CONN.info("handshake success, conn={}", message.getConnection()); - //10.触发握手成功事件 - EventBus.I.post(new HandshakeEvent(message.getConnection(), heartbeat)); - Logs.Conn.info("handshake success, session={}", context); } } \ No newline at end of file diff --git a/mpush-core/src/main/java/com/mpush/core/handler/HeartBeatHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/HeartBeatHandler.java index 88ffaf18..2b96b159 100644 --- a/mpush-core/src/main/java/com/mpush/core/handler/HeartBeatHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/handler/HeartBeatHandler.java @@ -19,7 +19,7 @@ package com.mpush.core.handler; -import com.mpush.api.MessageHandler; +import com.mpush.api.message.MessageHandler; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; import com.mpush.tools.log.Logs; diff --git a/mpush-core/src/main/java/com/mpush/core/handler/HttpProxyHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/HttpProxyHandler.java index efc014e2..9fde581d 100644 --- a/mpush-core/src/main/java/com/mpush/core/handler/HttpProxyHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/handler/HttpProxyHandler.java @@ -22,22 +22,22 @@ import com.google.common.base.Strings; import com.mpush.api.connection.Connection; import com.mpush.api.protocol.Packet; -import com.mpush.api.spi.SpiLoader; import com.mpush.api.spi.net.DnsMapping; import com.mpush.api.spi.net.DnsMappingManager; import com.mpush.common.handler.BaseMessageHandler; import com.mpush.common.message.HttpRequestMessage; import com.mpush.common.message.HttpResponseMessage; -import com.mpush.common.net.HttpProxyDnsMappingManager; +import com.mpush.core.MPushServer; import com.mpush.netty.http.HttpCallback; import com.mpush.netty.http.HttpClient; import com.mpush.netty.http.RequestContext; import com.mpush.tools.common.Profiler; -import com.mpush.tools.config.CC; import com.mpush.tools.log.Logs; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.*; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; import java.net.MalformedURLException; @@ -53,13 +53,12 @@ * @author ohun@live.cn */ public class HttpProxyHandler extends BaseMessageHandler { - private static final Logger LOGGER = Logs.HTTP; + private static final Logger LOGGER = LoggerFactory.getLogger(HttpProxyHandler.class); + private final DnsMappingManager dnsMappingManager = DnsMappingManager.create(); private final HttpClient httpClient; - private final DnsMappingManager dnsMappingManager = SpiLoader.load(DnsMappingManager.class, CC.mp.spi.dns_mapping_manager); - public HttpProxyHandler(HttpClient httpClient) { - this.httpClient = httpClient; - this.httpClient.start(); + public HttpProxyHandler(MPushServer mPushServer) { + this.httpClient = mPushServer.getHttpClient(); } @Override @@ -70,7 +69,6 @@ public HttpRequestMessage decode(Packet packet, Connection connection) { @Override public void handle(HttpRequestMessage message) { try { - Profiler.enter("start http proxy handler"); //1.参数校验 String method = message.getMethod(); String uri = message.uri; @@ -80,17 +78,18 @@ public void handle(HttpRequestMessage message) { .setStatusCode(400) .setReasonPhrase("Bad Request") .sendRaw(); - LOGGER.warn("request url is empty!"); + Logs.HTTP.warn("receive bad request url is empty, request={}", message); } //2.url转换 uri = doDnsMapping(uri); + Profiler.enter("time cost on [create FullHttpRequest]"); //3.包装成HTTP request - FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, HttpMethod.valueOf(method), uri); + FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, HttpMethod.valueOf(method), uri, getBody(message)); setHeaders(request, message);//处理header - setBody(request, message);//处理body + Profiler.enter("time cost on [HttpClient.request]"); //4.发送请求 httpClient.request(new RequestContext(request, new DefaultHttpCallback(message))); } catch (Exception e) { @@ -100,6 +99,7 @@ public void handle(HttpRequestMessage message) { .setReasonPhrase("Bad Gateway") .sendRaw(); LOGGER.error("send request ex, message=" + message, e); + Logs.HTTP.error("send proxy request ex, request={}, error={}", message, e.getMessage()); } finally { Profiler.release(); } @@ -118,9 +118,9 @@ public void onResponse(HttpResponse httpResponse) { HttpResponseMessage response = HttpResponseMessage .from(request) .setStatusCode(httpResponse.status().code()) - .setReasonPhrase(httpResponse.status().reasonPhrase().toString()); - for (Map.Entry entry : httpResponse.headers()) { - response.addHeader(entry.getKey().toString(), entry.getValue().toString()); + .setReasonPhrase(httpResponse.status().reasonPhrase()); + for (Map.Entry entry : httpResponse.headers()) { + response.addHeader(entry.getKey(), entry.getValue()); } if (httpResponse instanceof FullHttpResponse) { @@ -133,7 +133,7 @@ public void onResponse(HttpResponse httpResponse) { } } response.send(); - LOGGER.debug("callback success request={}, response={}", request, response); + Logs.HTTP.info("send proxy request success end request={}, response={}", request, response); } @Override @@ -143,7 +143,7 @@ public void onFailure(int statusCode, String reasonPhrase) { .setStatusCode(statusCode) .setReasonPhrase(reasonPhrase) .sendRaw(); - LOGGER.warn("callback failure request={}, response={}", request, statusCode + ":" + reasonPhrase); + Logs.HTTP.warn("send proxy request failure end request={}, response={}", request, statusCode + ":" + reasonPhrase); } @Override @@ -153,7 +153,9 @@ public void onException(Throwable throwable) { .setStatusCode(502) .setReasonPhrase("Bad Gateway") .sendRaw(); - LOGGER.error("callback exception request={}, response={}", request, 502, throwable); + + LOGGER.error("send proxy request ex end request={}, response={}", request, 502, throwable); + Logs.HTTP.error("send proxy request ex end request={}, response={}, error={}", request, 502, throwable.getMessage()); } @Override @@ -163,7 +165,7 @@ public void onTimeout() { .setStatusCode(408) .setReasonPhrase("Request Timeout") .sendRaw(); - LOGGER.warn("callback timeout request={}, response={}", request, 408); + Logs.HTTP.warn("send proxy request timeout end request={}, response={}", request, 408); } @Override @@ -181,18 +183,19 @@ private void setHeaders(FullHttpRequest request, HttpRequestMessage message) { httpHeaders.add(entry.getKey(), entry.getValue()); } } + + if (message.body != null && message.body.length > 0) { + request.headers().add(CONTENT_LENGTH, Integer.toString(message.body.length)); + } + InetSocketAddress remoteAddress = (InetSocketAddress) message.getConnection().getChannel().remoteAddress(); String remoteIp = remoteAddress.getAddress().getHostAddress();//这个要小心,不要使用getHostName,不然会耗时比较大 request.headers().add("x-forwarded-for", remoteIp); request.headers().add("x-forwarded-port", Integer.toString(remoteAddress.getPort())); } - private void setBody(FullHttpRequest request, HttpRequestMessage message) { - byte[] body = message.body; - if (body != null && body.length > 0) { - request.content().writeBytes(body); - request.headers().add(CONTENT_LENGTH, Integer.toString(body.length)); - } + private ByteBuf getBody(HttpRequestMessage message) { + return message.body == null ? Unpooled.EMPTY_BUFFER : Unpooled.wrappedBuffer(message.body); } private String doDnsMapping(String url) { @@ -200,6 +203,7 @@ private String doDnsMapping(String url) { try { uri = new URL(url); } catch (MalformedURLException e) { + //ignore e } if (uri == null) { return url; diff --git a/mpush-core/src/main/java/com/mpush/core/handler/UnbindUserHandler.java b/mpush-core/src/main/java/com/mpush/core/handler/UnbindUserHandler.java deleted file mode 100644 index ad8739e8..00000000 --- a/mpush-core/src/main/java/com/mpush/core/handler/UnbindUserHandler.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.core.handler; - -import com.google.common.base.Strings; -import com.mpush.api.connection.Connection; -import com.mpush.api.connection.SessionContext; -import com.mpush.api.event.UserOfflineEvent; -import com.mpush.api.protocol.Packet; -import com.mpush.common.handler.BaseMessageHandler; -import com.mpush.common.message.BindUserMessage; -import com.mpush.common.message.ErrorMessage; -import com.mpush.common.message.OkMessage; -import com.mpush.common.router.RemoteRouter; -import com.mpush.common.router.RemoteRouterManager; -import com.mpush.core.router.LocalRouter; -import com.mpush.core.router.LocalRouterManager; -import com.mpush.core.router.RouterCenter; -import com.mpush.tools.event.EventBus; -import com.mpush.tools.log.Logs; - - -/** - * Created by ohun on 2015/12/23. - * - * @author ohun@live.cn - */ -public final class UnbindUserHandler extends BaseMessageHandler { - - @Override - public BindUserMessage decode(Packet packet, Connection connection) { - return new BindUserMessage(packet, connection); - } - - /** - * 目前是以用户维度来存储路由信息的,所以在删除路由信息时要判断下是否是同一个设备 - * 后续可以修改为按设备来存储路由信息。 - * - * @param message - */ - @Override - public void handle(BindUserMessage message) { - if (Strings.isNullOrEmpty(message.userId)) { - ErrorMessage.from(message).setReason("invalid param").close(); - Logs.Conn.info("unbind user failure invalid param, session={}", message.getConnection().getSessionContext()); - return; - } - - //1.解绑用户时先看下是否握手成功 - SessionContext context = message.getConnection().getSessionContext(); - if (context.handshakeOk()) { - //2.先删除远程路由, 必须是同一个设备才允许解绑 - boolean unRegisterSuccess = true; - int clientType = context.getClientType(); - String userId = message.userId; - RemoteRouterManager remoteRouterManager = RouterCenter.I.getRemoteRouterManager(); - RemoteRouter remoteRouter = remoteRouterManager.lookup(userId, clientType); - if (remoteRouter != null) { - String deviceId = remoteRouter.getRouteValue().getDeviceId(); - if (context.deviceId.equals(deviceId)) {//判断是否是同一个设备 - unRegisterSuccess = remoteRouterManager.unRegister(userId, clientType); - } - } - - //3.删除本地路由信息 - LocalRouterManager localRouterManager = RouterCenter.I.getLocalRouterManager(); - LocalRouter localRouter = localRouterManager.lookup(userId, clientType); - if (localRouter != null) { - String deviceId = localRouter.getRouteValue().getSessionContext().deviceId; - if (context.deviceId.equals(deviceId)) {//判断是否是同一个设备 - unRegisterSuccess = localRouterManager.unRegister(userId, clientType) && unRegisterSuccess; - } - } - - //4.路由删除成功,广播用户下线事件 - if (unRegisterSuccess) { - context.userId = null; - EventBus.I.post(new UserOfflineEvent(message.getConnection(), userId)); - OkMessage.from(message).setData("unbind success").send(); - Logs.Conn.info("unbind user success, userId={}, session={}", userId, context); - } else { - ErrorMessage.from(message).setReason("unbind failed").send(); - Logs.Conn.info("unbind user failure, register router failure, userId={}, session={}", userId, context); - } - } else { - ErrorMessage.from(message).setReason("not handshake").close(); - Logs.Conn.info("unbind user failure not handshake, userId={}, session={}", message.userId, context); - } - } -} diff --git a/mpush-cache/src/main/java/com/mpush/cache/redis/hash/Node.java b/mpush-core/src/main/java/com/mpush/core/mq/MQClient.java similarity index 54% rename from mpush-cache/src/main/java/com/mpush/cache/redis/hash/Node.java rename to mpush-core/src/main/java/com/mpush/core/mq/MQClient.java index 295dfbf3..d1e62626 100644 --- a/mpush-cache/src/main/java/com/mpush/cache/redis/hash/Node.java +++ b/mpush-core/src/main/java/com/mpush/core/mq/MQClient.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,35 +14,34 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.cache.redis.hash; +package com.mpush.core.mq; -public class Node { +import java.util.Collection; +import java.util.Collections; - private String ip; //机器ip - private String name;//名字 +/** + * Created by ohun on 2016/12/26. + * + * @author ohun@live.cn (夜色) + */ +public final class MQClient { - public Node(String ip, String name) { - this.ip = ip; - this.name = name; - } + public void init() { - public String getIp() { - return ip; } - public void setIp(String ip) { - this.ip = ip; - } + public void subscribe(String topic, MQMessageReceiver listener) { - public String getName() { - return name; } - public void setName(String name) { - this.name = name; + public void publish(String topic, MQPushMessage message) { + } + public Collection take(String topic) { + return Collections.emptyList(); + } } diff --git a/mpush-core/src/main/java/com/mpush/core/mq/MQMessageReceiver.java b/mpush-core/src/main/java/com/mpush/core/mq/MQMessageReceiver.java new file mode 100644 index 00000000..e2721d0c --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/mq/MQMessageReceiver.java @@ -0,0 +1,75 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.mq; + +import com.mpush.core.push.PushCenter; +import com.mpush.tools.Utils; +import com.mpush.tools.config.ConfigTools; +import com.mpush.monitor.service.ThreadPoolManager; + +import java.util.Collection; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +public final class MQMessageReceiver { + + private final static String TOPIC = "/mpush/push/" + ConfigTools.getLocalIp(); + + private final MQClient mqClient; + + private PushCenter pushCenter; + + public static void subscribe(MQClient mqClient, PushCenter pushCenter) { + MQMessageReceiver receiver = new MQMessageReceiver(mqClient, pushCenter); + mqClient.subscribe(TOPIC, receiver); + receiver.fetchFormMQ(); + } + + public MQMessageReceiver(MQClient mqClient, PushCenter pushCenter) { + this.mqClient = mqClient; + this.pushCenter = pushCenter; + } + + public void onMessage(MQPushMessage message) { + pushCenter.push(message); + } + + public void fetchFormMQ() { + Utils.newThread("mq-push", this::dispatch); + } + + private void dispatch() { + try { + while (true) { + Collection message = mqClient.take(TOPIC); + if (message == null || message.isEmpty()) { + Thread.sleep(100); + continue; + } + message.forEach(this::onMessage); + } + } catch (InterruptedException e) { + this.dispatch(); + } + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/mq/MQPushListener.java b/mpush-core/src/main/java/com/mpush/core/mq/MQPushListener.java new file mode 100644 index 00000000..27f76c28 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/mq/MQPushListener.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.mq; + +import com.mpush.api.MPushContext; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.push.PushListener; +import com.mpush.api.spi.push.PushListenerFactory; +import com.mpush.core.MPushServer; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 2) +public final class MQPushListener implements PushListener, PushListenerFactory { + private final MQClient mqClient = new MQClient(); + + @Override + public void init(MPushContext context) { + mqClient.init(); + MQMessageReceiver.subscribe(mqClient, ((MPushServer) context).getPushCenter()); + } + + + @Override + public void onSuccess(MQPushMessage message, Object[] timePoints) { + //publish messageId to mq:[success/queue] + mqClient.publish("/mpush/push/success", message); + } + + @Override + public void onAckSuccess(MQPushMessage message, Object[] timePoints) { + //publish messageId to mq:[success/queue] + mqClient.publish("/mpush/push/success", message); + } + + @Override + public void onBroadcastComplete(MQPushMessage message, Object[] timePoints) { + //publish messageId to mq:[broadcast/finish/queue] + mqClient.publish("/mpush/push/broadcast_finish", message); + } + + @Override + public void onFailure(MQPushMessage message, Object[] timePoints) { + //publish messageId to mq:[failure/queue], client can retry + mqClient.publish("/mpush/push/failure", message); + } + + @Override + public void onOffline(MQPushMessage message, Object[] timePoints) { + //publish messageId to mq:[offline/queue], client persist offline message to db + mqClient.publish("/mpush/push/offline", message); + } + + @Override + public void onRedirect(MQPushMessage message, Object[] timePoints) { + //publish messageId to mq:[route/change/queue], client should be try again + mqClient.publish("/mpush/push/route_change", message); + } + + @Override + public void onTimeout(MQPushMessage message, Object[] timePoints) { + //publish messageId to mq:[ack/timeout/queue], client can retry + mqClient.publish("/mpush/push/ack_timeout", message); + } + + @Override + public PushListener get() { + return this; + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/mq/MQPushMessage.java b/mpush-core/src/main/java/com/mpush/core/mq/MQPushMessage.java new file mode 100644 index 00000000..86c0654b --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/mq/MQPushMessage.java @@ -0,0 +1,76 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.mq; + +import com.mpush.api.common.Condition; +import com.mpush.api.spi.push.IPushMessage; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +public final class MQPushMessage implements IPushMessage { + + @Override + public boolean isBroadcast() { + return false; + } + + @Override + public String getUserId() { + return null; + } + + @Override + public int getClientType() { + return 0; + } + + @Override + public byte[] getContent() { + return new byte[0]; + } + + @Override + public boolean isNeedAck() { + return false; + } + + @Override + public byte getFlags() { + return 0; + } + + @Override + public int getTimeoutMills() { + return 0; + } + + @Override + public String getTaskId() { + return null; + } + + @Override + public Condition getCondition() { + return null; + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/push/BroadcastPushTask.java b/mpush-core/src/main/java/com/mpush/core/push/BroadcastPushTask.java new file mode 100644 index 00000000..3d38a5b5 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/push/BroadcastPushTask.java @@ -0,0 +1,162 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.push; + +import com.mpush.api.message.Message; +import com.mpush.api.common.Condition; +import com.mpush.api.connection.Connection; +import com.mpush.api.connection.SessionContext; +import com.mpush.api.spi.push.IPushMessage; +import com.mpush.common.condition.AwaysPassCondition; +import com.mpush.common.message.PushMessage; +import com.mpush.common.qps.FlowControl; +import com.mpush.common.qps.OverFlowException; +import com.mpush.core.MPushServer; +import com.mpush.core.router.LocalRouter; +import com.mpush.tools.common.TimeLine; +import com.mpush.tools.log.Logs; +import io.netty.channel.ChannelFuture; + +import java.util.*; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class BroadcastPushTask implements PushTask { + + private final long begin = System.currentTimeMillis(); + + private final AtomicInteger finishTasks = new AtomicInteger(0); + + private final TimeLine timeLine = new TimeLine(); + + private final Set successUserIds = new HashSet<>(1024); + + private final FlowControl flowControl; + + private final IPushMessage message; + + private final Condition condition; + + private final MPushServer mPushServer; + + //使用Iterator, 记录任务遍历到的位置,因为有流控,一次任务可能会被分批发送,而且还有在推送过程中上/下线的用户 + private final Iterator>> iterator; + + public BroadcastPushTask(MPushServer mPushServer, IPushMessage message, FlowControl flowControl) { + this.mPushServer = mPushServer; + this.message = message; + this.flowControl = flowControl; + this.condition = message.getCondition(); + this.iterator = mPushServer.getRouterCenter().getLocalRouterManager().routers().entrySet().iterator(); + this.timeLine.begin("push-center-begin"); + } + + @Override + public void run() { + flowControl.reset(); + boolean done = broadcast(); + if (done) {//done 广播结束 + if (finishTasks.addAndGet(flowControl.total()) == 0) { + report(); + } + } else {//没有结束,就延时进行下次任务 TODO 考虑优先级问题 + mPushServer.getPushCenter().delayTask(flowControl.getDelay(), this); + } + flowControl.end(successUserIds.toArray(new String[successUserIds.size()])); + } + + private boolean broadcast() { + try { + iterator.forEachRemaining(entry -> { + + String userId = entry.getKey(); + entry.getValue().forEach((clientType, router) -> { + + Connection connection = router.getRouteValue(); + + if (checkCondition(condition, connection)) {//1.条件检测 + if (connection.isConnected()) { + if (connection.getChannel().isWritable()) { //检测TCP缓冲区是否已满且写队列超过最高阀值 + PushMessage + .build(connection) + .setContent(message.getContent()) + .send(future -> operationComplete(future, userId)); + //4. 检测qps, 是否超过流控限制,如果超过则结束当前循环直接进入catch + if (!flowControl.checkQps()) { + throw new OverFlowException(false); + } + } + } else { //2.如果链接失效,先删除本地失效的路由,再查下远程路由,看用户是否登陆到其他机器 + Logs.PUSH.warn("[Broadcast] find router in local but conn disconnect, message={}, conn={}", message, connection); + //删除已经失效的本地路由 + mPushServer.getRouterCenter().getLocalRouterManager().unRegister(userId, clientType); + } + } + + }); + + }); + } catch (OverFlowException e) { + //超出最大限制,或者遍历完毕,结束广播 + return e.isOverMaxLimit() || !iterator.hasNext(); + } + return !iterator.hasNext();//遍历完毕, 广播结束 + } + + private void report() { + Logs.PUSH.info("[Broadcast] task finished, cost={}, message={}", (System.currentTimeMillis() - begin), message); + mPushServer.getPushCenter().getPushListener().onBroadcastComplete(message, timeLine.end().getTimePoints());//通知发送方,广播推送完毕 + } + + private boolean checkCondition(Condition condition, Connection connection) { + if (condition == AwaysPassCondition.I) return true; + SessionContext sessionContext = connection.getSessionContext(); + Map env = new HashMap<>(); + env.put("userId", sessionContext.userId); + env.put("tags", sessionContext.tags); + env.put("clientVersion", sessionContext.clientVersion); + env.put("osName", sessionContext.osName); + env.put("osVersion", sessionContext.osVersion); + return condition.test(env); + } + + //@Override + private void operationComplete(ChannelFuture future, String userId) throws Exception { + if (future.isSuccess()) {//推送成功 + successUserIds.add(userId); + Logs.PUSH.info("[Broadcast] push message to client success, userId={}, message={}", message.getUserId(), message); + } else {//推送失败 + Logs.PUSH.warn("[Broadcast] push message to client failure, userId={}, message={}, conn={}", message.getUserId(), message, future.channel()); + } + if (finishTasks.decrementAndGet() == 0) { + report(); + } + } + + @Override + public ScheduledExecutorService getExecutor() { + return ((Message) message).getConnection().getChannel().eventLoop(); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/push/GatewayPushListener.java b/mpush-core/src/main/java/com/mpush/core/push/GatewayPushListener.java new file mode 100644 index 00000000..cca2c421 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/push/GatewayPushListener.java @@ -0,0 +1,205 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.push; + +import com.mpush.api.MPushContext; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.push.PushListener; +import com.mpush.api.spi.push.PushListenerFactory; +import com.mpush.common.message.ErrorMessage; +import com.mpush.common.message.OkMessage; +import com.mpush.common.message.gateway.GatewayPushMessage; +import com.mpush.core.MPushServer; +import com.mpush.tools.Jsons; +import com.mpush.tools.log.Logs; + +import java.util.concurrent.ScheduledExecutorService; + +import static com.mpush.common.ErrorCode.*; +import static com.mpush.common.push.GatewayPushResult.toJson; + +/** + * Created by ohun on 2016/12/24. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 1) +public final class GatewayPushListener implements PushListener, PushListenerFactory { + + private PushCenter pushCenter; + + @Override + public void init(MPushContext context) { + pushCenter = ((MPushServer) context).getPushCenter(); + } + + @Override + public void onSuccess(GatewayPushMessage message, Object[] timePoints) { + if (message.getConnection().isConnected()) { + pushCenter.addTask(new PushTask() { + @Override + public ScheduledExecutorService getExecutor() { + return message.getExecutor(); + } + + @Override + public void run() { + OkMessage + .from(message) + .setData(toJson(message, timePoints)) + .sendRaw(); + } + }); + } else { + Logs.PUSH.warn("push message to client success, but gateway connection is closed, timePoints={}, message={}" + , Jsons.toJson(timePoints), message); + } + } + + @Override + public void onAckSuccess(GatewayPushMessage message, Object[] timePoints) { + if (message.getConnection().isConnected()) { + pushCenter.addTask(new PushTask() { + @Override + public ScheduledExecutorService getExecutor() { + return message.getExecutor(); + } + + @Override + public void run() { + OkMessage + .from(message) + .setData(toJson(message, timePoints)) + .sendRaw(); + } + }); + + } else { + Logs.PUSH.warn("client ack success, but gateway connection is closed, timePoints={}, message={}" + , Jsons.toJson(timePoints), message); + } + } + + @Override + public void onBroadcastComplete(GatewayPushMessage message, Object[] timePoints) { + if (message.getConnection().isConnected()) { + pushCenter.addTask(new PushTask() { + @Override + public ScheduledExecutorService getExecutor() { + return message.getExecutor(); + } + + @Override + public void run() { + OkMessage + .from(message) + .sendRaw(); + } + }); + } else { + Logs.PUSH.warn("broadcast to client finish, but gateway connection is closed, timePoints={}, message={}" + , Jsons.toJson(timePoints), message); + } + } + + @Override + public void onFailure(GatewayPushMessage message, Object[] timePoints) { + if (message.getConnection().isConnected()) { + pushCenter.addTask(new PushTask() { + @Override + public ScheduledExecutorService getExecutor() { + return message.getExecutor(); + } + + @Override + public void run() { + ErrorMessage + .from(message) + .setErrorCode(PUSH_CLIENT_FAILURE) + .setData(toJson(message, timePoints)) + .sendRaw(); + } + }); + } else { + Logs.PUSH.warn("push message to client failure, but gateway connection is closed, timePoints={}, message={}" + , Jsons.toJson(timePoints), message); + } + } + + @Override + public void onOffline(GatewayPushMessage message, Object[] timePoints) { + if (message.getConnection().isConnected()) { + pushCenter.addTask(new PushTask() { + @Override + public ScheduledExecutorService getExecutor() { + return message.getExecutor(); + } + + @Override + public void run() { + ErrorMessage + .from(message) + .setErrorCode(OFFLINE) + .setData(toJson(message, timePoints)) + .sendRaw(); + } + }); + } else { + Logs.PUSH.warn("push message to client offline, but gateway connection is closed, timePoints={}, message={}" + , Jsons.toJson(timePoints), message); + } + } + + @Override + public void onRedirect(GatewayPushMessage message, Object[] timePoints) { + if (message.getConnection().isConnected()) { + pushCenter.addTask(new PushTask() { + @Override + public ScheduledExecutorService getExecutor() { + return message.getExecutor(); + } + + @Override + public void run() { + ErrorMessage + .from(message) + .setErrorCode(ROUTER_CHANGE) + .setData(toJson(message, timePoints)) + .sendRaw(); + } + }); + } else { + Logs.PUSH.warn("push message to client redirect, but gateway connection is closed, timePoints={}, message={}" + , Jsons.toJson(timePoints), message); + } + } + + + @Override + public void onTimeout(GatewayPushMessage message, Object[] timePoints) { + Logs.PUSH.warn("push message to client timeout, timePoints={}, message={}" + , Jsons.toJson(timePoints), message); + } + + @Override + public PushListener get() { + return this; + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/push/PushAckCallback.java b/mpush-core/src/main/java/com/mpush/core/push/PushAckCallback.java new file mode 100644 index 00000000..337dbd97 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/push/PushAckCallback.java @@ -0,0 +1,31 @@ +package com.mpush.core.push; + +import com.mpush.api.spi.push.IPushMessage; +import com.mpush.core.ack.AckCallback; +import com.mpush.core.ack.AckTask; +import com.mpush.tools.common.TimeLine; +import com.mpush.tools.log.Logs; + +public final class PushAckCallback implements AckCallback { + private final IPushMessage message; + private final TimeLine timeLine; + private final PushCenter pushCenter; + + public PushAckCallback(IPushMessage message, TimeLine timeLine, PushCenter pushCenter) { + this.message = message; + this.timeLine = timeLine; + this.pushCenter = pushCenter; + } + + @Override + public void onSuccess(AckTask task) { + pushCenter.getPushListener().onAckSuccess(message, timeLine.successEnd().getTimePoints()); + Logs.PUSH.info("[SingleUserPush] client ack success, timeLine={}, task={}", timeLine, task); + } + + @Override + public void onTimeout(AckTask task) { + pushCenter.getPushListener().onTimeout(message, timeLine.timeoutEnd().getTimePoints()); + Logs.PUSH.warn("[SingleUserPush] client ack timeout, timeLine={}, task={}", timeLine, task); + } +} \ No newline at end of file diff --git a/mpush-core/src/main/java/com/mpush/core/push/PushCenter.java b/mpush-core/src/main/java/com/mpush/core/push/PushCenter.java new file mode 100644 index 00000000..ee81b205 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/push/PushCenter.java @@ -0,0 +1,182 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.push; + +import com.mpush.api.spi.push.*; +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; +import com.mpush.core.MPushServer; +import com.mpush.core.ack.AckTaskQueue; +import com.mpush.common.qps.FastFlowControl; +import com.mpush.common.qps.FlowControl; +import com.mpush.common.qps.GlobalFlowControl; +import com.mpush.common.qps.RedisFlowControl; +import com.mpush.monitor.jmx.MBeanRegistry; +import com.mpush.monitor.jmx.mxbean.PushCenterBean; +import com.mpush.tools.config.CC; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; + +import static com.mpush.tools.config.CC.mp.push.flow_control.broadcast.duration; +import static com.mpush.tools.config.CC.mp.push.flow_control.broadcast.limit; +import static com.mpush.tools.config.CC.mp.push.flow_control.broadcast.max; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class PushCenter extends BaseService implements MessagePusher { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private final GlobalFlowControl globalFlowControl = new GlobalFlowControl( + CC.mp.push.flow_control.global.limit, CC.mp.push.flow_control.global.max, CC.mp.push.flow_control.global.duration + ); + + private final AtomicLong taskNum = new AtomicLong(); + + private final AckTaskQueue ackTaskQueue; + + private final MPushServer mPushServer; + + private PushListener pushListener; + + private PushTaskExecutor executor; + + + public PushCenter(MPushServer mPushServer) { + this.mPushServer = mPushServer; + this.ackTaskQueue = new AckTaskQueue(mPushServer); + } + + @Override + public void push(IPushMessage message) { + if (message.isBroadcast()) { + FlowControl flowControl = (message.getTaskId() == null) + ? new FastFlowControl(limit, max, duration) + : new RedisFlowControl(message.getTaskId(), max); + addTask(new BroadcastPushTask(mPushServer, message, flowControl)); + } else { + addTask(new SingleUserPushTask(mPushServer, message, globalFlowControl)); + } + } + + public void addTask(PushTask task) { + executor.addTask(task); + logger.debug("add new task to push center, count={}, task={}", taskNum.incrementAndGet(), task); + } + + public void delayTask(long delay, PushTask task) { + executor.delayTask(delay, task); + logger.debug("delay task to push center, count={}, task={}", taskNum.incrementAndGet(), task); + } + + @Override + protected void doStart(Listener listener) throws Throwable { + this.pushListener = PushListenerFactory.create(); + this.pushListener.init(mPushServer); + + if (CC.mp.net.udpGateway() || CC.mp.thread.pool.push_task > 0) { + executor = new CustomJDKExecutor(mPushServer.getMonitor().getThreadPoolManager().getPushTaskTimer()); + } else {//实际情况使用EventLoo并没有更快,还有待测试 + executor = new NettyEventLoopExecutor(); + } + + MBeanRegistry.getInstance().register(new PushCenterBean(taskNum), null); + ackTaskQueue.start(); + logger.info("push center start success"); + listener.onSuccess(); + } + + @Override + protected void doStop(Listener listener) throws Throwable { + executor.shutdown(); + ackTaskQueue.stop(); + logger.info("push center stop success"); + listener.onSuccess(); + } + + public PushListener getPushListener() { + return pushListener; + } + + public AckTaskQueue getAckTaskQueue() { + return ackTaskQueue; + } + + /** + * TCP 模式直接使用GatewayServer work 线程池 + */ + private static class NettyEventLoopExecutor implements PushTaskExecutor { + + @Override + public void shutdown() { + } + + @Override + public void addTask(PushTask task) { + task.getExecutor().execute(task); + } + + @Override + public void delayTask(long delay, PushTask task) { + task.getExecutor().schedule(task, delay, TimeUnit.NANOSECONDS); + } + } + + + /** + * UDP 模式使用自定义线程池 + */ + private static class CustomJDKExecutor implements PushTaskExecutor { + private final ScheduledExecutorService executorService; + + private CustomJDKExecutor(ScheduledExecutorService executorService) { + this.executorService = executorService; + } + + @Override + public void shutdown() { + executorService.shutdown(); + } + + @Override + public void addTask(PushTask task) { + executorService.execute(task); + } + + @Override + public void delayTask(long delay, PushTask task) { + executorService.schedule(task, delay, TimeUnit.NANOSECONDS); + } + } + + private interface PushTaskExecutor { + + void shutdown(); + + void addTask(PushTask task); + + void delayTask(long delay, PushTask task); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/push/PushTask.java b/mpush-core/src/main/java/com/mpush/core/push/PushTask.java new file mode 100644 index 00000000..b63b0cfe --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/push/PushTask.java @@ -0,0 +1,31 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.push; + +import java.util.concurrent.ScheduledExecutorService; + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public interface PushTask extends Runnable { + ScheduledExecutorService getExecutor(); +} diff --git a/mpush-core/src/main/java/com/mpush/core/push/SingleUserPushTask.java b/mpush-core/src/main/java/com/mpush/core/push/SingleUserPushTask.java new file mode 100644 index 00000000..938e6c4b --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/push/SingleUserPushTask.java @@ -0,0 +1,244 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.push; + +import com.mpush.api.message.Message; +import com.mpush.api.connection.Connection; +import com.mpush.api.spi.push.IPushMessage; +import com.mpush.common.message.PushMessage; +import com.mpush.common.qps.FlowControl; +import com.mpush.common.router.RemoteRouter; +import com.mpush.core.MPushServer; +import com.mpush.core.ack.AckTask; +import com.mpush.core.router.LocalRouter; +import com.mpush.tools.common.TimeLine; +import com.mpush.tools.log.Logs; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; + +import java.util.concurrent.ScheduledExecutorService; + + +/** + * Created by ohun on 16/10/24. + * + * @author ohun@live.cn (夜色) + */ +public final class SingleUserPushTask implements PushTask, ChannelFutureListener { + + private final FlowControl flowControl; + + private final IPushMessage message; + + private int messageId; + + private long start; + + private final TimeLine timeLine = new TimeLine(); + + private final MPushServer mPushServer; + + public SingleUserPushTask(MPushServer mPushServer, IPushMessage message, FlowControl flowControl) { + this.mPushServer = mPushServer; + this.flowControl = flowControl; + this.message = message; + this.timeLine.begin("push-center-begin"); + } + + @Override + public ScheduledExecutorService getExecutor() { + return ((Message) message).getConnection().getChannel().eventLoop(); + } + + /** + * 处理PushClient发送过来的Push推送请求 + *

+ * 查寻路由策略,先查本地路由,本地不存在,查远程,(注意:有可能远程查到也是本机IP) + *

+ * 正常情况本地路由应该存在,如果不存在或链接失效,有以下几种情况: + *

+ * 1.客户端重连,并且链接到了其他机器 + * 2.客户端下线,本地路由失效,远程路由还未清除 + * 3.PushClient使用了本地缓存,但缓存数据已经和实际情况不一致了 + *

+ * 对于三种情况的处理方式是, 再重新查寻下远程路由: + * 1.如果发现远程路由是本机,直接删除,因为此时的路由已失效 (解决场景2) + * 2.如果用户真在另一台机器,让PushClient清理下本地缓存后,重新推送 (解决场景1,3) + *

+ */ + @Override + public void run() { + if (checkTimeout()) return;// 超时 + + if (checkLocal(message)) return;// 本地连接存在 + + checkRemote(message);//本地连接不存在,检测远程路由 + } + + private boolean checkTimeout() { + if (start > 0) { + if (System.currentTimeMillis() - start > message.getTimeoutMills()) { + + mPushServer.getPushCenter().getPushListener().onTimeout(message, timeLine.timeoutEnd().getTimePoints()); + + Logs.PUSH.info("[SingleUserPush] push message to client timeout, timeLine={}, message={}", timeLine, message); + return true; + } + } else { + start = System.currentTimeMillis(); + } + return false; + } + + /** + * 检查本地路由,如果存在并且链接可用直接推送 + * 否则要检查下远程路由 + * + * @param message message + * @return true/false true:success + */ + private boolean checkLocal(IPushMessage message) { + String userId = message.getUserId(); + int clientType = message.getClientType(); + LocalRouter localRouter = mPushServer.getRouterCenter().getLocalRouterManager().lookup(userId, clientType); + + //1.如果本机不存在,再查下远程,看用户是否登陆到其他机器 + if (localRouter == null) return false; + + Connection connection = localRouter.getRouteValue(); + + //2.如果链接失效,先删除本地失效的路由,再查下远程路由,看用户是否登陆到其他机器 + if (!connection.isConnected()) { + + Logs.PUSH.warn("[SingleUserPush] find local router but conn disconnected, message={}, conn={}", message, connection); + + //删除已经失效的本地路由 + mPushServer.getRouterCenter().getLocalRouterManager().unRegister(userId, clientType); + + return false; + } + + //3.检测TCP缓冲区是否已满且写队列超过最高阀值 + if (!connection.getChannel().isWritable()) { + mPushServer.getPushCenter().getPushListener().onFailure(message, timeLine.failureEnd().getTimePoints()); + + Logs.PUSH.error("[SingleUserPush] push message to client failure, tcp sender too busy, message={}, conn={}", message, connection); + return true; + } + + //4. 检测qps, 是否超过流控限制,如果超过则进队列延后发送 + if (flowControl.checkQps()) { + timeLine.addTimePoint("before-send"); + //5.链接可用,直接下发消息到手机客户端 + PushMessage pushMessage = PushMessage.build(connection).setContent(message.getContent()); + pushMessage.getPacket().addFlag(message.getFlags()); + messageId = pushMessage.getSessionId(); + pushMessage.send(this); + } else {//超过流控限制, 进队列延后发送 + mPushServer.getPushCenter().delayTask(flowControl.getDelay(), this); + } + return true; + } + + /** + * 检测远程路由, + * 如果不存在直接返回用户已经下线 + * 如果是本机直接删除路由信息 + * 如果是其他机器让PushClient重推 + * + * @param message message + */ + private void checkRemote(IPushMessage message) { + String userId = message.getUserId(); + int clientType = message.getClientType(); + RemoteRouter remoteRouter = mPushServer.getRouterCenter().getRemoteRouterManager().lookup(userId, clientType); + + // 1.如果远程路由信息也不存在, 说明用户此时不在线, + if (remoteRouter == null || remoteRouter.isOffline()) { + + mPushServer.getPushCenter().getPushListener().onOffline(message, timeLine.end("offline-end").getTimePoints()); + + Logs.PUSH.info("[SingleUserPush] remote router not exists user offline, message={}", message); + + return; + } + + //2.如果查出的远程机器是当前机器,说明路由已经失效,此时用户已下线,需要删除失效的缓存 + if (remoteRouter.getRouteValue().isThisMachine(mPushServer.getGatewayServerNode().getHost(), mPushServer.getGatewayServerNode().getPort())) { + + mPushServer.getPushCenter().getPushListener().onOffline(message, timeLine.end("offline-end").getTimePoints()); + + //删除失效的远程缓存 + mPushServer.getRouterCenter().getRemoteRouterManager().unRegister(userId, clientType); + + Logs.PUSH.info("[SingleUserPush] find remote router in this pc, but local router not exists, userId={}, clientType={}, router={}" + , userId, clientType, remoteRouter); + + return; + } + + //3.否则说明用户已经跑到另外一台机器上了;路由信息发生更改,让PushClient重推 + mPushServer.getPushCenter().getPushListener().onRedirect(message, timeLine.end("redirect-end").getTimePoints()); + + Logs.PUSH.info("[SingleUserPush] find router in another pc, userId={}, clientType={}, router={}", userId, clientType, remoteRouter); + + } + + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (checkTimeout()) return; + + if (future.isSuccess()) {//推送成功 + + if (message.isNeedAck()) {//需要客户端ACK, 添加等待客户端响应ACK的任务 + addAckTask(messageId); + } else { + mPushServer.getPushCenter().getPushListener().onSuccess(message, timeLine.successEnd().getTimePoints()); + } + + Logs.PUSH.info("[SingleUserPush] push message to client success, timeLine={}, message={}", timeLine, message); + + } else {//推送失败 + + mPushServer.getPushCenter().getPushListener().onFailure(message, timeLine.failureEnd().getTimePoints()); + + Logs.PUSH.error("[SingleUserPush] push message to client failure, message={}, conn={}", message, future.channel()); + } + } + + + /** + * 添加ACK任务到队列, 等待客户端响应 + * + * @param messageId 下发到客户端待ack的消息的sessionId + */ + private void addAckTask(int messageId) { + timeLine.addTimePoint("waiting-ack"); + + //因为要进队列,可以提前释放一些比较占用内存的字段,便于垃圾回收 + message.finalized(); + + AckTask task = AckTask + .from(messageId) + .setCallback(new PushAckCallback(message, timeLine, mPushServer.getPushCenter())); + + mPushServer.getPushCenter().getAckTaskQueue().add(task, message.getTimeoutMills() - (int) (System.currentTimeMillis() - start)); + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/router/KickRemoteMsg.java b/mpush-core/src/main/java/com/mpush/core/router/KickRemoteMsg.java deleted file mode 100644 index f670f428..00000000 --- a/mpush-core/src/main/java/com/mpush/core/router/KickRemoteMsg.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.core.router; - -import com.mpush.api.router.ClientType; - -/** - * Created by ohun on 2016/1/4. - * - * @author ohun@live.cn - */ -public final class KickRemoteMsg { - public String userId; - public String deviceId; - public int clientType; - public String targetServer; - - @Override - public String toString() { - return "KickRemoteMsg{" - + "userId='" + userId + '\'' - + ", deviceId='" + deviceId + '\'' - + ", clientType='" + clientType + '\'' - + ", targetServer='" + targetServer + '\'' - + '}'; - } -} diff --git a/mpush-core/src/main/java/com/mpush/core/router/LocalRouter.java b/mpush-core/src/main/java/com/mpush/core/router/LocalRouter.java index 43130d9b..165cd5ac 100644 --- a/mpush-core/src/main/java/com/mpush/core/router/LocalRouter.java +++ b/mpush-core/src/main/java/com/mpush/core/router/LocalRouter.java @@ -20,7 +20,6 @@ package com.mpush.core.router; import com.mpush.api.connection.Connection; -import com.mpush.api.router.ClientType; import com.mpush.api.router.Router; /** @@ -30,15 +29,13 @@ */ public final class LocalRouter implements Router { private final Connection connection; - private final int clientType; public LocalRouter(Connection connection) { this.connection = connection; - this.clientType = connection.getSessionContext().getClientType(); } public int getClientType() { - return clientType; + return connection.getSessionContext().getClientType(); } @Override @@ -58,13 +55,13 @@ public boolean equals(Object o) { LocalRouter that = (LocalRouter) o; - return clientType == that.clientType; + return getClientType() == that.getClientType(); } @Override public int hashCode() { - return Integer.hashCode(clientType); + return Integer.hashCode(getClientType()); } @Override diff --git a/mpush-core/src/main/java/com/mpush/core/router/LocalRouterManager.java b/mpush-core/src/main/java/com/mpush/core/router/LocalRouterManager.java index 57de36a4..9f36a4fc 100644 --- a/mpush-core/src/main/java/com/mpush/core/router/LocalRouterManager.java +++ b/mpush-core/src/main/java/com/mpush/core/router/LocalRouterManager.java @@ -19,20 +19,23 @@ package com.mpush.core.router; +import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; import com.mpush.api.event.ConnectionCloseEvent; import com.mpush.api.event.UserOfflineEvent; -import com.mpush.api.router.ClientType; import com.mpush.api.router.RouterManager; import com.mpush.tools.event.EventBus; import com.mpush.tools.event.EventConsumer; -import io.netty.util.internal.chmv8.ConcurrentHashMapV8; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * Created by ohun on 2015/12/23. @@ -40,19 +43,18 @@ * @author ohun@live.cn */ public final class LocalRouterManager extends EventConsumer implements RouterManager { - public static final Logger LOGGER = LoggerFactory.getLogger(LocalRouterManager.class); - private static final Map EMPTY = Collections.unmodifiableMap(new HashMap<>(0)); + private static final Logger LOGGER = LoggerFactory.getLogger(LocalRouterManager.class); + private static final Map EMPTY = new HashMap<>(0); /** * 本地路由表 */ - private final Map> routers = new ConcurrentHashMapV8<>(); + private final Map> routers = new ConcurrentHashMap<>(); @Override public LocalRouter register(String userId, LocalRouter router) { LOGGER.info("register local router success userId={}, router={}", userId, router); - //add online userId - return routers.computeIfAbsent(userId, s -> new HashMap<>()).put(router.getClientType(), router); + return routers.computeIfAbsent(userId, s -> new HashMap<>(1)).put(router.getClientType(), router); } @Override @@ -74,12 +76,17 @@ public LocalRouter lookup(String userId, int clientType) { return router; } + public Map> routers() { + return routers; + } + /** * 监听链接关闭事件,清理失效的路由 * * @param event */ @Subscribe + @AllowConcurrentEvents void on(ConnectionCloseEvent event) { Connection connection = event.connection; if (connection == null) return; @@ -88,19 +95,19 @@ void on(ConnectionCloseEvent event) { String userId = context.userId; if (userId == null) return; - EventBus.I.post(new UserOfflineEvent(event.connection, userId)); + EventBus.post(new UserOfflineEvent(event.connection, userId)); int clientType = context.getClientType(); - LocalRouter router = routers.getOrDefault(userId, EMPTY).get(clientType); - if (router == null) return; + LocalRouter localRouter = routers.getOrDefault(userId, EMPTY).get(clientType); + if (localRouter == null) return; String connId = connection.getId(); //2.检测下,是否是同一个链接, 如果客户端重连,老的路由会被新的链接覆盖 - if (connId.equals(router.getRouteValue().getId())) { + if (connId.equals(localRouter.getRouteValue().getId())) { //3.删除路由 routers.getOrDefault(userId, EMPTY).remove(clientType); - LOGGER.info("clean disconnected local route, userId={}, route={}", userId, router); + LOGGER.info("clean disconnected local route, userId={}, route={}", userId, localRouter); } else { //如果不相等,则log一下 - LOGGER.info("clean disconnected local route, not clean:userId={}, route={}", userId, router); + LOGGER.info("clean disconnected local route, not clean:userId={}, route={}", userId, localRouter); } } } diff --git a/mpush-core/src/main/java/com/mpush/core/router/RouterCenter.java b/mpush-core/src/main/java/com/mpush/core/router/RouterCenter.java index 0140d2a4..48f89212 100644 --- a/mpush-core/src/main/java/com/mpush/core/router/RouterCenter.java +++ b/mpush-core/src/main/java/com/mpush/core/router/RouterCenter.java @@ -22,31 +22,49 @@ import com.mpush.api.connection.Connection; import com.mpush.api.event.RouterChangeEvent; import com.mpush.api.router.ClientLocation; -import com.mpush.api.router.ClientType; import com.mpush.api.router.Router; +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; import com.mpush.common.router.RemoteRouter; import com.mpush.common.router.RemoteRouterManager; -import com.mpush.tools.Utils; +import com.mpush.core.MPushServer; import com.mpush.tools.event.EventBus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Set; - /** * Created by ohun on 2015/12/23. * * @author ohun@live.cn */ -public final class RouterCenter { - public static final Logger LOGGER = LoggerFactory.getLogger(RouterCenter.class); - public static final RouterCenter I = new RouterCenter(); +public final class RouterCenter extends BaseService { + private static final Logger LOGGER = LoggerFactory.getLogger(RouterCenter.class); + + private LocalRouterManager localRouterManager; + private RemoteRouterManager remoteRouterManager; + private UserEventConsumer userEventConsumer; + private RouterChangeListener routerChangeListener; + private MPushServer mPushServer; + + public RouterCenter(MPushServer mPushServer) { + this.mPushServer = mPushServer; + } - private final LocalRouterManager localRouterManager = new LocalRouterManager(); - private final RemoteRouterManager remoteRouterManager = new RemoteRouterManager(); - private final RouterChangeListener routerChangeListener = new RouterChangeListener(); - private final UserOnlineOfflineListener userOnlineOfflineListener = new UserOnlineOfflineListener(); + @Override + protected void doStart(Listener listener) throws Throwable { + localRouterManager = new LocalRouterManager(); + remoteRouterManager = new RemoteRouterManager(); + routerChangeListener = new RouterChangeListener(mPushServer); + userEventConsumer = new UserEventConsumer(remoteRouterManager); + userEventConsumer.getUserManager().clearUserOnlineData(); + super.doStart(listener); + } + @Override + protected void doStop(Listener listener) throws Throwable { + userEventConsumer.getUserManager().clearUserOnlineData(); + super.doStop(listener); + } /** * 注册用户和链接 @@ -58,7 +76,8 @@ public final class RouterCenter { public boolean register(String userId, Connection connection) { ClientLocation location = ClientLocation .from(connection) - .setHost(Utils.getLocalIp()); + .setHost(mPushServer.getGatewayServerNode().getHost()) + .setPort(mPushServer.getGatewayServerNode().getPort()); LocalRouter localRouter = new LocalRouter(connection); RemoteRouter remoteRouter = new RemoteRouter(location); @@ -73,12 +92,12 @@ public boolean register(String userId, Connection connection) { } if (oldLocalRouter != null) { - EventBus.I.post(new RouterChangeEvent(userId, oldLocalRouter)); + EventBus.post(new RouterChangeEvent(userId, oldLocalRouter)); LOGGER.info("register router success, find old local router={}, userId={}", oldLocalRouter, userId); } - if (oldRemoteRouter != null) { - EventBus.I.post(new RouterChangeEvent(userId, oldRemoteRouter)); + if (oldRemoteRouter != null && oldRemoteRouter.isOnline()) { + EventBus.post(new RouterChangeEvent(userId, oldRemoteRouter)); LOGGER.info("register router success, find old remote router={}, userId={}", oldRemoteRouter, userId); } return true; @@ -97,13 +116,6 @@ public Router lookup(String userId, int clientType) { return remote; } - public Set> lookupAll(String userId) { - Set locals = localRouterManager.lookupAll(userId); - if (locals != null) return locals; - Set remotes = remoteRouterManager.lookupAll(userId); - return remotes; - } - public LocalRouterManager getLocalRouterManager() { return localRouterManager; } @@ -111,4 +123,12 @@ public LocalRouterManager getLocalRouterManager() { public RemoteRouterManager getRemoteRouterManager() { return remoteRouterManager; } + + public RouterChangeListener getRouterChangeListener() { + return routerChangeListener; + } + + public UserEventConsumer getUserEventConsumer() { + return userEventConsumer; + } } diff --git a/mpush-core/src/main/java/com/mpush/core/router/RouterChangeListener.java b/mpush-core/src/main/java/com/mpush/core/router/RouterChangeListener.java index e0f5b61f..cfb60399 100644 --- a/mpush-core/src/main/java/com/mpush/core/router/RouterChangeListener.java +++ b/mpush-core/src/main/java/com/mpush/core/router/RouterChangeListener.java @@ -19,103 +19,132 @@ package com.mpush.core.router; +import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; +import com.mpush.api.Constants; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; import com.mpush.api.event.RouterChangeEvent; import com.mpush.api.router.ClientLocation; -import com.mpush.api.router.ClientType; import com.mpush.api.router.Router; -import com.mpush.cache.redis.RedisKey; -import com.mpush.cache.redis.listener.ListenerDispatcher; -import com.mpush.cache.redis.listener.MessageListener; -import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.api.spi.common.MQClient; +import com.mpush.api.spi.common.MQClientFactory; +import com.mpush.api.spi.common.MQMessageReceiver; import com.mpush.common.message.KickUserMessage; +import com.mpush.common.message.gateway.GatewayKickUserMessage; +import com.mpush.common.router.KickRemoteMsg; +import com.mpush.common.router.MQKickRemoteMsg; import com.mpush.common.router.RemoteRouter; +import com.mpush.core.MPushServer; import com.mpush.tools.Jsons; -import com.mpush.tools.Utils; -import com.mpush.tools.config.ConfigManager; +import com.mpush.tools.config.CC; +import com.mpush.tools.config.ConfigTools; import com.mpush.tools.event.EventConsumer; import com.mpush.tools.log.Logs; +import java.net.InetSocketAddress; + +import static com.mpush.api.Constants.KICK_CHANNEL_PREFIX; + + /** * Created by ohun on 2016/1/4. * * @author ohun@live.cn */ -public final class RouterChangeListener extends EventConsumer implements MessageListener { - public static final String KICK_CHANNEL_ = "/mpush/kick/"; - private final String kick_channel = KICK_CHANNEL_ + Utils.getLocalIp(); +public final class RouterChangeListener extends EventConsumer implements MQMessageReceiver { + private final boolean udpGateway = CC.mp.net.udpGateway(); + private String kick_channel; + private MQClient mqClient; + private MPushServer mPushServer; - public RouterChangeListener() { - ListenerDispatcher.I.subscribe(getKickChannel(), this); + public RouterChangeListener(MPushServer mPushServer) { + this.mPushServer = mPushServer; + this.kick_channel = KICK_CHANNEL_PREFIX + mPushServer.getGatewayServerNode().hostAndPort(); + if (!udpGateway) { + mqClient = MQClientFactory.create(); + mqClient.init(mPushServer); + mqClient.subscribe(getKickChannel(), this); + } } public String getKickChannel() { return kick_channel; } - public String getKickChannel(String remoteIp) { - return KICK_CHANNEL_ + remoteIp; - } - @Subscribe + @AllowConcurrentEvents void on(RouterChangeEvent event) { String userId = event.userId; Router r = event.router; if (r.getRouteType().equals(Router.RouterType.LOCAL)) { - kickLocal(userId, (LocalRouter) r); + sendKickUserMessage2Client(userId, (LocalRouter) r); } else { - kickRemote(userId, (RemoteRouter) r); + sendKickUserMessage2MQ(userId, (RemoteRouter) r); } } /** * 发送踢人消息到客户端 * - * @param userId - * @param router + * @param userId 当前用户 + * @param router 本地路由信息 */ - public void kickLocal(final String userId, final LocalRouter router) { + private void sendKickUserMessage2Client(final String userId, final LocalRouter router) { Connection connection = router.getRouteValue(); SessionContext context = connection.getSessionContext(); - KickUserMessage message = new KickUserMessage(connection); + KickUserMessage message = KickUserMessage.build(connection); message.deviceId = context.deviceId; message.userId = userId; message.send(future -> { if (future.isSuccess()) { - Logs.Conn.info("kick local connection success, userId={}, router={}", userId, router); + Logs.CONN.info("kick local connection success, userId={}, router={}, conn={}", userId, router, connection); } else { - Logs.Conn.info("kick local connection failure, userId={}, router={}", userId, router); + Logs.CONN.warn("kick local connection failure, userId={}, router={}, conn={}", userId, router, connection); } }); } /** - * 广播踢人消息到消息中心(redis). + * 广播踢人消息到消息中心(MQ). *

* 有可能目标机器是当前机器,所以要做一次过滤 * 如果client连续2次链接到同一台机器上就有会出现这中情况 * - * @param userId - * @param router + * @param userId 当前用户 + * @param remoteRouter 用户的路由信息 */ - public void kickRemote(String userId, RemoteRouter router) { - ClientLocation location = router.getRouteValue(); + private void sendKickUserMessage2MQ(String userId, RemoteRouter remoteRouter) { + ClientLocation location = remoteRouter.getRouteValue(); //1.如果目标机器是当前机器,就不要再发送广播了,直接忽略 - if (location.getHost().equals(Utils.getLocalIp())) { - Logs.Conn.info("kick remote user but router in local, userId={}", userId); + if (mPushServer.isTargetMachine(location.getHost(), location.getPort())) { + Logs.CONN.debug("kick remote router in local pc, ignore remote broadcast, userId={}", userId); return; } - //2.发送广播 - //TODO 远程机器可能不存在,需要确认下redis 那个通道如果机器不存在的话,是否会存在消息积压的问题。 - KickRemoteMsg msg = new KickRemoteMsg(); - msg.deviceId = location.getDeviceId(); - msg.clientType = location.getClientType(); - msg.targetServer = location.getHost(); - msg.userId = userId; - RedisManager.I.publish(getKickChannel(msg.targetServer), msg); + if (udpGateway) { + Connection connection = mPushServer.getUdpGatewayServer().getConnection(); + GatewayKickUserMessage.build(connection) + .setUserId(userId) + .setClientType(location.getClientType()) + .setConnId(location.getConnId()) + .setDeviceId(location.getDeviceId()) + .setTargetServer(location.getHost()) + .setTargetPort(location.getPort()) + .setRecipient(new InetSocketAddress(location.getHost(), location.getPort())) + .sendRaw(); + } else { + //2.发送广播 + //TODO 远程机器可能不存在,需要确认下redis 那个通道如果机器不存在的话,是否会存在消息积压的问题。 + MQKickRemoteMsg message = new MQKickRemoteMsg() + .setUserId(userId) + .setClientType(location.getClientType()) + .setConnId(location.getConnId()) + .setDeviceId(location.getDeviceId()) + .setTargetServer(location.getHost()) + .setTargetPort(location.getPort()); + mqClient.publish(Constants.getKickChannel(location.getHostAndPort()), message); + } } /** @@ -128,43 +157,42 @@ public void kickRemote(String userId, RemoteRouter router) { */ public void onReceiveKickRemoteMsg(KickRemoteMsg msg) { //1.如果当前机器不是目标机器,直接忽略 - if (!msg.targetServer.equals(Utils.getLocalIp())) { - Logs.Conn.info("receive kick remote msg, target server error, localIp={}, msg={}", Utils.getLocalIp(), msg); + if (!mPushServer.isTargetMachine(msg.getTargetServer(), msg.getTargetPort())) { + Logs.CONN.error("receive kick remote msg, target server error, localIp={}, msg={}", ConfigTools.getLocalIp(), msg); return; } //2.查询本地路由,找到要被踢下线的链接,并删除该本地路由 - String userId = msg.userId; - int clientType = msg.clientType; - LocalRouterManager routerManager = RouterCenter.I.getLocalRouterManager(); - LocalRouter router = routerManager.lookup(userId, clientType); - if (router != null) { - Logs.Conn.info("receive kick remote msg, msg={}", msg); - //2.1删除本地路由信息 - routerManager.unRegister(userId, clientType); - //2.2发送踢人消息到客户端 - kickLocal(userId, router); - remStatUser(userId); + String userId = msg.getUserId(); + int clientType = msg.getClientType(); + LocalRouterManager localRouterManager = mPushServer.getRouterCenter().getLocalRouterManager(); + LocalRouter localRouter = localRouterManager.lookup(userId, clientType); + if (localRouter != null) { + Logs.CONN.info("receive kick remote msg, msg={}", msg); + if (localRouter.getRouteValue().getId().equals(msg.getConnId())) {//二次校验,防止误杀 + //2.1删除本地路由信息 + localRouterManager.unRegister(userId, clientType); + //2.2发送踢人消息到客户端 + sendKickUserMessage2Client(userId, localRouter); + } else { + Logs.CONN.warn("kick router failure target connId not match, localRouter={}, msg={}", localRouter, msg); + } } else { - Logs.Conn.info("no local router find, kick failure, msg={}", msg); + Logs.CONN.warn("kick router failure can't find local router, msg={}", msg); } } @Override - public void onMessage(String channel, String message) { - if (getKickChannel().equals(channel)) { - KickRemoteMsg msg = Jsons.fromJson(message, KickRemoteMsg.class); + public void receive(String topic, Object message) { + if (getKickChannel().equals(topic)) { + KickRemoteMsg msg = Jsons.fromJson(message.toString(), MQKickRemoteMsg.class); if (msg != null) { onReceiveKickRemoteMsg(msg); } else { - Logs.Conn.info("receive an error kick message={}", message); + Logs.CONN.warn("receive an error kick message={}", message); } } else { - Logs.Conn.info("receive an error redis channel={}", channel); + Logs.CONN.warn("receive an error redis channel={}", topic); } } - - private void remStatUser(String userId) { - RedisManager.I.zRem(RedisKey.getUserOnlineKey(ConfigManager.I.getPublicIp()), userId); - } } diff --git a/mpush-core/src/main/java/com/mpush/core/router/UserOnlineOfflineListener.java b/mpush-core/src/main/java/com/mpush/core/router/UserEventConsumer.java similarity index 51% rename from mpush-core/src/main/java/com/mpush/core/router/UserOnlineOfflineListener.java rename to mpush-core/src/main/java/com/mpush/core/router/UserEventConsumer.java index d0987f04..af915f83 100644 --- a/mpush-core/src/main/java/com/mpush/core/router/UserOnlineOfflineListener.java +++ b/mpush-core/src/main/java/com/mpush/core/router/UserEventConsumer.java @@ -19,37 +19,49 @@ package com.mpush.core.router; +import com.google.common.eventbus.AllowConcurrentEvents; import com.google.common.eventbus.Subscribe; import com.mpush.api.event.UserOfflineEvent; import com.mpush.api.event.UserOnlineEvent; -import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.api.spi.common.MQClient; +import com.mpush.api.spi.common.MQClientFactory; +import com.mpush.common.router.RemoteRouterManager; import com.mpush.common.user.UserManager; -import com.mpush.tools.event.EventBus; +import com.mpush.tools.event.EventConsumer; + +import static com.mpush.api.event.Topics.OFFLINE_CHANNEL; +import static com.mpush.api.event.Topics.ONLINE_CHANNEL; /** * Created by ohun on 2015/12/23. * * @author ohun@live.cn */ -public final class UserOnlineOfflineListener { +public final class UserEventConsumer extends EventConsumer { - public static final String ONLINE_CHANNEL = "/mpush/online/"; + private final MQClient mqClient = MQClientFactory.create(); - public static final String OFFLINE_CHANNEL = "/mpush/offline/"; + private final UserManager userManager; - public UserOnlineOfflineListener() { - EventBus.I.register(this); + public UserEventConsumer(RemoteRouterManager remoteRouterManager) { + this.userManager = new UserManager(remoteRouterManager); } @Subscribe + @AllowConcurrentEvents void on(UserOnlineEvent event) { - UserManager.I.recordUserOnline(event.getUserId()); - RedisManager.I.publish(ONLINE_CHANNEL, event.getUserId()); + userManager.addToOnlineList(event.getUserId()); + mqClient.publish(ONLINE_CHANNEL, event.getUserId()); } @Subscribe + @AllowConcurrentEvents void on(UserOfflineEvent event) { - UserManager.I.recordUserOffline(event.getUserId()); - RedisManager.I.publish(OFFLINE_CHANNEL, event.getUserId()); + userManager.remFormOnlineList(event.getUserId()); + mqClient.publish(OFFLINE_CHANNEL, event.getUserId()); + } + + public UserManager getUserManager() { + return userManager; } } diff --git a/mpush-core/src/main/java/com/mpush/core/server/AdminServer.java b/mpush-core/src/main/java/com/mpush/core/server/AdminServer.java index 589d7474..78b1185f 100644 --- a/mpush-core/src/main/java/com/mpush/core/server/AdminServer.java +++ b/mpush-core/src/main/java/com/mpush/core/server/AdminServer.java @@ -19,9 +19,11 @@ package com.mpush.core.server; +import com.mpush.core.MPushServer; import com.mpush.core.handler.AdminHandler; -import com.mpush.netty.server.NettyServer; -import com.mpush.tools.thread.pool.ThreadPoolManager; +import com.mpush.netty.server.NettyTCPServer; +import com.mpush.tools.config.CC; +import com.mpush.tools.thread.ThreadNames; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.DelimiterBasedFrameDecoder; @@ -29,35 +31,27 @@ import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; -import java.util.concurrent.Executor; +public final class AdminServer extends NettyTCPServer { -public final class AdminServer extends NettyServer { - private final ConnectionServer connectionServer; - private final GatewayServer gatewayServer; + private AdminHandler adminHandler; - private final AdminHandler adminHandler; + private MPushServer mPushServer; - public AdminServer(int port, ConnectionServer connectionServer, GatewayServer gatewayServer) { - super(port); - this.connectionServer = connectionServer; - this.gatewayServer = gatewayServer; - this.adminHandler = new AdminHandler(this); + public AdminServer(MPushServer mPushServer) { + super(CC.mp.net.admin_server_port); + this.mPushServer = mPushServer; } @Override - protected void initPipeline(ChannelPipeline pipeline) { - pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); - super.initPipeline(pipeline); + public void init() { + super.init(); + this.adminHandler = new AdminHandler(mPushServer); } @Override - protected Executor getBossExecutor() { - return ThreadPoolManager.I.getWorkExecutor(); - } - - @Override - protected Executor getWorkExecutor() { - return ThreadPoolManager.I.getWorkExecutor(); + protected void initPipeline(ChannelPipeline pipeline) { + pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); + super.initPipeline(pipeline); } @Override @@ -75,11 +69,18 @@ protected ChannelHandler getEncoder() { return new StringEncoder(); } - public ConnectionServer getConnectionServer() { - return connectionServer; + @Override + protected int getWorkThreadNum() { + return 1; } - public GatewayServer getGatewayServer() { - return gatewayServer; + @Override + protected String getBossThreadName() { + return ThreadNames.T_ADMIN_BOSS; + } + + @Override + protected String getWorkThreadName() { + return ThreadNames.T_ADMIN_WORKER; } } diff --git a/mpush-core/src/main/java/com/mpush/core/server/ConnectionServer.java b/mpush-core/src/main/java/com/mpush/core/server/ConnectionServer.java index b691e027..0022d4d4 100644 --- a/mpush-core/src/main/java/com/mpush/core/server/ConnectionServer.java +++ b/mpush-core/src/main/java/com/mpush/core/server/ConnectionServer.java @@ -23,82 +23,109 @@ import com.mpush.api.connection.ConnectionManager; import com.mpush.api.protocol.Command; import com.mpush.api.service.Listener; +import com.mpush.api.spi.handler.PushHandlerFactory; import com.mpush.common.MessageDispatcher; +import com.mpush.core.MPushServer; import com.mpush.core.handler.*; -import com.mpush.netty.http.HttpClient; -import com.mpush.netty.http.NettyHttpClient; -import com.mpush.netty.server.NettyServer; +import com.mpush.netty.server.NettyTCPServer; import com.mpush.tools.config.CC; -import com.mpush.tools.thread.pool.ThreadPoolManager; +import com.mpush.tools.config.CC.mp.net.rcv_buf; +import com.mpush.tools.config.CC.mp.net.snd_buf; +import com.mpush.tools.thread.NamedPoolThreadFactory; +import com.mpush.tools.thread.ThreadNames; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; +import io.netty.channel.WriteBufferWaterMark; import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler; -import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import static com.mpush.tools.config.CC.mp.net.connect_server_bind_ip; +import static com.mpush.tools.config.CC.mp.net.connect_server_port; import static com.mpush.tools.config.CC.mp.net.traffic_shaping.connect_server.*; +import static com.mpush.tools.config.CC.mp.net.write_buffer_water_mark.connect_server_high; +import static com.mpush.tools.config.CC.mp.net.write_buffer_water_mark.connect_server_low; +import static com.mpush.tools.thread.ThreadNames.T_TRAFFIC_SHAPING; /** * Created by ohun on 2015/12/30. + * + * @author ohun@live.cn (夜色) */ -public final class ConnectionServer extends NettyServer { +public final class ConnectionServer extends NettyTCPServer { + private ServerChannelHandler channelHandler; private GlobalChannelTrafficShapingHandler trafficShapingHandler; - - private ConnectionManager connectionManager = new ServerConnectionManager(); - private HttpClient httpClient; - - public ConnectionServer(int port) { - super(port); + private ScheduledExecutorService trafficShapingExecutor; + private MessageDispatcher messageDispatcher; + private ConnectionManager connectionManager; + private MPushServer mPushServer; + + public ConnectionServer(MPushServer mPushServer) { + super(connect_server_port, connect_server_bind_ip); + this.mPushServer = mPushServer; + this.connectionManager = new ServerConnectionManager(true); + this.messageDispatcher = new MessageDispatcher(); + this.channelHandler = new ServerChannelHandler(true, connectionManager, messageDispatcher); } @Override public void init() { super.init(); connectionManager.init(); - MessageDispatcher receiver = new MessageDispatcher(); - receiver.register(Command.HEARTBEAT, new HeartBeatHandler()); - receiver.register(Command.HANDSHAKE, new HandshakeHandler()); - receiver.register(Command.BIND, new BindUserHandler()); - receiver.register(Command.UNBIND, new UnbindUserHandler()); - receiver.register(Command.FAST_CONNECT, new FastConnectHandler()); - - if (CC.mp.http.proxy_enabled) { - httpClient = new NettyHttpClient(); - receiver.register(Command.HTTP_PROXY, new HttpProxyHandler(httpClient)); - } - channelHandler = new ServerChannelHandler(true, connectionManager, receiver); - - if (enabled) { + messageDispatcher.register(Command.HEARTBEAT, HeartBeatHandler::new); + messageDispatcher.register(Command.HANDSHAKE, () -> new HandshakeHandler(mPushServer)); + messageDispatcher.register(Command.BIND, () -> new BindUserHandler(mPushServer)); + messageDispatcher.register(Command.UNBIND, () -> new BindUserHandler(mPushServer)); + messageDispatcher.register(Command.FAST_CONNECT, () -> new FastConnectHandler(mPushServer)); + messageDispatcher.register(Command.PUSH, PushHandlerFactory::create); + messageDispatcher.register(Command.ACK, () -> new AckHandler(mPushServer)); + messageDispatcher.register(Command.HTTP_PROXY, () -> new HttpProxyHandler(mPushServer), CC.mp.http.proxy_enabled); + + if (CC.mp.net.traffic_shaping.connect_server.enabled) {//启用流量整形,限流 + trafficShapingExecutor = Executors.newSingleThreadScheduledExecutor(new NamedPoolThreadFactory(T_TRAFFIC_SHAPING)); trafficShapingHandler = new GlobalChannelTrafficShapingHandler( - Executors.newSingleThreadScheduledExecutor() - , write_global_limit, read_global_limit, + trafficShapingExecutor, + write_global_limit, read_global_limit, write_channel_limit, read_channel_limit, check_interval); } } + @Override + public void start(Listener listener) { + super.start(listener); + if (this.workerGroup != null) {// 增加线程池监控 + mPushServer.getMonitor().monitor("conn-worker", this.workerGroup); + } + } + @Override public void stop(Listener listener) { + super.stop(listener); if (trafficShapingHandler != null) { trafficShapingHandler.release(); + trafficShapingExecutor.shutdown(); } - super.stop(listener); - if (httpClient != null) httpClient.stop(); connectionManager.destroy(); } @Override - protected Executor getWorkExecutor() { - return ThreadPoolManager.I.getWorkExecutor(); + protected int getWorkThreadNum() { + return CC.mp.thread.pool.conn_work; + } + + @Override + protected String getBossThreadName() { + return ThreadNames.T_CONN_BOSS; } @Override - protected Executor getBossExecutor() { - return ThreadPoolManager.I.getBossExecutor(); + protected String getWorkThreadName() { + return ThreadNames.T_CONN_WORKER; } @Override @@ -112,12 +139,7 @@ protected void initPipeline(ChannelPipeline pipeline) { @Override protected void initOptions(ServerBootstrap b) { super.initOptions(b); - /*** - * 你可以设置这里指定的通道实现的配置参数。 - * 我们正在写一个TCP/IP的服务端, - * 因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。 - * 请参考ChannelOption和详细的ChannelConfig实现的接口文档以此可以对ChannelOptions的有一个大概的认识。 - */ + b.option(ChannelOption.SO_BACKLOG, 1024); /** @@ -125,8 +147,30 @@ protected void initOptions(ServerBootstrap b) { * 在Netty中分别对应ChannelOption的SO_SNDBUF和SO_RCVBUF, * 需要根据推送消息的大小,合理设置,对于海量长连接,通常32K是个不错的选择。 */ - b.childOption(ChannelOption.SO_SNDBUF, 32 * 1024); - b.childOption(ChannelOption.SO_RCVBUF, 32 * 1024); + if (snd_buf.connect_server > 0) b.childOption(ChannelOption.SO_SNDBUF, snd_buf.connect_server); + if (rcv_buf.connect_server > 0) b.childOption(ChannelOption.SO_RCVBUF, rcv_buf.connect_server); + + /** + * 这个坑其实也不算坑,只是因为懒,该做的事情没做。一般来讲我们的业务如果比较小的时候我们用同步处理,等业务到一定规模的时候,一个优化手段就是异步化。 + * 异步化是提高吞吐量的一个很好的手段。但是,与异步相比,同步有天然的负反馈机制,也就是如果后端慢了,前面也会跟着慢起来,可以自动的调节。 + * 但是异步就不同了,异步就像决堤的大坝一样,洪水是畅通无阻。如果这个时候没有进行有效的限流措施就很容易把后端冲垮。 + * 如果一下子把后端冲垮倒也不是最坏的情况,就怕把后端冲的要死不活。 + * 这个时候,后端就会变得特别缓慢,如果这个时候前面的应用使用了一些无界的资源等,就有可能把自己弄死。 + * 那么现在要介绍的这个坑就是关于Netty里的ChannelOutboundBuffer这个东西的。 + * 这个buffer是用在netty向channel write数据的时候,有个buffer缓冲,这样可以提高网络的吞吐量(每个channel有一个这样的buffer)。 + * 初始大小是32(32个元素,不是指字节),但是如果超过32就会翻倍,一直增长。 + * 大部分时候是没有什么问题的,但是在碰到对端非常慢(对端慢指的是对端处理TCP包的速度变慢,比如对端负载特别高的时候就有可能是这个情况)的时候就有问题了, + * 这个时候如果还是不断地写数据,这个buffer就会不断地增长,最后就有可能出问题了(我们的情况是开始吃swap,最后进程被linux killer干掉了)。 + * 为什么说这个地方是坑呢,因为大部分时候我们往一个channel写数据会判断channel是否active,但是往往忽略了这种慢的情况。 + * + * 那这个问题怎么解决呢?其实ChannelOutboundBuffer虽然无界,但是可以给它配置一个高水位线和低水位线, + * 当buffer的大小超过高水位线的时候对应channel的isWritable就会变成false, + * 当buffer的大小低于低水位线的时候,isWritable就会变成true。所以应用应该判断isWritable,如果是false就不要再写数据了。 + * 高水位线和低水位线是字节数,默认高水位是64K,低水位是32K,我们可以根据我们的应用需要支持多少连接数和系统资源进行合理规划。 + */ + b.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark( + connect_server_low, connect_server_high + )); } @Override @@ -138,7 +182,7 @@ public ConnectionManager getConnectionManager() { return connectionManager; } - public HttpClient getHttpClient() { - return httpClient; + public MessageDispatcher getMessageDispatcher() { + return messageDispatcher; } } diff --git a/mpush-core/src/main/java/com/mpush/core/server/DefaultServerEventListener.java b/mpush-core/src/main/java/com/mpush/core/server/DefaultServerEventListener.java new file mode 100644 index 00000000..e1a2e288 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/server/DefaultServerEventListener.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.server; + +import com.mpush.api.common.ServerEventListener; +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.core.ServerEventListenerFactory; + +/** + * Created by ohun on 16/10/19. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 1) +public final class DefaultServerEventListener implements ServerEventListener, ServerEventListenerFactory { + + @Override + public ServerEventListener get() { + return this; + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/server/GatewayServer.java b/mpush-core/src/main/java/com/mpush/core/server/GatewayServer.java index fb7976ee..7998347c 100644 --- a/mpush-core/src/main/java/com/mpush/core/server/GatewayServer.java +++ b/mpush-core/src/main/java/com/mpush/core/server/GatewayServer.java @@ -19,45 +19,67 @@ package com.mpush.core.server; +import com.mpush.api.connection.ConnectionManager; import com.mpush.api.protocol.Command; import com.mpush.api.service.Listener; import com.mpush.common.MessageDispatcher; +import com.mpush.core.MPushServer; import com.mpush.core.handler.GatewayPushHandler; -import com.mpush.netty.server.NettyServer; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelPipeline; +import com.mpush.netty.server.NettyTCPServer; +import com.mpush.tools.config.CC; +import com.mpush.tools.config.CC.mp.net.rcv_buf; +import com.mpush.tools.config.CC.mp.net.snd_buf; +import com.mpush.tools.thread.NamedPoolThreadFactory; +import com.mpush.tools.thread.ThreadNames; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.sctp.nio.NioSctpServerChannel; +import io.netty.channel.udt.nio.NioUdtProvider; import io.netty.handler.traffic.GlobalChannelTrafficShapingHandler; +import java.nio.channels.spi.SelectorProvider; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import static com.mpush.tools.config.CC.mp.net.gateway_server_bind_ip; +import static com.mpush.tools.config.CC.mp.net.gateway_server_port; import static com.mpush.tools.config.CC.mp.net.traffic_shaping.gateway_server.*; +import static com.mpush.tools.config.CC.mp.net.write_buffer_water_mark.gateway_server_high; +import static com.mpush.tools.config.CC.mp.net.write_buffer_water_mark.gateway_server_low; +import static com.mpush.tools.thread.ThreadNames.T_TRAFFIC_SHAPING; /** * Created by ohun on 2015/12/30. * * @author ohun@live.cn */ -public final class GatewayServer extends NettyServer { +public final class GatewayServer extends NettyTCPServer { private ServerChannelHandler channelHandler; - private ServerConnectionManager connectionManager; + private ConnectionManager connectionManager; + private MessageDispatcher messageDispatcher; private GlobalChannelTrafficShapingHandler trafficShapingHandler; + private ScheduledExecutorService trafficShapingExecutor; + private MPushServer mPushServer; - public GatewayServer(int port) { - super(port); + public GatewayServer(MPushServer mPushServer) { + super(gateway_server_port, gateway_server_bind_ip); + this.mPushServer = mPushServer; + this.messageDispatcher = new MessageDispatcher(); + this.connectionManager = new ServerConnectionManager(false); + this.channelHandler = new ServerChannelHandler(false, connectionManager, messageDispatcher); } @Override public void init() { super.init(); - MessageDispatcher receiver = new MessageDispatcher(); - receiver.register(Command.GATEWAY_PUSH, new GatewayPushHandler()); - connectionManager = new ServerConnectionManager(); - channelHandler = new ServerChannelHandler(false, connectionManager, receiver); - if (enabled) { + messageDispatcher.register(Command.GATEWAY_PUSH, () -> new GatewayPushHandler(mPushServer.getPushCenter())); + + if (CC.mp.net.traffic_shaping.gateway_server.enabled) {//启用流量整形,限流 + trafficShapingExecutor = Executors.newSingleThreadScheduledExecutor(new NamedPoolThreadFactory(T_TRAFFIC_SHAPING)); trafficShapingHandler = new GlobalChannelTrafficShapingHandler( - Executors.newSingleThreadScheduledExecutor() - , write_global_limit, read_global_limit, + trafficShapingExecutor, + write_global_limit, read_global_limit, write_channel_limit, read_channel_limit, check_interval); } @@ -65,15 +87,36 @@ public void init() { @Override public void stop(Listener listener) { + super.stop(listener); if (trafficShapingHandler != null) { trafficShapingHandler.release(); + trafficShapingExecutor.shutdown(); } - super.stop(listener); if (connectionManager != null) { connectionManager.destroy(); } } + @Override + protected String getBossThreadName() { + return ThreadNames.T_GATEWAY_BOSS; + } + + @Override + protected String getWorkThreadName() { + return ThreadNames.T_GATEWAY_WORKER; + } + + @Override + protected int getIoRate() { + return 100; + } + + @Override + protected int getWorkThreadNum() { + return CC.mp.thread.pool.gateway_server_work; + } + @Override protected void initPipeline(ChannelPipeline pipeline) { super.initPipeline(pipeline); @@ -82,8 +125,62 @@ protected void initPipeline(ChannelPipeline pipeline) { } } + @Override + protected void initOptions(ServerBootstrap b) { + super.initOptions(b); + if (snd_buf.gateway_server > 0) b.childOption(ChannelOption.SO_SNDBUF, snd_buf.gateway_server); + if (rcv_buf.gateway_server > 0) b.childOption(ChannelOption.SO_RCVBUF, rcv_buf.gateway_server); + /** + * 这个坑其实也不算坑,只是因为懒,该做的事情没做。一般来讲我们的业务如果比较小的时候我们用同步处理,等业务到一定规模的时候,一个优化手段就是异步化。 + * 异步化是提高吞吐量的一个很好的手段。但是,与异步相比,同步有天然的负反馈机制,也就是如果后端慢了,前面也会跟着慢起来,可以自动的调节。 + * 但是异步就不同了,异步就像决堤的大坝一样,洪水是畅通无阻。如果这个时候没有进行有效的限流措施就很容易把后端冲垮。 + * 如果一下子把后端冲垮倒也不是最坏的情况,就怕把后端冲的要死不活。 + * 这个时候,后端就会变得特别缓慢,如果这个时候前面的应用使用了一些无界的资源等,就有可能把自己弄死。 + * 那么现在要介绍的这个坑就是关于Netty里的ChannelOutboundBuffer这个东西的。 + * 这个buffer是用在netty向channel write数据的时候,有个buffer缓冲,这样可以提高网络的吞吐量(每个channel有一个这样的buffer)。 + * 初始大小是32(32个元素,不是指字节),但是如果超过32就会翻倍,一直增长。 + * 大部分时候是没有什么问题的,但是在碰到对端非常慢(对端慢指的是对端处理TCP包的速度变慢,比如对端负载特别高的时候就有可能是这个情况)的时候就有问题了, + * 这个时候如果还是不断地写数据,这个buffer就会不断地增长,最后就有可能出问题了(我们的情况是开始吃swap,最后进程被linux killer干掉了)。 + * 为什么说这个地方是坑呢,因为大部分时候我们往一个channel写数据会判断channel是否active,但是往往忽略了这种慢的情况。 + * + * 那这个问题怎么解决呢?其实ChannelOutboundBuffer虽然无界,但是可以给它配置一个高水位线和低水位线, + * 当buffer的大小超过高水位线的时候对应channel的isWritable就会变成false, + * 当buffer的大小低于低水位线的时候,isWritable就会变成true。所以应用应该判断isWritable,如果是false就不要再写数据了。 + * 高水位线和低水位线是字节数,默认高水位是64K,低水位是32K,我们可以根据我们的应用需要支持多少连接数和系统资源进行合理规划。 + */ + if (gateway_server_low > 0 && gateway_server_high > 0) { + b.childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark( + gateway_server_low, gateway_server_high + )); + } + } + + @Override + public ChannelFactory getChannelFactory() { + if (CC.mp.net.tcpGateway()) return super.getChannelFactory(); + if (CC.mp.net.udtGateway()) return NioUdtProvider.BYTE_ACCEPTOR; + if (CC.mp.net.sctpGateway()) return NioSctpServerChannel::new; + return super.getChannelFactory(); + } + + @Override + public SelectorProvider getSelectorProvider() { + if (CC.mp.net.tcpGateway()) return super.getSelectorProvider(); + if (CC.mp.net.udtGateway()) return NioUdtProvider.BYTE_PROVIDER; + if (CC.mp.net.sctpGateway()) return super.getSelectorProvider(); + return super.getSelectorProvider(); + } + @Override public ChannelHandler getChannelHandler() { return channelHandler; } + + public ConnectionManager getConnectionManager() { + return connectionManager; + } + + public MessageDispatcher getMessageDispatcher() { + return messageDispatcher; + } } diff --git a/mpush-core/src/main/java/com/mpush/core/server/GatewayUDPConnector.java b/mpush-core/src/main/java/com/mpush/core/server/GatewayUDPConnector.java new file mode 100644 index 00000000..c5d5d8bd --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/server/GatewayUDPConnector.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.server; + +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Command; +import com.mpush.common.MessageDispatcher; +import com.mpush.core.MPushServer; +import com.mpush.core.handler.GatewayKickUserHandler; +import com.mpush.core.handler.GatewayPushHandler; +import com.mpush.netty.udp.UDPChannelHandler; +import com.mpush.netty.udp.NettyUDPConnector; +import com.mpush.tools.Utils; +import com.mpush.tools.config.CC; +import com.mpush.tools.config.CC.mp.net.rcv_buf; +import com.mpush.tools.config.CC.mp.net.snd_buf; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelOption; + +import static com.mpush.common.MessageDispatcher.POLICY_LOG; + +/** + * Created by ohun on 2015/12/30. + * + * @author ohun@live.cn + */ +public final class GatewayUDPConnector extends NettyUDPConnector { + + private UDPChannelHandler channelHandler; + private MessageDispatcher messageDispatcher; + private MPushServer mPushServer; + + public GatewayUDPConnector(MPushServer mPushServer) { + super(CC.mp.net.gateway_server_port); + this.mPushServer = mPushServer; + this.messageDispatcher = new MessageDispatcher(POLICY_LOG); + this.channelHandler = new UDPChannelHandler(messageDispatcher); + } + + @Override + public void init() { + super.init(); + messageDispatcher.register(Command.GATEWAY_PUSH, () -> new GatewayPushHandler(mPushServer.getPushCenter())); + messageDispatcher.register(Command.GATEWAY_KICK, () -> new GatewayKickUserHandler(mPushServer.getRouterCenter())); + channelHandler.setMulticastAddress(Utils.getInetAddress(CC.mp.net.gateway_server_multicast)); + channelHandler.setNetworkInterface(Utils.getLocalNetworkInterface()); + } + + @Override + protected void initOptions(Bootstrap b) { + super.initOptions(b); + b.option(ChannelOption.IP_MULTICAST_LOOP_DISABLED, true);//默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口 + b.option(ChannelOption.IP_MULTICAST_TTL, 255);//选项IP_MULTICAST_TTL允许设置超时TTL,范围为0~255之间的任何值 + //b.option(ChannelOption.IP_MULTICAST_IF, null);//选项IP_MULTICAST_IF用于设置组播的默认网络接口,会从给定的网络接口发送,另一个网络接口会忽略此数据,参数addr是希望多播输出接口的IP地址,使用INADDR_ANY地址回送到默认接口。 + //b.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(32 * 1024, 1024 * 1024)); + if (snd_buf.gateway_server > 0) b.option(ChannelOption.SO_SNDBUF, snd_buf.gateway_server); + if (rcv_buf.gateway_server > 0) b.option(ChannelOption.SO_RCVBUF, rcv_buf.gateway_server); + } + + @Override + public ChannelHandler getChannelHandler() { + return channelHandler; + } + + public Connection getConnection() { + return channelHandler.getConnection(); + } + + public MessageDispatcher getMessageDispatcher() { + return messageDispatcher; + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/server/ServerChannelHandler.java b/mpush-core/src/main/java/com/mpush/core/server/ServerChannelHandler.java index 9aa7c078..971c88d7 100644 --- a/mpush-core/src/main/java/com/mpush/core/server/ServerChannelHandler.java +++ b/mpush-core/src/main/java/com/mpush/core/server/ServerChannelHandler.java @@ -20,18 +20,20 @@ package com.mpush.core.server; -import com.mpush.api.PacketReceiver; +import com.mpush.api.message.PacketReceiver; import com.mpush.api.connection.Connection; import com.mpush.api.connection.ConnectionManager; import com.mpush.api.event.ConnectionCloseEvent; +import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; import com.mpush.netty.connection.NettyConnection; import com.mpush.tools.common.Profiler; +import com.mpush.tools.config.CC; import com.mpush.tools.event.EventBus; import com.mpush.tools.log.Logs; import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,14 +43,13 @@ * @author ohun@live.cn */ @ChannelHandler.Sharable -public final class ServerChannelHandler extends ChannelHandlerAdapter { +public final class ServerChannelHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(ServerChannelHandler.class); - /** - * 是否启用加密 - */ - private final boolean security; + private static final long profile_slowly_limit = CC.mp.monitor.profile_slowly_duration.toMillis(); + + private final boolean security; //是否启用加密 private final ConnectionManager connectionManager; private final PacketReceiver receiver; @@ -60,17 +61,19 @@ public ServerChannelHandler(boolean security, ConnectionManager connectionManage @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Packet packet = (Packet) msg; + byte cmd = packet.cmd; + try { - Profiler.start("channel read:"); + Profiler.start("time cost on [channel read]: ", packet.toString()); Connection connection = connectionManager.get(ctx.channel()); - LOGGER.debug("channelRead channel={}, connection={}, packet={}", ctx.channel(), connection, msg); + LOGGER.debug("channelRead conn={}, packet={}", ctx.channel(), connection.getSessionContext(), msg); connection.updateLastReadTime(); - receiver.onReceive((Packet) msg, connection); + receiver.onReceive(packet, connection); } finally { Profiler.release(); - long duration = Profiler.getDuration(); - if (duration > 80) { - LOGGER.error("channel read busy:" + duration + "," + Profiler.dump()); + if (Profiler.getDuration() > profile_slowly_limit) { + Logs.PROFILE.info("Read Packet[cmd={}] Slowly: \n{}", Command.toCMD(cmd), Profiler.dump()); } Profiler.reset(); } @@ -78,14 +81,15 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - Connection connection = connectionManager.removeAndClose(ctx.channel()); - Logs.Conn.error("client exceptionCaught channel={}, connection={}", ctx.channel(), connection); - LOGGER.error("caught an ex, channel={}, connection={}", ctx.channel(), connection, cause); + Connection connection = connectionManager.get(ctx.channel()); + Logs.CONN.error("client caught ex, conn={}", connection); + LOGGER.error("caught an ex, channel={}, conn={}", ctx.channel(), connection, cause); + ctx.close(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { - Logs.Conn.info("client connect channel={}", ctx.channel()); + Logs.CONN.info("client connected conn={}", ctx.channel()); Connection connection = new NettyConnection(); connection.init(ctx.channel(), security); connectionManager.add(connection); @@ -94,7 +98,7 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { Connection connection = connectionManager.removeAndClose(ctx.channel()); - EventBus.I.post(new ConnectionCloseEvent(connection)); - Logs.Conn.info("client disconnect channel={}, connection={}", ctx.channel(), connection); + EventBus.post(new ConnectionCloseEvent(connection)); + Logs.CONN.info("client disconnected conn={}", connection); } } \ No newline at end of file diff --git a/mpush-core/src/main/java/com/mpush/core/server/ServerConnectionManager.java b/mpush-core/src/main/java/com/mpush/core/server/ServerConnectionManager.java index f18d9f91..f468893d 100644 --- a/mpush-core/src/main/java/com/mpush/core/server/ServerConnectionManager.java +++ b/mpush-core/src/main/java/com/mpush/core/server/ServerConnectionManager.java @@ -20,22 +20,20 @@ package com.mpush.core.server; -import com.google.common.collect.Lists; -import com.google.common.eventbus.Subscribe; import com.mpush.api.connection.Connection; import com.mpush.api.connection.ConnectionManager; -import com.mpush.api.event.HandshakeEvent; +import com.mpush.netty.connection.NettyConnection; import com.mpush.tools.config.CC; -import com.mpush.tools.event.EventBus; import com.mpush.tools.log.Logs; +import com.mpush.tools.thread.NamedThreadFactory; +import com.mpush.tools.thread.ThreadNames; import io.netty.channel.Channel; +import io.netty.channel.ChannelId; import io.netty.util.HashedWheelTimer; import io.netty.util.Timeout; -import io.netty.util.Timer; import io.netty.util.TimerTask; -import io.netty.util.internal.chmv8.ConcurrentHashMapV8; -import java.util.List; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; @@ -46,91 +44,154 @@ * @author ohun@live.cn */ public final class ServerConnectionManager implements ConnectionManager { - private final ConcurrentMap connections = new ConcurrentHashMapV8<>(); - - private Timer timer; + private final ConcurrentMap connections = new ConcurrentHashMap<>(); + private final ConnectionHolder DEFAULT = new SimpleConnectionHolder(null); + private final boolean heartbeatCheck; + private final ConnectionHolderFactory holderFactory; + private HashedWheelTimer timer; + + public ServerConnectionManager(boolean heartbeatCheck) { + this.heartbeatCheck = heartbeatCheck; + this.holderFactory = heartbeatCheck ? HeartbeatCheckTask::new : SimpleConnectionHolder::new; + } @Override public void init() { - //每秒钟走一步,一个心跳周期内走一圈 - long tickDuration = 1000;//1s - int ticksPerWheel = (int) (CC.mp.core.max_heartbeat / tickDuration); - this.timer = new HashedWheelTimer(tickDuration, TimeUnit.MILLISECONDS, ticksPerWheel); - EventBus.I.register(this); + if (heartbeatCheck) { + long tickDuration = TimeUnit.SECONDS.toMillis(1);//1s 每秒钟走一步,一个心跳周期内大致走一圈 + int ticksPerWheel = (int) (CC.mp.core.max_heartbeat / tickDuration); + this.timer = new HashedWheelTimer( + new NamedThreadFactory(ThreadNames.T_CONN_TIMER), + tickDuration, TimeUnit.MILLISECONDS, ticksPerWheel + ); + } } @Override public void destroy() { - if (timer != null) timer.stop(); - for (Connection connection : connections.values()) { - connection.close(); + if (timer != null) { + timer.stop(); } + connections.values().forEach(ConnectionHolder::close); connections.clear(); } @Override - public Connection get(final Channel channel) { - return connections.get(channel.id().asShortText()); + public Connection get(Channel channel) { + return connections.getOrDefault(channel.id(), DEFAULT).get(); } @Override public void add(Connection connection) { - connections.putIfAbsent(connection.getChannel().id().asShortText(), connection); + connections.putIfAbsent(connection.getChannel().id(), holderFactory.create(connection)); } @Override public Connection removeAndClose(Channel channel) { - Connection connection = connections.remove(channel.id().asShortText()); - if (connection != null) { - connection.close(); + ConnectionHolder holder = connections.remove(channel.id()); + if (holder != null) { + Connection connection = holder.get(); + holder.close(); + return connection; } + + //add default + Connection connection = new NettyConnection(); + connection.init(channel, false); + connection.close(); return connection; } @Override - public List getConnections() { - return Lists.newArrayList(connections.values()); + public int getConnNum() { + return connections.size(); } - @Subscribe - void on(HandshakeEvent event) { - HeartbeatCheckTask task = new HeartbeatCheckTask(event.connection); - task.startTimeout(); - } + private interface ConnectionHolder { + Connection get(); - private class HeartbeatCheckTask implements TimerTask { + void close(); + } - private int timeoutTimes = 0; + private static class SimpleConnectionHolder implements ConnectionHolder { private final Connection connection; - public HeartbeatCheckTask(Connection connection) { + private SimpleConnectionHolder(Connection connection) { this.connection = connection; } - public void startTimeout() { - int timeout = connection.getSessionContext().heartbeat; - timer.newTimeout(this, timeout > 0 ? timeout : CC.mp.core.min_heartbeat, TimeUnit.MILLISECONDS); + @Override + public Connection get() { + return connection; + } + + @Override + public void close() { + if (connection != null) { + connection.close(); + } + } + } + + + private class HeartbeatCheckTask implements ConnectionHolder, TimerTask { + + private byte timeoutTimes = 0; + private Connection connection; + + private HeartbeatCheckTask(Connection connection) { + this.connection = connection; + this.startTimeout(); + } + + void startTimeout() { + Connection connection = this.connection; + + if (connection != null && connection.isConnected()) { + int timeout = connection.getSessionContext().heartbeat; + timer.newTimeout(this, timeout, TimeUnit.MILLISECONDS); + } } @Override public void run(Timeout timeout) throws Exception { - if (!connection.isConnected()) { - Logs.HB.info("connection was disconnected, heartbeat timeout times={}, connection={}", timeoutTimes, connection); + Connection connection = this.connection; + + if (connection == null || !connection.isConnected()) { + Logs.HB.info("heartbeat timeout times={}, connection disconnected, conn={}", timeoutTimes, connection); return; } - if (connection.heartbeatTimeout()) { + + if (connection.isReadTimeout()) { if (++timeoutTimes > CC.mp.core.max_hb_timeout_times) { connection.close(); - Logs.HB.info("client heartbeat timeout times={}, do close connection={}", timeoutTimes, connection); + Logs.HB.warn("client heartbeat timeout times={}, do close conn={}", timeoutTimes, connection); return; } else { Logs.HB.info("client heartbeat timeout times={}, connection={}", timeoutTimes, connection); } } else { timeoutTimes = 0; - //Logs.HB.info("client heartbeat timeout times reset 0, connection={}", connection); } startTimeout(); } + + @Override + public void close() { + if (connection != null) { + connection.close(); + connection = null; + } + } + + @Override + public Connection get() { + return connection; + } + } + + @FunctionalInterface + private interface ConnectionHolderFactory { + ConnectionHolder create(Connection connection); } } diff --git a/mpush-core/src/main/java/com/mpush/core/server/WebSocketChannelHandler.java b/mpush-core/src/main/java/com/mpush/core/server/WebSocketChannelHandler.java new file mode 100644 index 00000000..390fa7ce --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/server/WebSocketChannelHandler.java @@ -0,0 +1,71 @@ +package com.mpush.core.server; + +import com.mpush.api.message.PacketReceiver; +import com.mpush.api.connection.Connection; +import com.mpush.api.connection.ConnectionManager; +import com.mpush.api.event.ConnectionCloseEvent; +import com.mpush.api.protocol.Packet; +import com.mpush.netty.codec.PacketDecoder; +import com.mpush.netty.connection.NettyConnection; +import com.mpush.tools.event.EventBus; +import com.mpush.tools.log.Logs; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Echoes uppercase content of text frames. + */ +@ChannelHandler.Sharable +public class WebSocketChannelHandler extends SimpleChannelInboundHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketChannelHandler.class); + private final ConnectionManager connectionManager; + private final PacketReceiver receiver; + + public WebSocketChannelHandler(ConnectionManager connectionManager, PacketReceiver receiver) { + this.connectionManager = connectionManager; + this.receiver = receiver; + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { + if (frame instanceof TextWebSocketFrame) { + String text = ((TextWebSocketFrame) frame).text(); + Connection connection = connectionManager.get(ctx.channel()); + Packet packet = PacketDecoder.decodeFrame(text); + LOGGER.debug("channelRead conn={}, packet={}", ctx.channel(), connection.getSessionContext(), packet); + receiver.onReceive(packet, connection); + } else { + String message = "unsupported frame type: " + frame.getClass().getName(); + throw new UnsupportedOperationException(message); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + Connection connection = connectionManager.get(ctx.channel()); + Logs.CONN.error("client caught ex, conn={}", connection); + LOGGER.error("caught an ex, channel={}, conn={}", ctx.channel(), connection, cause); + ctx.close(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + Logs.CONN.info("client connected conn={}", ctx.channel()); + Connection connection = new NettyConnection(); + connection.init(ctx.channel(), false); + connectionManager.add(connection); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + Connection connection = connectionManager.removeAndClose(ctx.channel()); + EventBus.post(new ConnectionCloseEvent(connection)); + Logs.CONN.info("client disconnected conn={}", connection); + } +} \ No newline at end of file diff --git a/mpush-core/src/main/java/com/mpush/core/server/WebSocketIndexPageHandler.java b/mpush-core/src/main/java/com/mpush/core/server/WebSocketIndexPageHandler.java new file mode 100644 index 00000000..f158562e --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/server/WebSocketIndexPageHandler.java @@ -0,0 +1,85 @@ +package com.mpush.core.server; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.handler.codec.http.*; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.CharsetUtil; + +import java.io.InputStream; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * Outputs index page content. + */ +@ChannelHandler.Sharable +public class WebSocketIndexPageHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { + // Handle a bad request. + if (!req.decoderResult().isSuccess()) { + sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST)); + return; + } + + // Allow only GET methods. + if (req.method() != GET) { + sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN)); + return; + } + + // Send the index page + if ("/".equals(req.uri()) || "/index.html".equals(req.uri())) { + ByteBuf content = getContent(); + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, OK, content); + + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8"); + HttpUtil.setContentLength(res, content.readableBytes()); + + sendHttpResponse(ctx, req, res); + } else { + sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND)); + } + ctx.pipeline().remove(this); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + ctx.close(); + } + + private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { + // Generate an error page if response getStatus code is not OK (200). + if (res.status().code() != 200) { + ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8); + res.content().writeBytes(buf); + buf.release(); + HttpUtil.setContentLength(res, res.content().readableBytes()); + } + + // Send the response and close the connection if necessary. + ChannelFuture f = ctx.channel().writeAndFlush(res); + if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) { + f.addListener(ChannelFutureListener.CLOSE); + } + } + + public ByteBuf getContent() { + try (InputStream in = this.getClass().getResourceAsStream("/index.html")) { + byte[] data = new byte[in.available()]; + if (in.read(data) > 0) { + return Unpooled.wrappedBuffer(data); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return Unpooled.EMPTY_BUFFER; + } + +} \ No newline at end of file diff --git a/mpush-core/src/main/java/com/mpush/core/server/WebsocketServer.java b/mpush-core/src/main/java/com/mpush/core/server/WebsocketServer.java new file mode 100644 index 00000000..b58acde2 --- /dev/null +++ b/mpush-core/src/main/java/com/mpush/core/server/WebsocketServer.java @@ -0,0 +1,123 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.core.server; + +import com.mpush.api.connection.ConnectionManager; +import com.mpush.api.protocol.Command; +import com.mpush.api.service.Listener; +import com.mpush.api.spi.handler.PushHandlerFactory; +import com.mpush.common.MessageDispatcher; +import com.mpush.core.MPushServer; +import com.mpush.core.handler.AckHandler; +import com.mpush.core.handler.BindUserHandler; +import com.mpush.core.handler.HandshakeHandler; +import com.mpush.netty.server.NettyTCPServer; +import com.mpush.tools.config.CC; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; + +/** + * Created by ohun on 2016/12/16. + * + * @author ohun@live.cn (夜色) + */ +public final class WebsocketServer extends NettyTCPServer { + + private final ChannelHandler channelHandler; + + private final MessageDispatcher messageDispatcher; + + private final ConnectionManager connectionManager; + + private final MPushServer mPushServer; + + public WebsocketServer(MPushServer mPushServer) { + super(CC.mp.net.ws_server_port); + this.mPushServer = mPushServer; + this.messageDispatcher = new MessageDispatcher(); + this.connectionManager = new ServerConnectionManager(false); + this.channelHandler = new WebSocketChannelHandler(connectionManager, messageDispatcher); + } + + @Override + public void init() { + super.init(); + connectionManager.init(); + messageDispatcher.register(Command.HANDSHAKE, () -> new HandshakeHandler(mPushServer)); + messageDispatcher.register(Command.BIND, () -> new BindUserHandler(mPushServer)); + messageDispatcher.register(Command.UNBIND, () -> new BindUserHandler(mPushServer)); + messageDispatcher.register(Command.PUSH, PushHandlerFactory::create); + messageDispatcher.register(Command.ACK, () -> new AckHandler(mPushServer)); + } + + @Override + public void stop(Listener listener) { + super.stop(listener); + connectionManager.destroy(); + } + + @Override + public EventLoopGroup getBossGroup() { + return mPushServer.getConnectionServer().getBossGroup(); + } + + @Override + public EventLoopGroup getWorkerGroup() { + return mPushServer.getConnectionServer().getWorkerGroup(); + } + + @Override + protected void initPipeline(ChannelPipeline pipeline) { + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(65536)); + pipeline.addLast(new WebSocketServerCompressionHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler(CC.mp.net.ws_path, null, true)); + pipeline.addLast(new WebSocketIndexPageHandler()); + pipeline.addLast(getChannelHandler()); + } + + @Override + protected void initOptions(ServerBootstrap b) { + super.initOptions(b); + b.option(ChannelOption.SO_BACKLOG, 1024); + b.childOption(ChannelOption.SO_SNDBUF, 32 * 1024); + b.childOption(ChannelOption.SO_RCVBUF, 32 * 1024); + } + + @Override + public ChannelHandler getChannelHandler() { + return channelHandler; + } + + public ConnectionManager getConnectionManager() { + return connectionManager; + } + + public MessageDispatcher getMessageDispatcher() { + return messageDispatcher; + } +} diff --git a/mpush-core/src/main/java/com/mpush/core/session/ReusableSessionManager.java b/mpush-core/src/main/java/com/mpush/core/session/ReusableSessionManager.java index 5b9394bc..9260edb8 100644 --- a/mpush-core/src/main/java/com/mpush/core/session/ReusableSessionManager.java +++ b/mpush-core/src/main/java/com/mpush/core/session/ReusableSessionManager.java @@ -20,8 +20,9 @@ package com.mpush.core.session; import com.mpush.api.connection.SessionContext; -import com.mpush.cache.redis.RedisKey; -import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; +import com.mpush.common.CacheKeys; import com.mpush.tools.common.Strings; import com.mpush.tools.config.CC; import com.mpush.tools.crypto.MD5Utils; @@ -32,18 +33,18 @@ * @author ohun@live.cn */ public final class ReusableSessionManager { - public static final ReusableSessionManager INSTANCE = new ReusableSessionManager(); - private int expiredTime = CC.mp.core.session_expired_time; + private final int expiredTime = CC.mp.core.session_expired_time; + private final CacheManager cacheManager = CacheManagerFactory.create(); public boolean cacheSession(ReusableSession session) { - String key = RedisKey.getSessionKey(session.sessionId); - RedisManager.I.set(key, ReusableSession.encode(session.context), expiredTime); + String key = CacheKeys.getSessionKey(session.sessionId); + cacheManager.set(key, ReusableSession.encode(session.context), expiredTime); return true; } public ReusableSession querySession(String sessionId) { - String key = RedisKey.getSessionKey(sessionId); - String value = RedisManager.I.get(key, String.class); + String key = CacheKeys.getSessionKey(sessionId); + String value = cacheManager.get(key, String.class); if (Strings.isBlank(value)) return null; return ReusableSession.decode(value); } diff --git a/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.common.ExecutorFactory b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.common.ExecutorFactory new file mode 100644 index 00000000..4047aab7 --- /dev/null +++ b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.common.ExecutorFactory @@ -0,0 +1 @@ +com.mpush.core.ServerExecutorFactory \ No newline at end of file diff --git a/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.core.ServerEventListenerFactory b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.core.ServerEventListenerFactory new file mode 100644 index 00000000..a5a2c8e1 --- /dev/null +++ b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.core.ServerEventListenerFactory @@ -0,0 +1 @@ +com.mpush.core.server.DefaultServerEventListener \ No newline at end of file diff --git a/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.handler.BindValidatorFactory b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.handler.BindValidatorFactory new file mode 100644 index 00000000..edbcba68 --- /dev/null +++ b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.handler.BindValidatorFactory @@ -0,0 +1 @@ +com.mpush.core.handler.BindUserHandler$DefaultBindValidatorFactory \ No newline at end of file diff --git a/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.handler.PushHandlerFactory b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.handler.PushHandlerFactory new file mode 100644 index 00000000..42b566cc --- /dev/null +++ b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.handler.PushHandlerFactory @@ -0,0 +1 @@ +com.mpush.core.handler.ClientPushHandler \ No newline at end of file diff --git a/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.push.PushListenerFactory b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.push.PushListenerFactory new file mode 100644 index 00000000..029cdd58 --- /dev/null +++ b/mpush-core/src/main/resources/META-INF/services/com.mpush.api.spi.push.PushListenerFactory @@ -0,0 +1 @@ +com.mpush.core.push.GatewayPushListener \ No newline at end of file diff --git a/mpush-core/src/main/resources/index.html b/mpush-core/src/main/resources/index.html new file mode 100644 index 00000000..4ed1b230 --- /dev/null +++ b/mpush-core/src/main/resources/index.html @@ -0,0 +1,256 @@ + + + + + MPush WebSocket Client + + + + +

+ + +
+ + +

+ +
+ + + \ No newline at end of file diff --git a/mpush-core/src/test/resources/logback.xml b/mpush-core/src/test/resources/logback.xml deleted file mode 100644 index 1ff25c6d..00000000 --- a/mpush-core/src/test/resources/logback.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - System.out - UTF-8 - - DEBUG - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - System.err - UTF-8 - - WARN - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - - - - - diff --git a/mpush-monitor/pom.xml b/mpush-monitor/pom.xml index 886b22fc..a7f817ff 100644 --- a/mpush-monitor/pom.xml +++ b/mpush-monitor/pom.xml @@ -2,24 +2,21 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml 4.0.0 - ${mpush.groupId} mpush-monitor - ${mpush-monitor-version} jar mpush-monitor + MPUSH消息推送系统监控模块 + https://github.com/mpusher/mpush - ${mpush.groupId} - mpush-api - - - ${mpush.groupId} + ${project.groupId} mpush-tools diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/data/ResultCollector.java b/mpush-monitor/src/main/java/com/mpush/monitor/data/ResultCollector.java index f6d5f244..10c1ce03 100644 --- a/mpush-monitor/src/main/java/com/mpush/monitor/data/ResultCollector.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/data/ResultCollector.java @@ -20,6 +20,7 @@ package com.mpush.monitor.data; import com.mpush.monitor.quota.impl.*; +import com.mpush.monitor.service.ThreadPoolManager; /** * Created by yxx on 2016/5/19. @@ -27,15 +28,47 @@ * @author ohun@live.cn */ public class ResultCollector { + private final JVMInfo jvmInfo; + private final JVMGC jvmgc; + private final JVMMemory jvmMemory; + private final JVMThread jvmThread; + private final JVMThreadPool jvmThreadPool; + + public ResultCollector(ThreadPoolManager threadPoolManager) { + this.jvmInfo = new JVMInfo(); + this.jvmgc = new JVMGC(); + this.jvmMemory = new JVMMemory(); + this.jvmThread = new JVMThread(); + this.jvmThreadPool = new JVMThreadPool(threadPoolManager); + } public MonitorResult collect() { MonitorResult result = new MonitorResult(); - result.addResult("jvm-info", JVMInfo.I.monitor()); - result.addResult("jvm-gc", JVMGC.I.monitor()); - result.addResult("jvm-memory", JVMMemory.I.monitor()); - result.addResult("jvm-thread", JVMThread.I.monitor()); - result.addResult("jvm-thread-pool", JVMThreadPool.I.monitor()); + result.addResult("jvm-info", jvmInfo.monitor()); + result.addResult("jvm-gc", jvmgc.monitor()); + result.addResult("jvm-memory", jvmMemory.monitor()); + result.addResult("jvm-thread", jvmThread.monitor()); + result.addResult("jvm-thread-pool", jvmThreadPool.monitor()); return result; } + public JVMInfo getJvmInfo() { + return jvmInfo; + } + + public JVMGC getJvmgc() { + return jvmgc; + } + + public JVMMemory getJvmMemory() { + return jvmMemory; + } + + public JVMThread getJvmThread() { + return jvmThread; + } + + public JVMThreadPool getJvmThreadPool() { + return jvmThreadPool; + } } diff --git a/mpush-zk/src/main/java/com/mpush/zk/node/ZKNode.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MBeanInfo.java similarity index 72% rename from mpush-zk/src/main/java/com/mpush/zk/node/ZKNode.java rename to mpush-monitor/src/main/java/com/mpush/monitor/jmx/MBeanInfo.java index 7a279ead..9ca63461 100644 --- a/mpush-zk/src/main/java/com/mpush/zk/node/ZKNode.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MBeanInfo.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,16 +14,16 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.zk.node; +package com.mpush.monitor.jmx; /** - * Created by yxx on 2016/5/17. + * Created by ohun on 16/10/23. * - * @author ohun@live.cn + * @author ohun@live.cn (夜色) */ -public interface ZKNode { - String encode(); +public interface MBeanInfo { + String getName(); } diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MBeanRegistry.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MBeanRegistry.java new file mode 100644 index 00000000..d3c97d98 --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MBeanRegistry.java @@ -0,0 +1,177 @@ +package com.mpush.monitor.jmx; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.*; +import java.lang.management.ManagementFactory; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public class MBeanRegistry { + public static final String DOMAIN = "com.mpush"; + private static final Logger LOG = LoggerFactory.getLogger(MBeanRegistry.class); + + private static MBeanRegistry instance = new MBeanRegistry(); + + private Map mapBean2Path = new ConcurrentHashMap<>(); + + private MBeanServer mBeanServer; + + public static MBeanRegistry getInstance() { + return instance; + } + + public MBeanRegistry() { + try { + mBeanServer = ManagementFactory.getPlatformMBeanServer(); + } catch (Error e) { + // Account for running within IKVM and create a new MBeanServer + // if the PlatformMBeanServer does not exist. + mBeanServer = MBeanServerFactory.createMBeanServer(); + } + } + + /** + * Return the underlying MBeanServer that is being + * used to register MBean's. The returned MBeanServer + * may be a new empty MBeanServer if running through IKVM. + */ + public MBeanServer getPlatformMBeanServer() { + return mBeanServer; + } + + /** + * Registers a new MBean with the platform MBean server. + * + * @param bean the bean being registered + * @param parent if not null, the new bean will be registered as a child + * node of this parent. + */ + public void register(MBeanInfo bean, MBeanInfo parent) { + assert bean != null; + String path = null; + if (parent != null) { + path = mapBean2Path.get(parent); + assert path != null; + } + path = makeFullPath(path, parent); + try { + ObjectName name = makeObjectName(path, bean); + mBeanServer.registerMBean(bean, name); + mapBean2Path.put(bean, path); + } catch (JMException e) { + LOG.warn("Failed to register MBean " + bean.getName()); + throw new MException(e); + } + } + + /** + * Unregister the MBean identified by the path. + * + * @param path + * @param bean + */ + private void unregister(String path, MBeanInfo bean) { + if (path == null) return; + try { + mBeanServer.unregisterMBean(makeObjectName(path, bean)); + } catch (JMException e) { + LOG.warn("Failed to unregister MBean " + bean.getName()); + throw new MException(e); + } + } + + /** + * Unregister MBean. + * + * @param bean + */ + public void unregister(MBeanInfo bean) { + if (bean == null) return; + + String path = mapBean2Path.get(bean); + unregister(path, bean); + mapBean2Path.remove(bean); + } + + /** + * Unregister all currently registered MBeans + */ + public void unregisterAll() { + for (Map.Entry e : mapBean2Path.entrySet()) { + try { + unregister(e.getValue(), e.getKey()); + } catch (MException e1) { + LOG.warn("Error during unregister", e1); + } + } + mapBean2Path.clear(); + } + + /** + * Generate a filesystem-like path. + * + * @param prefix path prefix + * @param name path elements + * @return absolute path + */ + public String makeFullPath(String prefix, String... name) { + StringBuilder sb = new StringBuilder(prefix == null ? "/" : (prefix.equals("/") ? prefix : prefix + "/")); + boolean first = true; + for (String s : name) { + if (s == null) continue; + if (!first) { + sb.append("/"); + } else + first = false; + sb.append(s); + } + return sb.toString(); + } + + protected String makeFullPath(String prefix, MBeanInfo bean) { + return makeFullPath(prefix, bean == null ? null : bean.getName()); + } + + /** + * This takes a path, such as /a/b/c, and converts it to + * name0=a,name1=b,name2=c + */ + private int tokenize(StringBuilder sb, String path, int index) { + String[] tokens = path.split("/"); + for (String s : tokens) { + if (s.length() == 0) + continue; + sb.append("name").append(index++) + .append("=").append(s).append(","); + } + return index; + } + + /** + * Builds an MBean path and creates an ObjectName instance using the path. + * + * @param path MBean path + * @param bean the MBean instance + * @return ObjectName to be registered with the platform MBean server + */ + protected ObjectName makeObjectName(String path, MBeanInfo bean) + throws MalformedObjectNameException { + if (path == null) + return null; + StringBuilder beanName = new StringBuilder(DOMAIN).append(':'); + int counter = 0; + counter = tokenize(beanName, path, counter); + tokenize(beanName, bean.getName(), counter); + beanName.deleteCharAt(beanName.length() - 1); + try { + return new ObjectName(beanName.toString()); + } catch (MalformedObjectNameException e) { + LOG.warn("Invalid name \"" + beanName.toString() + "\" for class " + + bean.getClass().toString()); + throw e; + } + } +} \ No newline at end of file diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MException.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MException.java new file mode 100644 index 00000000..94fcdfe1 --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/MException.java @@ -0,0 +1,39 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.monitor.jmx; + +/** + * Created by ohun on 16/10/23. + * + * @author ohun@live.cn (夜色) + */ +public final class MException extends RuntimeException { + public MException(String message) { + super(message); + } + + public MException(String message, Throwable cause) { + super(message, cause); + } + + public MException(Throwable cause) { + super(cause); + } +} diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/PushCenterBean.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/PushCenterBean.java new file mode 100644 index 00000000..f994b071 --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/PushCenterBean.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.monitor.jmx.mxbean; + +import com.mpush.monitor.jmx.MBeanInfo; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by ohun on 2016/12/23. + * + * @author ohun@live.cn (夜色) + */ +public final class PushCenterBean implements PushCenterMXBean, MBeanInfo { + private final AtomicLong taskNum; + + public PushCenterBean(AtomicLong taskNum) { + this.taskNum = taskNum; + } + + @Override + public String getName() { + return "PushCenter"; + } + + @Override + public long getTaskNum() { + return taskNum.get(); + } +} diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/PushCenterMXBean.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/PushCenterMXBean.java new file mode 100644 index 00000000..b8d33b6b --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/PushCenterMXBean.java @@ -0,0 +1,29 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.monitor.jmx.mxbean; + +/** + * Created by ohun on 2016/12/23. + * + * @author ohun@live.cn (夜色) + */ +public interface PushCenterMXBean { + long getTaskNum(); +} diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/ServerMXBean.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/ServerMXBean.java new file mode 100644 index 00000000..5f872e18 --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/mxbean/ServerMXBean.java @@ -0,0 +1,132 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.monitor.jmx.mxbean; + +/** + * Created by ohun on 16/10/23. + * + * @author ohun@live.cn (夜色) + */ +public interface ServerMXBean { + /** + * @return the server socket port number + */ + String getClientPort(); + + /** + * @return the server version + */ + String getVersion(); + + /** + * @return time the server was started + */ + String getStartTime(); + + /** + * @return min request latency in ms + */ + long getMinRequestLatency(); + + /** + * @return average request latency in ms + */ + long getAvgRequestLatency(); + + /** + * @return max request latency in ms + */ + long getMaxRequestLatency(); + + /** + * @return number of packets received so far + */ + long getPacketsReceived(); + + /** + * @return number of packets sent so far + */ + long getPacketsSent(); + + /** + * @return number of outstanding requests. + */ + long getOutstandingRequests(); + + /** + * Current TickTime of server in milliseconds + */ + int getTickTime(); + + /** + * Set TickTime of server in milliseconds + */ + void setTickTime(int tickTime); + + /** + * Current maxClientCnxns allowed from a particular host + */ + int getMaxClientCnxnsPerHost(); + + /** + * Set maxClientCnxns allowed from a particular host + */ + void setMaxClientCnxnsPerHost(int max); + + /** + * Current minSessionTimeout of the server in milliseconds + */ + int getMinSessionTimeout(); + + /** + * Set minSessionTimeout of server in milliseconds + */ + void setMinSessionTimeout(int min); + + /** + * Current maxSessionTimeout of the server in milliseconds + */ + int getMaxSessionTimeout(); + + /** + * Set maxSessionTimeout of server in milliseconds + */ + void setMaxSessionTimeout(int max); + + /** + * Reset packet and latency statistics + */ + void resetStatistics(); + + /** + * Reset min/avg/max latency statistics + */ + void resetLatency(); + + /** + * Reset max latency statistics only. + */ + void resetMaxLatency(); + + /** + * @return number of alive client connections + */ + long getNumAliveConnections(); +} diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/jmx/stats/ServerStats.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/stats/ServerStats.java new file mode 100644 index 00000000..7f106fbf --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/stats/ServerStats.java @@ -0,0 +1,152 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.monitor.jmx.stats; + +/** + * Created by ohun on 16/10/23. + * + * @author ohun@live.cn (夜色) + */ +public final class ServerStats { + private long packetsSent; + private long packetsReceived; + private long maxLatency; + private long minLatency = Long.MAX_VALUE; + private long totalLatency = 0; + private long count = 0; + + private final Provider provider; + + public interface Provider { + long getOutstandingRequests(); + + long getLastProcessedXid(); + + String getState(); + + int getNumAliveConnections(); + } + + public ServerStats(Provider provider) { + this.provider = provider; + } + + // getters + synchronized public long getMinLatency() { + return minLatency == Long.MAX_VALUE ? 0 : minLatency; + } + + synchronized public long getAvgLatency() { + if (count != 0) { + return totalLatency / count; + } + return 0; + } + + synchronized public long getMaxLatency() { + return maxLatency; + } + + public long getOutstandingRequests() { + return provider.getOutstandingRequests(); + } + + public long getLastProcessedXid() { + return provider.getLastProcessedXid(); + } + + synchronized public long getPacketsReceived() { + return packetsReceived; + } + + synchronized public long getPacketsSent() { + return packetsSent; + } + + public String getServerState() { + return provider.getState(); + } + + /** + * The number of client connections alive to this server + */ + public int getNumAliveClientConnections() { + return provider.getNumAliveConnections(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Latency min/avg/max: " + getMinLatency() + "/" + + getAvgLatency() + "/" + getMaxLatency() + "\n"); + sb.append("Received: " + getPacketsReceived() + "\n"); + sb.append("Sent: " + getPacketsSent() + "\n"); + sb.append("Connections: " + getNumAliveClientConnections() + "\n"); + + if (provider != null) { + sb.append("Outstanding: " + getOutstandingRequests() + "\n"); + sb.append("xid: 0x" + Long.toHexString(getLastProcessedXid()) + "\n"); + } + sb.append("Mode: " + getServerState() + "\n"); + return sb.toString(); + } + + // mutators + synchronized void updateLatency(long requestCreateTime) { + long latency = System.currentTimeMillis() - requestCreateTime; + totalLatency += latency; + count++; + if (latency < minLatency) { + minLatency = latency; + } + if (latency > maxLatency) { + maxLatency = latency; + } + } + + synchronized public void resetLatency() { + totalLatency = 0; + count = 0; + maxLatency = 0; + minLatency = Long.MAX_VALUE; + } + + synchronized public void resetMaxLatency() { + maxLatency = getMinLatency(); + } + + synchronized public void incrementPacketsReceived() { + packetsReceived++; + } + + synchronized public void incrementPacketsSent() { + packetsSent++; + } + + synchronized public void resetRequestCounters() { + packetsReceived = 0; + packetsSent = 0; + } + + synchronized public void reset() { + resetLatency(); + resetRequestCounters(); + } +} diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/jmx/stats/Stats.java b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/stats/Stats.java new file mode 100644 index 00000000..eb8337d5 --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/jmx/stats/Stats.java @@ -0,0 +1,94 @@ +package com.mpush.monitor.jmx.stats; + +import java.util.Date; + +/** + * Statistics on the ServerCnxn + */ +interface Stats { + /** + * Date/time the connection was established + * + * @since 3.3.0 + */ + Date getEstablished(); + + /** + * The number of requests that have been submitted but not yet + * responded to. + */ + long getOutstandingRequests(); + + /** + * Number of packets received + */ + long getPacketsReceived(); + + /** + * Number of packets sent (incl notifications) + */ + long getPacketsSent(); + + /** + * Min latency in ms + * + * @since 3.3.0 + */ + long getMinLatency(); + + /** + * Average latency in ms + * + * @since 3.3.0 + */ + long getAvgLatency(); + + /** + * Max latency in ms + * + * @since 3.3.0 + */ + long getMaxLatency(); + + /** + * Last operation performed by this connection + * + * @since 3.3.0 + */ + String getLastOperation(); + + /** + * Last cxid of this connection + * + * @since 3.3.0 + */ + long getLastCxid(); + + /** + * Last zxid of this connection + * + * @since 3.3.0 + */ + long getLastZxid(); + + /** + * Last time server sent a response to client on this connection + * + * @since 3.3.0 + */ + long getLastResponseTime(); + + /** + * Latency of last response to client on this connection in ms + * + * @since 3.3.0 + */ + long getLastLatency(); + + /** + * Reset counters + * + * @since 3.3.0 + */ + void resetStats(); +} \ No newline at end of file diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMGC.java b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMGC.java index 2b9d0903..1b83ac8a 100644 --- a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMGC.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMGC.java @@ -37,8 +37,6 @@ public class JVMGC implements GCMQuota { private final List youngGcName = Lists.newArrayList("ParNew", "Copy", "PS Scavenge", "G1 Young Generation", "Garbage collection optimized for short pausetimes Young Collector", "Garbage collection optimized for throughput Young Collector", "Garbage collection optimized for deterministic pausetimes Young Collector"); - public static final JVMGC I = new JVMGC(); - private GarbageCollectorMXBean fullGc; private GarbageCollectorMXBean yongGc; @@ -48,7 +46,7 @@ public class JVMGC implements GCMQuota { private long lastFullGcCollectionTime = -1; - private JVMGC() { + public JVMGC() { for (GarbageCollectorMXBean item : ManagementFactory.getGarbageCollectorMXBeans()) { String name = item.getName(); if (youngGcName.contains(name)) { diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMInfo.java b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMInfo.java index eb0276a4..f48b6f9c 100644 --- a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMInfo.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMInfo.java @@ -30,15 +30,13 @@ public class JVMInfo implements InfoQuota { - public static final JVMInfo I = new JVMInfo(); - private RuntimeMXBean runtimeMXBean; private OperatingSystemMXBean systemMXBean; private String currentPid; - private JVMInfo() { + public JVMInfo() { runtimeMXBean = ManagementFactory.getRuntimeMXBean(); systemMXBean = ManagementFactory.getOperatingSystemMXBean(); } diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMMemory.java b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMMemory.java index 441822a4..26dc10f0 100644 --- a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMMemory.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMMemory.java @@ -48,7 +48,7 @@ public class JVMMemory implements MemoryQuota { private MemoryPoolMXBean edenSpaceMxBean; private MemoryPoolMXBean pSSurvivorSpaceMxBean; - private JVMMemory() { + public JVMMemory() { memoryMXBean = ManagementFactory.getMemoryMXBean(); List list = ManagementFactory.getMemoryPoolMXBeans(); for (MemoryPoolMXBean item : list) { diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThread.java b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThread.java index abae771a..c5df4c09 100644 --- a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThread.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThread.java @@ -30,9 +30,7 @@ public class JVMThread implements ThreadQuota { private ThreadMXBean threadMXBean; - public static final JVMThread I = new JVMThread(); - - private JVMThread() { + public JVMThread() { threadMXBean = ManagementFactory.getThreadMXBean(); } diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThreadPool.java b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThreadPool.java index 52bcd554..f7fbb9b3 100644 --- a/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThreadPool.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/quota/impl/JVMThreadPool.java @@ -20,29 +20,37 @@ package com.mpush.monitor.quota.impl; import com.mpush.monitor.quota.ThreadPoolQuota; -import com.mpush.tools.thread.pool.ThreadPoolManager; +import com.mpush.monitor.service.ThreadPoolManager; +import io.netty.channel.EventLoopGroup; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; +import static com.mpush.tools.Utils.getPoolInfo; + + public class JVMThreadPool implements ThreadPoolQuota { - public static final JVMThreadPool I = new JVMThreadPool(); + private final ThreadPoolManager threadPoolManager; - private JVMThreadPool() { + public JVMThreadPool(ThreadPoolManager threadPoolManager) { + this.threadPoolManager = threadPoolManager; } - @Override public Object monitor(Object... args) { - Map map = new HashMap<>(); - Map pool = ThreadPoolManager.I.getActivePools(); - for (Map.Entry entry : pool.entrySet()) { + Map result = new HashMap<>(); + Map pools = threadPoolManager.getActivePools(); + for (Map.Entry entry : pools.entrySet()) { String serviceName = entry.getKey(); - ThreadPoolExecutor executor = (ThreadPoolExecutor) entry.getValue(); - map.put(serviceName, ThreadPoolManager.getPoolInfo(executor)); + Executor executor = entry.getValue(); + if (executor instanceof ThreadPoolExecutor) { + result.put(serviceName, getPoolInfo((ThreadPoolExecutor) executor)); + } else if (executor instanceof EventLoopGroup) { + result.put(serviceName, getPoolInfo((EventLoopGroup) executor)); + } } - return map; + return result; } } diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/service/MonitorService.java b/mpush-monitor/src/main/java/com/mpush/monitor/service/MonitorService.java index 3ce12611..6a173ce4 100644 --- a/mpush-monitor/src/main/java/com/mpush/monitor/service/MonitorService.java +++ b/mpush-monitor/src/main/java/com/mpush/monitor/service/MonitorService.java @@ -19,36 +19,47 @@ package com.mpush.monitor.service; +import com.mpush.api.common.Monitor; import com.mpush.api.service.BaseService; import com.mpush.api.service.Listener; import com.mpush.monitor.data.MonitorResult; import com.mpush.monitor.data.ResultCollector; -import com.mpush.monitor.quota.impl.JVMInfo; +import com.mpush.tools.Utils; import com.mpush.tools.common.JVMUtil; import com.mpush.tools.config.CC; import com.mpush.tools.log.Logs; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.mpush.tools.thread.ThreadNames; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; -public class MonitorService extends BaseService implements Runnable { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - public static final MonitorService I = new MonitorService(); +public class MonitorService extends BaseService implements Monitor, Runnable { - private static final int firstJstack = 2, secondJstack = 4, thirdJstack = 6, firstJmap = 4; + private static final int FIRST_DUMP_JSTACK_LOAD_AVG = 2, + SECOND_DUMP_JSTACK_LOAD_AVG = 4, + THIRD_DUMP_JSTACK_LOAD_AVG = 6, + FIRST_DUMP_JMAP_LOAD_AVG = 4; private static final String dumpLogDir = CC.mp.monitor.dump_dir; private static final boolean dumpEnabled = CC.mp.monitor.dump_stack; private static final boolean printLog = CC.mp.monitor.print_log; private static final long dumpPeriod = CC.mp.monitor.dump_period.getSeconds(); - private boolean dumpFirstJstack = false; - private boolean dumpSecondJstack = false; - private boolean dumpThirdJstack = false; - private boolean dumpJmap = false; + private volatile boolean dumpFirstJstack = false; + private volatile boolean dumpSecondJstack = false; + private volatile boolean dumpThirdJstack = false; + private volatile boolean dumpJmap = false; - private final ResultCollector collector = new ResultCollector(); + private final ResultCollector collector; + + private final ThreadPoolManager threadPoolManager; + + public MonitorService() { + threadPoolManager = new ThreadPoolManager(); + collector = new ResultCollector(threadPoolManager); + } + + private Thread thread; @Override public void run() { @@ -56,7 +67,7 @@ public void run() { MonitorResult result = collector.collect(); if (printLog) { - Logs.Monitor.info(result.toJson()); + Logs.MONITOR.info(result.toJson()); } if (dumpEnabled) { @@ -66,7 +77,7 @@ public void run() { try { TimeUnit.SECONDS.sleep(dumpPeriod); } catch (InterruptedException e) { - stop(); + if (isRunning()) stop(); } } } @@ -74,44 +85,62 @@ public void run() { @Override protected void doStart(Listener listener) throws Throwable { if (printLog || dumpEnabled) { - Thread thread = new Thread(this, "mp-t-monitor"); + thread = Utils.newThread(ThreadNames.T_MONITOR, this); + thread.setDaemon(true); thread.start(); } + listener.onSuccess(); } @Override protected void doStop(Listener listener) throws Throwable { - logger.error("monitor service stopped!"); + if (thread != null && thread.isAlive()) thread.interrupt(); + listener.onSuccess(); } private void dump() { - double load = JVMInfo.I.load(); - if (load > firstJstack) { + double load = collector.getJvmInfo().load(); + if (load > FIRST_DUMP_JSTACK_LOAD_AVG) { if (!dumpFirstJstack) { dumpFirstJstack = true; JVMUtil.dumpJstack(dumpLogDir); } } - if (load > secondJstack) { + if (load > SECOND_DUMP_JSTACK_LOAD_AVG) { if (!dumpSecondJstack) { dumpSecondJstack = true; JVMUtil.dumpJmap(dumpLogDir); } } - if (load > thirdJstack) { + if (load > THIRD_DUMP_JSTACK_LOAD_AVG) { if (!dumpThirdJstack) { dumpThirdJstack = true; JVMUtil.dumpJmap(dumpLogDir); } } - if (load > firstJmap) { + if (load > FIRST_DUMP_JMAP_LOAD_AVG) { if (!dumpJmap) { dumpJmap = true; JVMUtil.dumpJmap(dumpLogDir); } } } + + + @Override + public void monitor(String name, Thread thread) { + + } + + @Override + public void monitor(String name, Executor executor) { + threadPoolManager.register(name, executor); + } + + public ThreadPoolManager getThreadPoolManager() { + return threadPoolManager; + } } diff --git a/mpush-monitor/src/main/java/com/mpush/monitor/service/ThreadPoolManager.java b/mpush-monitor/src/main/java/com/mpush/monitor/service/ThreadPoolManager.java new file mode 100644 index 00000000..3f9a0c14 --- /dev/null +++ b/mpush-monitor/src/main/java/com/mpush/monitor/service/ThreadPoolManager.java @@ -0,0 +1,76 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.monitor.service; + +import com.mpush.api.spi.common.ExecutorFactory; +import com.mpush.tools.thread.NamedThreadFactory; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SingleThreadEventLoop; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.ThreadProperties; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadPoolExecutor; + +public final class ThreadPoolManager { + + private final ExecutorFactory executorFactory = ExecutorFactory.create(); + + private final Map pools = new ConcurrentHashMap<>(); + + public Executor getRedisExecutor() { + return pools.computeIfAbsent("mq", s -> executorFactory.get(ExecutorFactory.MQ)); + } + + public Executor getEventBusExecutor() { + return pools.computeIfAbsent("event-bus", s -> executorFactory.get(ExecutorFactory.EVENT_BUS)); + } + + public ScheduledExecutorService getPushClientTimer() { + return (ScheduledExecutorService) pools.computeIfAbsent("push-client-timer" + , s -> executorFactory.get(ExecutorFactory.PUSH_CLIENT)); + } + + public ScheduledExecutorService getPushTaskTimer() { + return (ScheduledExecutorService) pools.computeIfAbsent("push-task-timer" + , s -> executorFactory.get(ExecutorFactory.PUSH_TASK)); + } + + public ScheduledExecutorService getAckTimer() { + return (ScheduledExecutorService) pools.computeIfAbsent("ack-timer" + , s -> executorFactory.get(ExecutorFactory.ACK_TIMER)); + } + + public void register(String name, Executor executor) { + Objects.requireNonNull(name); + Objects.requireNonNull(executor); + pools.put(name, executor); + } + + public Map getActivePools() { + return pools; + } + +} diff --git a/mpush-netty/pom.xml b/mpush-netty/pom.xml index 4fb8bc19..3b1c6035 100644 --- a/mpush-netty/pom.xml +++ b/mpush-netty/pom.xml @@ -2,26 +2,25 @@ + + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} mpush-netty - ${mpush-netty-version} jar mpush-netty + MPUSH消息推送系统Netty模块 + https://github.com/mpusher/mpush - ${mpush.groupId} - mpush-api - - - ${mpush.groupId} + ${project.groupId} mpush-tools @@ -45,5 +44,13 @@ io.netty netty-handler + + io.netty + netty-transport-udt + + + io.netty + netty-transport-sctp + diff --git a/mpush-netty/src/main/java/com/mpush/netty/client/NettyClient.java b/mpush-netty/src/main/java/com/mpush/netty/client/NettyClient.java deleted file mode 100644 index b3efb889..00000000 --- a/mpush-netty/src/main/java/com/mpush/netty/client/NettyClient.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.netty.client; - -import com.mpush.api.service.BaseService; -import com.mpush.api.service.Client; -import com.mpush.api.service.Listener; -import com.mpush.api.service.ServiceException; -import com.mpush.netty.codec.PacketDecoder; -import com.mpush.netty.codec.PacketEncoder; -import io.netty.bootstrap.Bootstrap; -import io.netty.buffer.PooledByteBufAllocator; -import io.netty.channel.*; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; - -public abstract class NettyClient extends BaseService implements Client { - private static final Logger LOGGER = LoggerFactory.getLogger(NettyClient.class); - - private final String host; - private final int port; - private EventLoopGroup workerGroup; - - public NettyClient(String host, int port) { - this.host = host; - this.port = port; - } - - @Override - public void start(final Listener listener) { - if (started.compareAndSet(false, true)) { - Bootstrap bootstrap = new Bootstrap(); - workerGroup = new NioEventLoopGroup(); - bootstrap.group(workerGroup)// - .option(ChannelOption.TCP_NODELAY, true)// - .option(ChannelOption.SO_REUSEADDR, true)// - .option(ChannelOption.SO_KEEPALIVE, true)// - .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)// - .channel(NioSocketChannel.class) - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000); - - bootstrap.handler(new ChannelInitializer() { // (4) - @Override - public void initChannel(SocketChannel ch) throws Exception { - initPipeline(ch.pipeline()); - } - }); - - ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)); - future.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - if (listener != null) listener.onSuccess(port); - LOGGER.info("start netty client success, host={}, port={}", host, port); - } else { - if (listener != null) listener.onFailure(future.cause()); - LOGGER.error("start netty client failure, host={}, port={}", host, port, future.cause()); - } - } - }); - } else { - listener.onFailure(new ServiceException("client has started!")); - } - } - - protected void initPipeline(ChannelPipeline pipeline) { - pipeline.addLast("decoder", getDecoder()); - pipeline.addLast("encoder", getEncoder()); - pipeline.addLast("handler", getChannelHandler()); - } - - protected ChannelHandler getDecoder() { - return new PacketDecoder(); - } - - protected ChannelHandler getEncoder() { - return PacketEncoder.INSTANCE; - } - - - public abstract ChannelHandler getChannelHandler(); - - @Override - protected void doStart(Listener listener) throws Throwable { - - } - - @Override - protected void doStop(Listener listener) throws Throwable { - if (workerGroup != null) { - workerGroup.shutdownGracefully(); - } - LOGGER.error("netty client [{}] stopped.", this.getClass().getSimpleName()); - } - - public String getHost() { - return host; - } - - public int getPort() { - return port; - } - - @Override - public String toString() { - return "NettyClient{" + - "host='" + host + '\'' + - ", port=" + port + - ", name=" + this.getClass().getSimpleName() + - '}'; - } -} diff --git a/mpush-netty/src/main/java/com/mpush/netty/client/NettyTCPClient.java b/mpush-netty/src/main/java/com/mpush/netty/client/NettyTCPClient.java new file mode 100644 index 00000000..85608902 --- /dev/null +++ b/mpush-netty/src/main/java/com/mpush/netty/client/NettyTCPClient.java @@ -0,0 +1,172 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.netty.client; + +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Client; +import com.mpush.api.service.Listener; +import com.mpush.netty.codec.PacketDecoder; +import com.mpush.netty.codec.PacketEncoder; +import com.mpush.tools.config.CC; +import com.mpush.tools.thread.ThreadNames; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.*; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.epoll.Native; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.nio.channels.spi.SelectorProvider; + +public abstract class NettyTCPClient extends BaseService implements Client { + private static final Logger LOGGER = LoggerFactory.getLogger(NettyTCPClient.class); + + private EventLoopGroup workerGroup; + protected Bootstrap bootstrap; + + private void createClient(Listener listener, EventLoopGroup workerGroup, ChannelFactory channelFactory) { + this.workerGroup = workerGroup; + this.bootstrap = new Bootstrap(); + bootstrap.group(workerGroup)// + .option(ChannelOption.SO_REUSEADDR, true)// + .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)// + .channelFactory(channelFactory); + bootstrap.handler(new ChannelInitializer() { // (4) + @Override + public void initChannel(Channel ch) throws Exception { + initPipeline(ch.pipeline()); + } + }); + initOptions(bootstrap); + listener.onSuccess(); + } + + public ChannelFuture connect(String host, int port) { + return bootstrap.connect(new InetSocketAddress(host, port)); + } + + public ChannelFuture connect(String host, int port, Listener listener) { + return bootstrap.connect(new InetSocketAddress(host, port)).addListener(f -> { + if (f.isSuccess()) { + if (listener != null) listener.onSuccess(port); + LOGGER.info("start netty client success, host={}, port={}", host, port); + } else { + if (listener != null) listener.onFailure(f.cause()); + LOGGER.error("start netty client failure, host={}, port={}", host, port, f.cause()); + } + }); + } + + private void createNioClient(Listener listener) { + NioEventLoopGroup workerGroup = new NioEventLoopGroup( + getWorkThreadNum(), new DefaultThreadFactory(ThreadNames.T_TCP_CLIENT), getSelectorProvider() + ); + workerGroup.setIoRatio(getIoRate()); + createClient(listener, workerGroup, getChannelFactory()); + } + + private void createEpollClient(Listener listener) { + EpollEventLoopGroup workerGroup = new EpollEventLoopGroup( + getWorkThreadNum(), new DefaultThreadFactory(ThreadNames.T_TCP_CLIENT) + ); + workerGroup.setIoRatio(getIoRate()); + createClient(listener, workerGroup, EpollSocketChannel::new); + } + + protected void initPipeline(ChannelPipeline pipeline) { + pipeline.addLast("decoder", getDecoder()); + pipeline.addLast("encoder", getEncoder()); + pipeline.addLast("handler", getChannelHandler()); + } + + protected ChannelHandler getDecoder() { + return new PacketDecoder(); + } + + protected ChannelHandler getEncoder() { + return PacketEncoder.INSTANCE; + } + + protected int getIoRate() { + return 50; + } + + protected int getWorkThreadNum() { + return 1; + } + + public abstract ChannelHandler getChannelHandler(); + + @Override + protected void doStart(Listener listener) throws Throwable { + if (useNettyEpoll()) { + createEpollClient(listener); + } else { + createNioClient(listener); + } + } + + private boolean useNettyEpoll() { + if (CC.mp.core.useNettyEpoll()) { + try { + Native.offsetofEpollData(); + return true; + } catch (UnsatisfiedLinkError error) { + LOGGER.warn("can not load netty epoll, switch nio model."); + } + } + return false; + } + + @Override + protected void doStop(Listener listener) throws Throwable { + if (workerGroup != null) { + workerGroup.shutdownGracefully(); + } + LOGGER.error("netty client [{}] stopped.", this.getClass().getSimpleName()); + listener.onSuccess(); + } + + protected void initOptions(Bootstrap b) { + b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 4000); + b.option(ChannelOption.TCP_NODELAY, true); + } + + public ChannelFactory getChannelFactory() { + return NioSocketChannel::new; + } + + public SelectorProvider getSelectorProvider() { + return SelectorProvider.provider(); + } + + @Override + public String toString() { + return "NettyClient{" + + ", name=" + this.getClass().getSimpleName() + + '}'; + } +} diff --git a/mpush-netty/src/main/java/com/mpush/netty/codec/PacketDecoder.java b/mpush-netty/src/main/java/com/mpush/netty/codec/PacketDecoder.java index 158678e9..916fde77 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/codec/PacketDecoder.java +++ b/mpush-netty/src/main/java/com/mpush/netty/codec/PacketDecoder.java @@ -19,15 +19,22 @@ package com.mpush.netty.codec; -import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; +import com.mpush.api.protocol.JsonPacket; +import com.mpush.api.protocol.UDPPacket; +import com.mpush.tools.Jsons; import com.mpush.tools.config.CC; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.TooLongFrameException; +import java.util.HashMap; import java.util.List; +import static com.mpush.api.protocol.Packet.decodePacket; + /** * Created by ohun on 2015/12/19. * length(4)+cmd(1)+cc(2)+flags(1)+sessionId(4)+lrc(1)+body(n) @@ -46,7 +53,7 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t private void decodeHeartbeat(ByteBuf in, List out) { while (in.isReadable()) { if (in.readByte() == Packet.HB_PACKET_BYTE) { - out.add(Packet.HB_PACKE); + out.add(Packet.HB_PACKET); } else { in.readerIndex(in.readerIndex() - 1); break; @@ -54,48 +61,47 @@ private void decodeHeartbeat(ByteBuf in, List out) { } } - private void decodeFrames(ByteBuf in, List out) throws Exception { - try { - while (in.readableBytes() >= Packet.HEADER_LEN) { - //1.记录当前读取位置位置.如果读取到非完整的frame,要恢复到该位置,便于下次读取 - in.markReaderIndex(); - out.add(decodeFrame(in)); + private void decodeFrames(ByteBuf in, List out) { + if (in.readableBytes() >= Packet.HEADER_LEN) { + //1.记录当前读取位置位置.如果读取到非完整的frame,要恢复到该位置,便于下次读取 + in.markReaderIndex(); + + Packet packet = decodeFrame(in); + if (packet != null) { + out.add(packet); + } else { + //2.读取到不完整的frame,恢复到最近一次正常读取的位置,便于下次读取 + in.resetReaderIndex(); } - } catch (DecodeException e) { - //2.读取到不完整的frame,恢复到最近一次正常读取的位置,便于下次读取 - in.resetReaderIndex(); } } - private Packet decodeFrame(ByteBuf in) throws Exception { - int bufferSize = in.readableBytes(); + private Packet decodeFrame(ByteBuf in) { + int readableBytes = in.readableBytes(); int bodyLength = in.readInt(); - if (bufferSize < (bodyLength + Packet.HEADER_LEN)) { - throw new DecodeException("invalid frame"); + if (readableBytes < (bodyLength + Packet.HEADER_LEN)) { + return null; } - return readPacket(in, bodyLength); + if (bodyLength > maxPacketSize) { + throw new TooLongFrameException("packet body length over limit:" + bodyLength); + } + return decodePacket(new Packet(in.readByte()), in, bodyLength); } - private Packet readPacket(ByteBuf in, int bodyLength) { - byte command = in.readByte(); - short cc = in.readShort(); - byte flags = in.readByte(); - int sessionId = in.readInt(); - byte lrc = in.readByte(); - byte[] body = null; - if (bodyLength > 0) { - if (bodyLength > maxPacketSize) { - throw new RuntimeException("ERROR PACKET_SIZE:" + bodyLength); - } - body = new byte[bodyLength]; - in.readBytes(body); + public static Packet decodeFrame(DatagramPacket frame) { + ByteBuf in = frame.content(); + int readableBytes = in.readableBytes(); + int bodyLength = in.readInt(); + if (readableBytes < (bodyLength + Packet.HEADER_LEN)) { + return null; } - Packet packet = new Packet(command); - packet.cc = cc; - packet.flags = flags; - packet.sessionId = sessionId; - packet.lrc = lrc; - packet.body = body; - return packet; + + return decodePacket(new UDPPacket(in.readByte() + , frame.sender()), in, bodyLength); + } + + public static Packet decodeFrame(String frame) throws Exception { + if (frame == null) return null; + return Jsons.fromJson(frame, JsonPacket.class); } -} +} \ No newline at end of file diff --git a/mpush-netty/src/main/java/com/mpush/netty/codec/PacketEncoder.java b/mpush-netty/src/main/java/com/mpush/netty/codec/PacketEncoder.java index ae6bfed3..22da7677 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/codec/PacketEncoder.java +++ b/mpush-netty/src/main/java/com/mpush/netty/codec/PacketEncoder.java @@ -22,10 +22,13 @@ import com.mpush.api.protocol.Command; import com.mpush.api.protocol.Packet; import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; +import static com.mpush.api.protocol.Packet.encodePacket; + /** * Created by ohun on 2015/12/19. * length(4)+cmd(1)+cc(2)+flags(1)+sessionId(4)+lrc(1)+body(n) @@ -38,18 +41,6 @@ public final class PacketEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf out) throws Exception { - if (packet.cmd == Command.HEARTBEAT.cmd) { - out.writeByte(Packet.HB_PACKET_BYTE); - } else { - out.writeInt(packet.getBodyLength()); - out.writeByte(packet.cmd); - out.writeShort(packet.cc); - out.writeByte(packet.flags); - out.writeInt(packet.sessionId); - out.writeByte(packet.lrc); - if (packet.getBodyLength() > 0) { - out.writeBytes(packet.body); - } - } + encodePacket(packet, out); } } diff --git a/mpush-netty/src/main/java/com/mpush/netty/connection/NettyConnection.java b/mpush-netty/src/main/java/com/mpush/netty/connection/NettyConnection.java index a963c00c..dababc4e 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/connection/NettyConnection.java +++ b/mpush-netty/src/main/java/com/mpush/netty/connection/NettyConnection.java @@ -19,14 +19,17 @@ package com.mpush.netty.connection; +import com.mpush.api.connection.Cipher; import com.mpush.api.connection.Connection; import com.mpush.api.connection.SessionContext; import com.mpush.api.protocol.Packet; -import com.mpush.api.spi.SpiLoader; import com.mpush.api.spi.core.CipherFactory; +import com.mpush.netty.codec.PacketEncoder; +import com.mpush.tools.log.Logs; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; +import io.netty.channel.socket.DatagramPacket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,10 +40,10 @@ */ public final class NettyConnection implements Connection, ChannelFutureListener { private static final Logger LOGGER = LoggerFactory.getLogger(NettyConnection.class); - private static final CipherFactory factory = SpiLoader.load(CipherFactory.class); + private static final Cipher RSA_CIPHER = CipherFactory.create(); private SessionContext context; private Channel channel; - private volatile int status = STATUS_NEW; + private volatile byte status = STATUS_NEW; private long lastReadTime; private long lastWriteTime; @@ -50,8 +53,8 @@ public void init(Channel channel, boolean security) { this.context = new SessionContext(); this.lastReadTime = System.currentTimeMillis(); this.status = STATUS_CONNECTED; - if (security && factory != null) { - this.context.changeCipher(factory.get()); + if (security) { + this.context.changeCipher(RSA_CIPHER); } } @@ -77,13 +80,30 @@ public ChannelFuture send(Packet packet) { @Override public ChannelFuture send(Packet packet, final ChannelFutureListener listener) { - if (channel.isActive() && channel.isWritable()) { + if (channel.isActive()) { + + ChannelFuture future = channel.writeAndFlush(packet.toFrame(channel)).addListener(this); + if (listener != null) { - return channel.writeAndFlush(packet).addListener(listener).addListener(this); - } else { - return channel.writeAndFlush(packet).addListener(this); + future.addListener(listener); } + + if (channel.isWritable()) { + return future; + } + + //阻塞调用线程还是抛异常? + //return channel.newPromise().setFailure(new RuntimeException("send data too busy")); + if (!future.channel().eventLoop().inEventLoop()) { + future.awaitUninterruptibly(100); + } + return future; } else { + /*if (listener != null) { + channel.newPromise() + .addListener(listener) + .setFailure(new RuntimeException("connection is disconnected")); + }*/ return this.close(); } } @@ -97,23 +117,22 @@ public ChannelFuture close() { @Override public boolean isConnected() { - return status == STATUS_CONNECTED || channel.isActive(); + return status == STATUS_CONNECTED; } @Override - public boolean heartbeatTimeout() { - long between = System.currentTimeMillis() - lastReadTime; - return context.heartbeat > 0 && between > context.heartbeat; + public boolean isReadTimeout() { + return System.currentTimeMillis() - lastReadTime > context.heartbeat + 1000; } @Override - public void updateLastReadTime() { - lastReadTime = System.currentTimeMillis(); + public boolean isWriteTimeout() { + return System.currentTimeMillis() - lastWriteTime > context.heartbeat - 1000; } @Override - public long getLastReadTime() { - return lastReadTime; + public void updateLastReadTime() { + lastReadTime = System.currentTimeMillis(); } @Override @@ -122,18 +141,19 @@ public void operationComplete(ChannelFuture future) throws Exception { lastWriteTime = System.currentTimeMillis(); } else { LOGGER.error("connection send msg error", future.cause()); + Logs.CONN.error("connection send msg error={}, conn={}", future.cause().getMessage(), this); } } + @Override public void updateLastWriteTime() { lastWriteTime = System.currentTimeMillis(); } - @Override public String toString() { - return "NettyConnection [context=" + context - + ", channel=" + channel + return "[channel=" + channel + + ", context=" + context + ", status=" + status + ", lastReadTime=" + lastReadTime + ", lastWriteTime=" + lastWriteTime @@ -145,5 +165,18 @@ public Channel getChannel() { return channel; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + NettyConnection that = (NettyConnection) o; + return channel.id().equals(that.channel.id()); + } + + @Override + public int hashCode() { + return channel.id().hashCode(); + } } diff --git a/mpush-netty/src/main/java/com/mpush/netty/connection/NettyConnectionManager.java b/mpush-netty/src/main/java/com/mpush/netty/connection/NettyConnectionManager.java new file mode 100644 index 00000000..81b22cae --- /dev/null +++ b/mpush-netty/src/main/java/com/mpush/netty/connection/NettyConnectionManager.java @@ -0,0 +1,68 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.netty.connection; + +import com.mpush.api.connection.Connection; +import com.mpush.api.connection.ConnectionManager; +import io.netty.channel.Channel; +import io.netty.channel.ChannelId; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public final class NettyConnectionManager implements ConnectionManager { + private final ConcurrentMap connections = new ConcurrentHashMap<>(); + + @Override + public Connection get(Channel channel) { + return connections.get(channel.id()); + } + + @Override + public Connection removeAndClose(Channel channel) { + return connections.remove(channel.id()); + } + + @Override + public void add(Connection connection) { + connections.putIfAbsent(connection.getChannel().id(), connection); + } + + @Override + public int getConnNum() { + return connections.size(); + } + + @Override + public void init() { + + } + + @Override + public void destroy() { + connections.values().forEach(Connection::close); + connections.clear(); + } +} diff --git a/mpush-netty/src/main/java/com/mpush/netty/http/HttpClientHandler.java b/mpush-netty/src/main/java/com/mpush/netty/http/HttpClientHandler.java index 26f6a31e..1eababf6 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/http/HttpClientHandler.java +++ b/mpush-netty/src/main/java/com/mpush/netty/http/HttpClientHandler.java @@ -1,8 +1,8 @@ package com.mpush.netty.http; import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.*; import io.netty.util.IllegalReferenceCountException; import io.netty.util.ReferenceCountUtil; @@ -13,8 +13,9 @@ import java.net.URLDecoder; @ChannelHandler.Sharable -public class HttpClientHandler extends ChannelHandlerAdapter { +/*package*/ class HttpClientHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(NettyHttpClient.class); + private final NettyHttpClient client; public HttpClientHandler(NettyHttpClient client) { @@ -23,7 +24,7 @@ public HttpClientHandler(NettyHttpClient client) { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - RequestContext context = ctx.channel().attr(client.requestKey).getAndRemove(); + RequestContext context = ctx.channel().attr(client.requestKey).getAndSet(null); try { if (context != null && context.tryDone()) { context.onException(cause); @@ -31,28 +32,30 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E } finally { client.pool.tryRelease(ctx.channel()); } - LOGGER.error("http client caught an error, info={}", context, cause); + LOGGER.error("http client caught an ex, info={}", context, cause); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - RequestContext context = ctx.channel().attr(client.requestKey).getAndRemove(); + RequestContext context = ctx.channel().attr(client.requestKey).getAndSet(null); try { if (context != null && context.tryDone()) { + LOGGER.debug("receive server response, request={}, response={}", context, msg); HttpResponse response = (HttpResponse) msg; if (isRedirect(response)) { if (context.onRedirect(response)) { String location = getRedirectLocation(context.request, response); if (location != null && location.length() > 0) { context.cancelled.set(false); - context.request = context.request.copy().setUri(location); + context.request.setUri(location); client.request(context); return; } } } context.onResponse(response); - LOGGER.debug("request done request={}", context); + } else { + LOGGER.warn("receive server response but timeout, request={}, response={}", context, msg); } } finally { client.pool.tryRelease(ctx.channel()); @@ -76,13 +79,13 @@ private boolean isRedirect(HttpResponse response) { } private String getRedirectLocation(HttpRequest request, HttpResponse response) throws Exception { - String hdr = URLDecoder.decode(response.headers().get(HttpHeaderNames.LOCATION).toString(), "UTF-8"); + String hdr = URLDecoder.decode(response.headers().get(HttpHeaderNames.LOCATION), "UTF-8"); if (hdr != null) { if (hdr.toLowerCase().startsWith("http://") || hdr.toLowerCase().startsWith("https://")) { return hdr; } else { URL orig = new URL(request.uri()); - String pth = orig.getPath() == null ? "/" : URLDecoder.decode(orig.getPath().toString(), "UTF-8"); + String pth = orig.getPath() == null ? "/" : URLDecoder.decode(orig.getPath(), "UTF-8"); if (hdr.startsWith("/")) { pth = hdr; } else if (pth.endsWith("/")) { @@ -90,7 +93,7 @@ private String getRedirectLocation(HttpRequest request, HttpResponse response) t } else { pth += "/" + hdr; } - StringBuilder sb = new StringBuilder(orig.getProtocol().toString()); + StringBuilder sb = new StringBuilder(orig.getProtocol()); sb.append("://").append(orig.getHost()); if (orig.getPort() > 0) { sb.append(":").append(orig.getPort()); @@ -105,15 +108,16 @@ private String getRedirectLocation(HttpRequest request, HttpResponse response) t return null; } + @SuppressWarnings("unused") private HttpRequest copy(String uri, HttpRequest request) { HttpRequest nue = request; if (request instanceof DefaultFullHttpRequest) { - DefaultFullHttpRequest dfrq = (DefaultFullHttpRequest) request; + DefaultFullHttpRequest dfr = (DefaultFullHttpRequest) request; FullHttpRequest rq; try { - rq = dfrq.copy(); - } catch (IllegalReferenceCountException e) { // Empty bytebuf - rq = dfrq; + rq = dfr.copy(); + } catch (IllegalReferenceCountException e) { // Empty byteBuf + rq = dfr; } rq.setUri(uri); } else { diff --git a/mpush-netty/src/main/java/com/mpush/netty/http/HttpConnectionPool.java b/mpush-netty/src/main/java/com/mpush/netty/http/HttpConnectionPool.java index ce55224e..8af6cdad 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/http/HttpConnectionPool.java +++ b/mpush-netty/src/main/java/com/mpush/netty/http/HttpConnectionPool.java @@ -33,10 +33,10 @@ * * @author ohun@live.cn (夜色) */ -public class HttpConnectionPool { +/*package*/ class HttpConnectionPool { private static final int maxConnPerHost = CC.mp.http.max_conn_per_host; - private final Logger LOGGER = LoggerFactory.getLogger(HttpConnectionPool.class); + private static final Logger LOGGER = LoggerFactory.getLogger(HttpConnectionPool.class); private final AttributeKey hostKey = AttributeKey.newInstance("host"); @@ -53,15 +53,15 @@ public synchronized Channel tryAcquire(String host) { LOGGER.debug("tryAcquire channel success, host={}", host); channel.attr(hostKey).set(host); return channel; - } else {//链接由于意外情况不可用了,keepAlive_timeout - LOGGER.error("tryAcquire channel false channel is inactive, host={}", host); + } else {//链接由于意外情况不可用了, 比如: keepAlive_timeout + LOGGER.warn("tryAcquire channel false channel is inactive, host={}", host); } } return null; } public synchronized void tryRelease(Channel channel) { - String host = channel.attr(hostKey).getAndRemove(); + String host = channel.attr(hostKey).getAndSet(null); List channels = channelPool.get(host); if (channels == null || channels.size() < maxConnPerHost) { LOGGER.debug("tryRelease channel success, host={}", host); @@ -77,9 +77,7 @@ public void attachHost(String host, Channel channel) { } public void close() { - for (Channel channel : channelPool.values()) { - channel.close(); - } + channelPool.values().forEach(Channel::close); channelPool.clear(); } } diff --git a/mpush-netty/src/main/java/com/mpush/netty/http/NettyHttpClient.java b/mpush-netty/src/main/java/com/mpush/netty/http/NettyHttpClient.java index 9af0db7e..1e31b710 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/http/NettyHttpClient.java +++ b/mpush-netty/src/main/java/com/mpush/netty/http/NettyHttpClient.java @@ -23,7 +23,7 @@ import com.mpush.api.service.Listener; import com.mpush.tools.config.CC; import com.mpush.tools.thread.NamedThreadFactory; -import com.mpush.tools.thread.pool.ThreadPoolManager; +import com.mpush.tools.thread.ThreadNames; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.*; @@ -36,12 +36,15 @@ import io.netty.util.AttributeKey; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; +import io.netty.util.concurrent.DefaultThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.util.concurrent.TimeUnit; +import static com.mpush.tools.config.CC.mp.thread.pool.http_work; +import static com.mpush.tools.thread.ThreadNames.T_HTTP_TIMER; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; import static io.netty.handler.codec.http.HttpHeaderNames.HOST; import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; @@ -82,17 +85,14 @@ public void request(RequestContext context) throws Exception { final long startCreate = System.currentTimeMillis(); LOGGER.debug("create new channel, host={}", host); ChannelFuture f = b.connect(host, port); - f.addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - LOGGER.debug("create new channel cost={}", (System.currentTimeMillis() - startCreate)); - if (future.isSuccess()) {//3.1.把请求写到http server - writeRequest(future.channel(), context); - } else {//3.2如果链接创建失败,直接返回客户端网关超时 - context.tryDone(); - context.onFailure(504, "Gateway Timeout"); - LOGGER.warn("create new channel failure, request={}", context); - } + f.addListener((ChannelFutureListener) future -> { + LOGGER.debug("create new channel cost={}", (System.currentTimeMillis() - startCreate)); + if (future.isSuccess()) {//3.1.把请求写到http server + writeRequest(future.channel(), context); + } else {//3.2如果链接创建失败,直接返回客户端网关超时 + context.tryDone(); + context.onFailure(504, "Gateway Timeout"); + LOGGER.warn("create new channel failure, request={}", context); } }); } else { @@ -104,23 +104,20 @@ public void operationComplete(ChannelFuture future) throws Exception { private void writeRequest(Channel channel, RequestContext context) { channel.attr(requestKey).set(context); pool.attachHost(context.host, channel); - channel.writeAndFlush(context.request).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (!future.isSuccess()) { - RequestContext info = future.channel().attr(requestKey).getAndRemove(); - info.tryDone(); - info.onFailure(503, "Service Unavailable"); - LOGGER.debug("request failure request={}", info); - pool.tryRelease(future.channel()); - } + channel.writeAndFlush(context.request).addListener((ChannelFutureListener) future -> { + if (!future.isSuccess()) { + RequestContext info = future.channel().attr(requestKey).getAndSet(null); + info.tryDone(); + info.onFailure(503, "Service Unavailable"); + LOGGER.debug("request failure request={}", info); + pool.tryRelease(future.channel()); } }); } @Override protected void doStart(Listener listener) throws Throwable { - workerGroup = new NioEventLoopGroup(0, ThreadPoolManager.I.getHttpExecutor()); + workerGroup = new NioEventLoopGroup(http_work, new DefaultThreadFactory(ThreadNames.T_HTTP_CLIENT)); b = new Bootstrap(); b.group(workerGroup); b.channel(NioSocketChannel.class); @@ -138,7 +135,8 @@ public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("handler", new HttpClientHandler(NettyHttpClient.this)); } }); - timer = new HashedWheelTimer(new NamedThreadFactory("http-client-timer-"), 1, TimeUnit.SECONDS, 64); + timer = new HashedWheelTimer(new NamedThreadFactory(T_HTTP_TIMER), 1, TimeUnit.SECONDS, 64); + listener.onSuccess(); } @Override @@ -146,5 +144,6 @@ protected void doStop(Listener listener) throws Throwable { pool.close(); workerGroup.shutdownGracefully(); timer.stop(); + listener.onSuccess(); } } diff --git a/mpush-netty/src/main/java/com/mpush/netty/http/RequestContext.java b/mpush-netty/src/main/java/com/mpush/netty/http/RequestContext.java index 915a09b3..da2eb45b 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/http/RequestContext.java +++ b/mpush-netty/src/main/java/com/mpush/netty/http/RequestContext.java @@ -31,10 +31,10 @@ public class RequestContext implements TimerTask, HttpCallback { private static final int TIMEOUT = CC.mp.http.default_read_timeout; + private final long startTime = System.currentTimeMillis(); final AtomicBoolean cancelled = new AtomicBoolean(false); - final long startTime = System.currentTimeMillis(); final int readTimeout; - long endTime = startTime; + private long endTime = startTime; private String uri; private HttpCallback callback; FullHttpRequest request; @@ -48,8 +48,9 @@ public RequestContext(FullHttpRequest request, HttpCallback callback) { } private int parseTimeout() { - String timeout = request.headers().getAndRemoveAndConvert(Constants.HTTP_HEAD_READ_TIMEOUT); + String timeout = request.headers().get(Constants.HTTP_HEAD_READ_TIMEOUT); if (timeout != null) { + request.headers().remove(Constants.HTTP_HEAD_READ_TIMEOUT); Integer integer = Ints.tryParse(timeout); if (integer != null && integer > 0) { return integer; diff --git a/mpush-netty/src/main/java/com/mpush/netty/server/NettyServer.java b/mpush-netty/src/main/java/com/mpush/netty/server/NettyTCPServer.java similarity index 53% rename from mpush-netty/src/main/java/com/mpush/netty/server/NettyServer.java rename to mpush-netty/src/main/java/com/mpush/netty/server/NettyTCPServer.java index 0b242517..672908bb 100644 --- a/mpush-netty/src/main/java/com/mpush/netty/server/NettyServer.java +++ b/mpush-netty/src/main/java/com/mpush/netty/server/NettyTCPServer.java @@ -25,21 +25,24 @@ import com.mpush.api.service.ServiceException; import com.mpush.netty.codec.PacketDecoder; import com.mpush.netty.codec.PacketEncoder; +import com.mpush.tools.common.Strings; import com.mpush.tools.config.CC; -import com.mpush.tools.log.Logs; +import com.mpush.tools.thread.ThreadNames; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.*; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; +import io.netty.channel.epoll.Native; import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Locale; -import java.util.concurrent.Executor; +import java.net.InetSocketAddress; +import java.nio.channels.spi.SelectorProvider; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicReference; /** @@ -47,7 +50,7 @@ * * @author ohun@live.cn */ -public abstract class NettyServer extends BaseService implements Server { +public abstract class NettyTCPServer extends BaseService implements Server { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -56,16 +59,23 @@ public enum State {Created, Initialized, Starting, Started, Shutdown} protected final AtomicReference serverState = new AtomicReference<>(State.Created); protected final int port; + protected final String host; protected EventLoopGroup bossGroup; protected EventLoopGroup workerGroup; - public NettyServer(int port) { + public NettyTCPServer(int port) { this.port = port; + this.host = null; + } + + public NettyTCPServer(int port, String host) { + this.port = port; + this.host = host; } public void init() { if (!serverState.compareAndSet(State.Created, State.Initialized)) { - throw new IllegalStateException("Server already init"); + throw new ServiceException("Server already init"); } } @@ -77,15 +87,14 @@ public boolean isRunning() { @Override public void stop(Listener listener) { if (!serverState.compareAndSet(State.Started, State.Shutdown)) { - IllegalStateException e = new IllegalStateException("server was already shutdown."); - if (listener != null) listener.onFailure(e); - Logs.Console.error("{} was already shutdown.", this.getClass().getSimpleName()); + if (listener != null) listener.onFailure(new ServiceException("server was already shutdown.")); + logger.error("{} was already shutdown.", this.getClass().getSimpleName()); return; } - Logs.Console.error("try shutdown {}...", this.getClass().getSimpleName()); - if (workerGroup != null) workerGroup.shutdownGracefully().syncUninterruptibly(); - if (bossGroup != null) bossGroup.shutdownGracefully().syncUninterruptibly(); - Logs.Console.error("{} shutdown success.", this.getClass().getSimpleName()); + logger.info("try shutdown {}...", this.getClass().getSimpleName()); + if (bossGroup != null) bossGroup.shutdownGracefully().syncUninterruptibly();//要先关闭接收连接的main reactor + if (workerGroup != null) workerGroup.shutdownGracefully().syncUninterruptibly();//再关闭处理业务的sub reactor + logger.info("{} shutdown success.", this.getClass().getSimpleName()); if (listener != null) { listener.onSuccess(port); } @@ -94,7 +103,7 @@ public void stop(Listener listener) { @Override public void start(final Listener listener) { if (!serverState.compareAndSet(State.Initialized, State.Starting)) { - throw new IllegalStateException("Server already started or have not init"); + throw new ServiceException("Server already started or have not init"); } if (useNettyEpoll()) { createEpollServer(listener); @@ -103,7 +112,7 @@ public void start(final Listener listener) { } } - private void createServer(final Listener listener, EventLoopGroup boss, EventLoopGroup work, Class clazz) { + private void createServer(Listener listener, EventLoopGroup boss, EventLoopGroup work, ChannelFactory channelFactory) { /*** * NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器, * Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。 @@ -133,7 +142,7 @@ private void createServer(final Listener listener, EventLoopGroup boss, EventLoo * ServerSocketChannel以NIO的selector为基础进行实现的,用来接收新的连接 * 这里告诉Channel如何获取新的连接. */ - b.channel(clazz); + b.channelFactory(channelFactory); /*** @@ -145,9 +154,9 @@ private void createServer(final Listener listener, EventLoopGroup boss, EventLoo * 当你的程序变的复杂时,可能你会增加更多的处理类到pipeline上, * 然后提取这些匿名类到最顶层的类上。 */ - b.childHandler(new ChannelInitializer() { // (4) + b.childHandler(new ChannelInitializer() { // (4) @Override - public void initChannel(SocketChannel ch) throws Exception { + public void initChannel(Channel ch) throws Exception {//每连上一个链接调用一次 initPipeline(ch.pipeline()); } }); @@ -157,60 +166,69 @@ public void initChannel(SocketChannel ch) throws Exception { /*** * 绑定端口并启动去接收进来的连接 */ - ChannelFuture f = b.bind(port).sync().addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - Logs.Console.error("server start success on:{}", port); - if (listener != null) listener.onSuccess(port); - } else { - Logs.Console.error("server start failure on:{}", port, future.cause()); - if (listener != null) listener.onFailure(future.cause()); - } + InetSocketAddress address = Strings.isBlank(host) ? new InetSocketAddress(port) : new InetSocketAddress(host, port); + b.bind(address).addListener(future -> { + if (future.isSuccess()) { + serverState.set(State.Started); + logger.info("server start success on:{}", port); + if (listener != null) listener.onSuccess(port); + } else { + logger.error("server start failure on:{}", port, future.cause()); + if (listener != null) listener.onFailure(future.cause()); } }); - if (f.isSuccess()) { - serverState.set(State.Started); - /** - * 这里会一直等待,直到socket被关闭 - */ - f.channel().closeFuture().sync(); - } - } catch (Exception e) { logger.error("server start exception", e); if (listener != null) listener.onFailure(e); throw new ServiceException("server start exception, port=" + port, e); - } finally { - /*** - * 优雅关闭 - */ - stop(null); } } private void createNioServer(Listener listener) { - NioEventLoopGroup bossGroup = new NioEventLoopGroup(1, getBossExecutor()); - NioEventLoopGroup workerGroup = new NioEventLoopGroup(0, getWorkExecutor()); - createServer(listener, bossGroup, workerGroup, NioServerSocketChannel.class); + EventLoopGroup bossGroup = getBossGroup(); + EventLoopGroup workerGroup = getWorkerGroup(); + + if (bossGroup == null) { + NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(getBossThreadNum(), getBossThreadFactory(), getSelectorProvider()); + nioEventLoopGroup.setIoRatio(100); + bossGroup = nioEventLoopGroup; + } + + if (workerGroup == null) { + NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(getWorkThreadNum(), getWorkThreadFactory(), getSelectorProvider()); + nioEventLoopGroup.setIoRatio(getIoRate()); + workerGroup = nioEventLoopGroup; + } + + createServer(listener, bossGroup, workerGroup, getChannelFactory()); } - @SuppressWarnings("unused") private void createEpollServer(Listener listener) { - EpollEventLoopGroup bossGroup = new EpollEventLoopGroup(1, getBossExecutor()); - EpollEventLoopGroup workerGroup = new EpollEventLoopGroup(0, getWorkExecutor()); - createServer(listener, bossGroup, workerGroup, EpollServerSocketChannel.class); - } + EventLoopGroup bossGroup = getBossGroup(); + EventLoopGroup workerGroup = getWorkerGroup(); - protected void initOptions(ServerBootstrap b) { + if (bossGroup == null) { + EpollEventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup(getBossThreadNum(), getBossThreadFactory()); + epollEventLoopGroup.setIoRatio(100); + bossGroup = epollEventLoopGroup; + } - /*** - * option()是提供给NioServerSocketChannel用来接收进来的连接。 - * childOption()是提供给由父管道ServerChannel接收到的连接, - * 在这个例子中也是NioServerSocketChannel。 - */ - b.childOption(ChannelOption.SO_KEEPALIVE, true); + if (workerGroup == null) { + EpollEventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup(getWorkThreadNum(), getWorkThreadFactory()); + epollEventLoopGroup.setIoRatio(getIoRate()); + workerGroup = epollEventLoopGroup; + } + createServer(listener, bossGroup, workerGroup, EpollServerSocketChannel::new); + } + + /*** + * option()是提供给NioServerSocketChannel用来接收进来的连接。 + * childOption()是提供给由父管道ServerChannel接收到的连接, + * 在这个例子中也是NioServerSocketChannel。 + */ + protected void initOptions(ServerBootstrap b) { + //b.childOption(ChannelOption.SO_KEEPALIVE, false);// 使用应用层心跳 /** * 在Netty 4中实现了一个新的ByteBuf内存池,它是一个纯Java版本的 jemalloc (Facebook也在用)。 @@ -230,36 +248,85 @@ protected ChannelHandler getDecoder() { } protected ChannelHandler getEncoder() { - return PacketEncoder.INSTANCE; + return PacketEncoder.INSTANCE;//每连上一个链接调用一次, 所有用单利 } + /** + * 每连上一个链接调用一次 + * + * @param pipeline + */ protected void initPipeline(ChannelPipeline pipeline) { pipeline.addLast("decoder", getDecoder()); pipeline.addLast("encoder", getEncoder()); pipeline.addLast("handler", getChannelHandler()); } - protected Executor getBossExecutor() { - return null; + /** + * netty 默认的Executor为ThreadPerTaskExecutor + * 线程池的使用在SingleThreadEventExecutor#doStartThread + *

+ * eventLoop.execute(runnable); + * 是比较重要的一个方法。在没有启动真正线程时, + * 它会启动线程并将待执行任务放入执行队列里面。 + * 启动真正线程(startThread())会判断是否该线程已经启动, + * 如果已经启动则会直接跳过,达到线程复用的目的 + * + * @return + */ + protected ThreadFactory getBossThreadFactory() { + return new DefaultThreadFactory(getBossThreadName()); } - protected Executor getWorkExecutor() { - return null; + protected ThreadFactory getWorkThreadFactory() { + return new DefaultThreadFactory(getWorkThreadName()); } - private boolean useNettyEpoll() { - if (!"netty".equals(CC.mp.core.epoll_provider)) return false; - String name = CC.cfg.getString("os.name").toLowerCase(Locale.UK).trim(); - return name.startsWith("linux"); + protected int getBossThreadNum() { + return 1; } - @Override - protected void doStart(Listener listener) throws Throwable { + protected int getWorkThreadNum() { + return 0; + } + protected String getBossThreadName() { + return ThreadNames.T_BOSS; } - @Override - protected void doStop(Listener listener) throws Throwable { + protected String getWorkThreadName() { + return ThreadNames.T_WORKER; + } + + protected int getIoRate() { + return 70; + } + + protected boolean useNettyEpoll() { + if (CC.mp.core.useNettyEpoll()) { + try { + Native.offsetofEpollData(); + return true; + } catch (UnsatisfiedLinkError error) { + logger.warn("can not load netty epoll, switch nio model."); + } + } + return false; + } + + public EventLoopGroup getBossGroup() { + return bossGroup; + } + + public EventLoopGroup getWorkerGroup() { + return workerGroup; + } + + public ChannelFactory getChannelFactory() { + return NioServerSocketChannel::new; + } + public SelectorProvider getSelectorProvider() { + return SelectorProvider.provider(); } } diff --git a/mpush-netty/src/main/java/com/mpush/netty/udp/NettyUDPConnector.java b/mpush-netty/src/main/java/com/mpush/netty/udp/NettyUDPConnector.java new file mode 100644 index 00000000..9046285f --- /dev/null +++ b/mpush-netty/src/main/java/com/mpush/netty/udp/NettyUDPConnector.java @@ -0,0 +1,123 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.netty.udp; + +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; +import com.mpush.api.service.Server; +import com.mpush.api.service.ServiceException; +import com.mpush.tools.thread.ThreadNames; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFactory; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.EpollDatagramChannel; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.nio.NioDatagramChannel; +import io.netty.util.concurrent.DefaultThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static io.netty.channel.socket.InternetProtocolFamily.IPv4; + +/** + * Created by ohun on 16/10/20. + * + * @author ohun@live.cn (夜色) + */ +public abstract class NettyUDPConnector extends BaseService implements Server { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + protected final int port; + private EventLoopGroup eventLoopGroup; + + public NettyUDPConnector(int port) { + this.port = port; + } + + @Override + protected void doStart(Listener listener) throws Throwable { + createNioServer(listener); + } + + @Override + protected void doStop(Listener listener) throws Throwable { + logger.info("try shutdown {}...", this.getClass().getSimpleName()); + if (eventLoopGroup != null) eventLoopGroup.shutdownGracefully().syncUninterruptibly(); + logger.info("{} shutdown success.", this.getClass().getSimpleName()); + listener.onSuccess(port); + } + + private void createServer(Listener listener, EventLoopGroup eventLoopGroup, ChannelFactory channelFactory) { + this.eventLoopGroup = eventLoopGroup; + try { + Bootstrap b = new Bootstrap(); + b.group(eventLoopGroup)//默认是根据机器情况创建Channel,如果机器支持ipv6,则无法使用ipv4的地址加入组播 + .channelFactory(channelFactory) + .option(ChannelOption.SO_BROADCAST, true) + .handler(getChannelHandler()); + + initOptions(b); + + //直接绑定端口,不要指定host,不然收不到组播消息 + b.bind(port).addListener(future -> { + if (future.isSuccess()) { + logger.info("udp server start success on:{}", port); + if (listener != null) listener.onSuccess(port); + } else { + logger.error("udp server start failure on:{}", port, future.cause()); + if (listener != null) listener.onFailure(future.cause()); + } + }); + } catch (Exception e) { + logger.error("udp server start exception", e); + if (listener != null) listener.onFailure(e); + throw new ServiceException("udp server start exception, port=" + port, e); + } + } + + private void createNioServer(Listener listener) { + NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup( + 1, new DefaultThreadFactory(ThreadNames.T_GATEWAY_WORKER) + ); + eventLoopGroup.setIoRatio(100); + createServer(listener, eventLoopGroup, () -> new NioDatagramChannel(IPv4));//默认是根据机器情况创建Channel,如果机器支持ipv6,则无法使用ipv4的地址加入组播 + } + + @SuppressWarnings("unused") + private void createEpollServer(Listener listener) { + EpollEventLoopGroup eventLoopGroup = new EpollEventLoopGroup( + 1, new DefaultThreadFactory(ThreadNames.T_GATEWAY_WORKER) + ); + eventLoopGroup.setIoRatio(100); + createServer(listener, eventLoopGroup, EpollDatagramChannel::new); + } + + protected void initOptions(Bootstrap b) { + b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + b.option(ChannelOption.SO_REUSEADDR, true); + } + + public abstract ChannelHandler getChannelHandler(); + +} diff --git a/mpush-netty/src/main/java/com/mpush/netty/udp/UDPChannelHandler.java b/mpush-netty/src/main/java/com/mpush/netty/udp/UDPChannelHandler.java new file mode 100644 index 00000000..8f8741d2 --- /dev/null +++ b/mpush-netty/src/main/java/com/mpush/netty/udp/UDPChannelHandler.java @@ -0,0 +1,116 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.netty.udp; + +import com.mpush.api.message.PacketReceiver; +import com.mpush.api.connection.Connection; +import com.mpush.api.protocol.Packet; +import com.mpush.netty.codec.PacketDecoder; +import com.mpush.netty.connection.NettyConnection; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramPacket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetAddress; +import java.net.NetworkInterface; + +/** + * Created by ohun on 16/10/21. + * + * @author ohun@live.cn (夜色) + */ +@ChannelHandler.Sharable +public final class UDPChannelHandler extends ChannelInboundHandlerAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(UDPChannelHandler.class); + private final NettyConnection connection = new NettyConnection(); + private final PacketReceiver receiver; + private InetAddress multicastAddress; + private NetworkInterface networkInterface; + + public UDPChannelHandler(PacketReceiver receiver) { + this.receiver = receiver; + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + connection.init(ctx.channel(), false); + if (multicastAddress != null) { + ((DatagramChannel) ctx.channel()).joinGroup(multicastAddress, networkInterface, null).addListener(future -> { + if (future.isSuccess()) { + LOGGER.info("join multicast group success, channel={}, group={}", ctx.channel(), multicastAddress); + } else { + LOGGER.error("join multicast group error, channel={}, group={}", ctx.channel(), multicastAddress, future.cause()); + } + }); + } + LOGGER.info("init udp channel={}", ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + connection.close(); + if (multicastAddress != null) { + ((DatagramChannel) ctx.channel()).leaveGroup(multicastAddress, networkInterface, null).addListener(future -> { + if (future.isSuccess()) { + LOGGER.info("leave multicast group success, channel={}, group={}", ctx.channel(), multicastAddress); + } else { + LOGGER.error("leave multicast group error, channel={}, group={}", ctx.channel(), multicastAddress, future.cause()); + } + }); + } + LOGGER.info("disconnect udp channel={}, connection={}", ctx.channel(), connection); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + DatagramPacket datagramPacket = (DatagramPacket) msg; + Packet packet = PacketDecoder.decodeFrame(datagramPacket); + receiver.onReceive(packet, connection); + datagramPacket.release();//最后一个使用方要释放引用 + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + connection.close(); + LOGGER.error("udp handler caught an exception, channel={}, conn={}", ctx.channel(), connection, cause); + } + + public UDPChannelHandler setMulticastAddress(InetAddress multicastAddress) { + if (!multicastAddress.isMulticastAddress()) { + throw new IllegalArgumentException(multicastAddress + "not a multicastAddress"); + } + + this.multicastAddress = multicastAddress; + return this; + } + + public UDPChannelHandler setNetworkInterface(NetworkInterface networkInterface) { + this.networkInterface = networkInterface; + return this; + } + + public Connection getConnection() { + return connection; + } +} diff --git a/mpush-netty/src/test/resources/logback.xml b/mpush-netty/src/test/resources/logback.xml deleted file mode 100644 index 8b23ef25..00000000 --- a/mpush-netty/src/test/resources/logback.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - System.out - UTF-8 - - INFO - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - System.err - UTF-8 - - WARN - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - - - - - diff --git a/mpush-test/pom.xml b/mpush-test/pom.xml index 7a1312d7..c0c0903f 100644 --- a/mpush-test/pom.xml +++ b/mpush-test/pom.xml @@ -1,43 +1,89 @@ + + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} mpush-test - ${mpush-test-version} jar mpush-test + MPUSH消息推送系统测试模块 + https://github.com/mpusher/mpush - ${mpush.groupId} - mpush-api - - - ${mpush.groupId} - mpush-netty - - - ${mpush.groupId} - mpush-tools - - - ${mpush.groupId} - mpush-core - - - ${mpush.groupId} + ${project.groupId} mpush-boot - ${mpush.groupId} + ${project.groupId} mpush-client + + junit + junit + compile + - + + + + org.apache.maven.plugins + maven-deploy-plugin + 2.8.2 + + true + + + + + + + test + + + + src/main/resources + + **/* + + + + META-INF/services/* + + true + + + + + maven-assembly-plugin + + + + com.mpush.test.client.ConnClientTestMain + + + + jar-with-dependencies + + + + + package + + single + + + + + + + + diff --git a/mpush-test/src/main/java/com/mpush/test/client/ConnClientBoot.java b/mpush-test/src/main/java/com/mpush/test/client/ConnClientBoot.java new file mode 100644 index 00000000..014297cb --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/client/ConnClientBoot.java @@ -0,0 +1,127 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.client; + +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.api.srd.ServiceNames; +import com.mpush.api.srd.ServiceNode; +import com.mpush.cache.redis.manager.RedisManager; +import com.mpush.client.connect.ClientConfig; +import com.mpush.client.connect.ConnClientChannelHandler; +import com.mpush.monitor.service.MonitorService; +import com.mpush.netty.codec.PacketDecoder; +import com.mpush.netty.codec.PacketEncoder; +import com.mpush.tools.event.EventBus; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.List; +import java.util.concurrent.Executors; + +import static com.mpush.client.connect.ConnClientChannelHandler.CONFIG_KEY; + +public final class ConnClientBoot extends BaseService { + private static final Logger LOGGER = LoggerFactory.getLogger(ConnClientBoot.class); + + private Bootstrap bootstrap; + private NioEventLoopGroup workerGroup; + private MonitorService monitorService; + + + @Override + protected void doStart(Listener listener) throws Throwable { + ServiceDiscoveryFactory.create().syncStart(); + CacheManagerFactory.create().init(); + monitorService = new MonitorService(); + EventBus.create(monitorService.getThreadPoolManager().getEventBusExecutor()); + + this.workerGroup = new NioEventLoopGroup(); + this.bootstrap = new Bootstrap(); + bootstrap.group(workerGroup)// + .option(ChannelOption.TCP_NODELAY, true)// + .option(ChannelOption.SO_REUSEADDR, true)// + .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)// + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 60 * 1000) + .option(ChannelOption.SO_RCVBUF, 5 * 1024 * 1024) + .channel(NioSocketChannel.class); + + bootstrap.handler(new ChannelInitializer() { // (4) + @Override + public void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast("decoder", new PacketDecoder()); + ch.pipeline().addLast("encoder", PacketEncoder.INSTANCE); + ch.pipeline().addLast("handler", new ConnClientChannelHandler()); + } + }); + + listener.onSuccess(); + } + + @Override + protected void doStop(Listener listener) throws Throwable { + if (workerGroup != null) workerGroup.shutdownGracefully(); + ServiceDiscoveryFactory.create().syncStop(); + CacheManagerFactory.create().destroy(); + listener.onSuccess(); + } + + public List getServers() { + return ServiceDiscoveryFactory.create().lookup(ServiceNames.CONN_SERVER); + } + + public ChannelFuture connect(InetSocketAddress remote, InetSocketAddress local, ClientConfig clientConfig) { + ChannelFuture future = local != null ? bootstrap.connect(remote, local) : bootstrap.connect(remote); + if (future.channel() != null) future.channel().attr(CONFIG_KEY).set(clientConfig); + future.addListener(f -> { + if (f.isSuccess()) { + future.channel().attr(CONFIG_KEY).set(clientConfig); + LOGGER.info("start netty client success, remote={}, local={}", remote, local); + } else { + LOGGER.error("start netty client failure, remote={}, local={}", remote, local, f.cause()); + } + }); + return future; + } + + public ChannelFuture connect(String host, int port, ClientConfig clientConfig) { + return connect(new InetSocketAddress(host, port), null, clientConfig); + } + + public Bootstrap getBootstrap() { + return bootstrap; + } + + public NioEventLoopGroup getWorkerGroup() { + return workerGroup; + } +} \ No newline at end of file diff --git a/mpush-test/src/main/java/com/mpush/test/client/ConnClientTestMain.java b/mpush-test/src/main/java/com/mpush/test/client/ConnClientTestMain.java new file mode 100644 index 00000000..c859ed6a --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/client/ConnClientTestMain.java @@ -0,0 +1,117 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.client; + +import com.mpush.api.srd.ServiceNode; +import com.mpush.client.connect.ClientConfig; +import com.mpush.client.connect.ConnClientChannelHandler; +import com.mpush.common.security.CipherBox; +import com.mpush.tools.log.Logs; +import io.netty.channel.ChannelFuture; +import org.apache.commons.lang3.math.NumberUtils; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import static com.mpush.api.srd.ServiceNames.ATTR_PUBLIC_IP; + +public class ConnClientTestMain { + + public static void main(String[] args) throws Exception { + int count = 1; + String userPrefix = ""; + int printDelay = 1; + boolean sync = true; + + if (args.length > 0) { + count = NumberUtils.toInt(args[0], count); + } + + if (args.length > 1) { + userPrefix = args[1]; + } + + if (args.length > 2) { + printDelay = NumberUtils.toInt(args[2], printDelay); + } + + if (args.length > 3) { + sync = !"1".equals(args[3]); + } + + testConnClient(count, userPrefix, printDelay, sync); + } + + @Test + public void testConnClient() throws Exception { + testConnClient(1, "", 1, true); + LockSupport.park(); + } + + private static void testConnClient(int count, String userPrefix, int printDelay, boolean sync) throws Exception { + Logs.init(); + ConnClientBoot boot = new ConnClientBoot(); + boot.start().get(); + + List serverList = boot.getServers(); + if (serverList.isEmpty()) { + boot.stop(); + System.out.println("no mpush server."); + return; + } + + if (printDelay > 0) { + Executors.newSingleThreadScheduledExecutor() + .scheduleAtFixedRate( + () -> System.err.println(ConnClientChannelHandler.STATISTICS) + , 3, printDelay, TimeUnit.SECONDS + ); + } + + for (int i = 0; i < count; i++) { + String clientVersion = "1.0." + i; + String osName = "android"; + String osVersion = "1.0.1"; + String userId = userPrefix + "user-" + i; + String deviceId = userPrefix + "test-device-id-" + i; + byte[] clientKey = CipherBox.I.randomAESKey(); + byte[] iv = CipherBox.I.randomAESIV(); + + ClientConfig config = new ClientConfig(); + config.setClientKey(clientKey); + config.setIv(iv); + config.setClientVersion(clientVersion); + config.setDeviceId(deviceId); + config.setOsName(osName); + config.setOsVersion(osVersion); + config.setUserId(userId); + + int L = serverList.size(); + int index = (int) ((Math.random() % L) * L); + ServiceNode node = serverList.get(index); + + ChannelFuture future = boot.connect(node.getHost(), node.getPort(), config); + if (sync) future.awaitUninterruptibly(); + } + } +} diff --git a/mpush-test/src/test/java/com/mpush/test/configcenter/ConfigCenterTest.java b/mpush-test/src/main/java/com/mpush/test/configcenter/ConfigCenterTest.java similarity index 97% rename from mpush-test/src/test/java/com/mpush/test/configcenter/ConfigCenterTest.java rename to mpush-test/src/main/java/com/mpush/test/configcenter/ConfigCenterTest.java index 97bedcc4..ec896c16 100644 --- a/mpush-test/src/test/java/com/mpush/test/configcenter/ConfigCenterTest.java +++ b/mpush-test/src/main/java/com/mpush/test/configcenter/ConfigCenterTest.java @@ -36,8 +36,7 @@ public void setUp() throws Exception { @Test public void testKey() { //String t = ConfigKey.app_env.getString(); - System.out.println(CC.mp.security.aes_key_length); - System.out.println(CC.mp.redis.cluster_group); + System.out.println(CC.mp.push.flow_control.global.max); } @Test diff --git a/mpush-test/src/test/java/com/mpush/test/connection/body/BodyTest.java b/mpush-test/src/main/java/com/mpush/test/connection/body/BodyTest.java similarity index 100% rename from mpush-test/src/test/java/com/mpush/test/connection/body/BodyTest.java rename to mpush-test/src/main/java/com/mpush/test/connection/body/BodyTest.java diff --git a/mpush-test/src/test/java/com/mpush/test/crypto/RsaTest.java b/mpush-test/src/main/java/com/mpush/test/crypto/RsaTest.java similarity index 100% rename from mpush-test/src/test/java/com/mpush/test/crypto/RsaTest.java rename to mpush-test/src/main/java/com/mpush/test/crypto/RsaTest.java diff --git a/mpush-test/src/test/java/com/mpush/test/gson/DnsMappingTest.java b/mpush-test/src/main/java/com/mpush/test/gson/DnsMappingTest.java similarity index 100% rename from mpush-test/src/test/java/com/mpush/test/gson/DnsMappingTest.java rename to mpush-test/src/main/java/com/mpush/test/gson/DnsMappingTest.java diff --git a/mpush-test/src/test/java/com/mpush/test/gson/GsonTest.java b/mpush-test/src/main/java/com/mpush/test/gson/GsonTest.java similarity index 74% rename from mpush-test/src/test/java/com/mpush/test/gson/GsonTest.java rename to mpush-test/src/main/java/com/mpush/test/gson/GsonTest.java index 0a89949e..3f8aa1e2 100644 --- a/mpush-test/src/test/java/com/mpush/test/gson/GsonTest.java +++ b/mpush-test/src/main/java/com/mpush/test/gson/GsonTest.java @@ -20,8 +20,8 @@ package com.mpush.test.gson; import com.google.common.collect.Maps; -import com.mpush.api.push.PushContent; -import com.mpush.api.push.PushContent.PushType; +import com.mpush.api.push.MsgType; +import com.mpush.api.push.PushMsg; import com.mpush.tools.Jsons; import org.junit.Test; @@ -35,7 +35,7 @@ public void test() { map.put("key1", 1121 + ""); map.put("key2", "value2"); - PushContent content = PushContent.build(PushType.MESSAGE, Jsons.toJson(map)); + PushMsg content = PushMsg.build(MsgType.MESSAGE, Jsons.toJson(map)); System.out.println(Jsons.toJson(content)); @@ -44,16 +44,8 @@ public void test() { @Test public void test2() { - ValueMap map = new ValueMap("1122", "value2"); - PushContent content = PushContent.build(PushType.MESSAGE, Jsons.toJson(map)); - - - System.out.println(Jsons.toJson(content)); - - PushContent newContetn = Jsons.fromJson(Jsons.toJson(content), PushContent.class); - - System.out.println(newContetn.getContent()); + System.out.println(Jsons.toJson(new ValueMap("xxx"))); } @@ -62,12 +54,17 @@ private static class ValueMap { private String key1; private String key2; + transient private boolean key3; public ValueMap(String key1, String key2) { this.key1 = key1; this.key2 = key2; } + public ValueMap(String key1) { + this.key1 = key1; + } + public String getKey1() { return key1; } @@ -77,6 +74,14 @@ public String getKey2() { } + public boolean isKey3() { + return key3; + } + + public ValueMap setKey3(boolean key3) { + this.key3 = key3; + return this; + } } } diff --git a/mpush-test/src/main/java/com/mpush/test/push/PushClientTestMain.java b/mpush-test/src/main/java/com/mpush/test/push/PushClientTestMain.java new file mode 100644 index 00000000..1c021db2 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/push/PushClientTestMain.java @@ -0,0 +1,77 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.push; + +import com.google.common.collect.Sets; +import com.mpush.api.push.*; +import com.mpush.tools.log.Logs; +import org.junit.Test; + +import java.util.Arrays; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +/** + * Created by ohun on 2016/1/7. + * + * @author ohun@live.cn + */ +public class PushClientTestMain { + + public static void main(String[] args) throws Exception { + new PushClientTestMain().testPush(); + } + + @Test + public void testPush() throws Exception { + Logs.init(); + PushSender sender = PushSender.create(); + sender.start().join(); + Thread.sleep(1000); + + for (int i = 0; i < 1; i++) { + + PushMsg msg = PushMsg.build(MsgType.MESSAGE, "this a first push."); + msg.setMsgId("msgId_" + i); + + PushContext context = PushContext.build(msg) + .setAckModel(AckModel.AUTO_ACK) + .setUserId("user-" + i) + .setBroadcast(false) + //.setTags(Sets.newHashSet("test")) + //.setCondition("tags&&tags.indexOf('test')!=-1") + //.setUserIds(Arrays.asList("user-0", "user-1")) + .setTimeout(2000) + .setCallback(new PushCallback() { + @Override + public void onResult(PushResult result) { + System.err.println("\n\n" + result); + } + }); + FutureTask future = sender.send(context); + + //System.err.println("\n\n" + future.get()); + } + + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(30)); + } + +} \ No newline at end of file diff --git a/mpush-test/src/main/java/com/mpush/test/push/PushClientTestMain2.java b/mpush-test/src/main/java/com/mpush/test/push/PushClientTestMain2.java new file mode 100644 index 00000000..0771b271 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/push/PushClientTestMain2.java @@ -0,0 +1,139 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.push; + +import com.mpush.api.push.*; +import com.mpush.common.qps.FlowControl; +import com.mpush.common.qps.GlobalFlowControl; +import com.mpush.tools.log.Logs; +import org.junit.Test; + +import java.time.LocalTime; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.LockSupport; + +/** + * Created by ohun on 2016/1/7. + * + * @author ohun@live.cn + */ +public class PushClientTestMain2 { + + public static void main(String[] args) throws Exception { + new PushClientTestMain2().testPush(); + } + + @Test + public void testPush() throws Exception { + Logs.init(); + PushSender sender = PushSender.create(); + sender.start().join(); + Thread.sleep(1000); + + + Statistics statistics = new Statistics(); + FlowControl flowControl = new GlobalFlowControl(1000);// qps=1000 + + ScheduledThreadPoolExecutor service = new ScheduledThreadPoolExecutor(4); + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { + System.out.println("time=" + LocalTime.now() + + ", flowControl=" + flowControl.report() + + ", statistics=" + statistics + ); + }, 1, 1, TimeUnit.SECONDS); + + for (int k = 0; k < 1000; k++) { + for (int i = 0; i < 1; i++) { + + while (service.getQueue().size() > 1000) Thread.sleep(1); // 防止内存溢出 + + PushMsg msg = PushMsg.build(MsgType.MESSAGE, "this a first push."); + msg.setMsgId("msgId_" + i); + + PushContext context = PushContext.build(msg) + .setAckModel(AckModel.NO_ACK) + .setUserId("user-" + i) + .setBroadcast(false) + .setTimeout(60000) + .setCallback(new PushCallback() { + @Override + public void onResult(PushResult result) { + statistics.add(result.resultCode); + } + }); + service.execute(new PushTask(sender, context, service, flowControl, statistics)); + } + } + + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(30000)); + } + + private static class PushTask implements Runnable { + PushSender sender; + FlowControl flowControl; + Statistics statistics; + ScheduledExecutorService executor; + PushContext context; + + public PushTask(PushSender sender, + PushContext context, + ScheduledExecutorService executor, + FlowControl flowControl, + Statistics statistics) { + this.sender = sender; + this.context = context; + this.flowControl = flowControl; + this.executor = executor; + this.statistics = statistics; + } + + @Override + public void run() { + if (flowControl.checkQps()) { + FutureTask future = sender.send(context); + } else { + executor.schedule(this, flowControl.getDelay(), TimeUnit.NANOSECONDS); + } + } + } + + private static class Statistics { + final AtomicInteger successNum = new AtomicInteger(); + final AtomicInteger failureNum = new AtomicInteger(); + final AtomicInteger offlineNum = new AtomicInteger(); + final AtomicInteger timeoutNum = new AtomicInteger(); + AtomicInteger[] counters = new AtomicInteger[]{successNum, failureNum, offlineNum, timeoutNum}; + + private void add(int code) { + counters[code - 1].incrementAndGet(); + } + + @Override + public String toString() { + return "{" + + "successNum=" + successNum + + ", offlineNum=" + offlineNum + + ", timeoutNum=" + timeoutNum + + ", failureNum=" + failureNum + + '}'; + } + } +} \ No newline at end of file diff --git a/mpush-test/src/test/java/com/mpush/test/redis/PubSubTest.java b/mpush-test/src/main/java/com/mpush/test/redis/PubSubTest.java similarity index 62% rename from mpush-test/src/test/java/com/mpush/test/redis/PubSubTest.java rename to mpush-test/src/main/java/com/mpush/test/redis/PubSubTest.java index f3cf52e6..ac62b1ad 100644 --- a/mpush-test/src/test/java/com/mpush/test/redis/PubSubTest.java +++ b/mpush-test/src/main/java/com/mpush/test/redis/PubSubTest.java @@ -19,32 +19,24 @@ package com.mpush.test.redis; -import com.mpush.cache.redis.RedisGroup; -import com.mpush.cache.redis.RedisServer; +import com.mpush.cache.redis.mq.ListenerDispatcher; import com.mpush.cache.redis.manager.RedisManager; -import com.mpush.cache.redis.manager.ZKRedisClusterManager; -import com.mpush.cache.redis.mq.Subscriber; import org.junit.Before; import org.junit.Test; import java.util.concurrent.locks.LockSupport; public class PubSubTest { - - private ZKRedisClusterManager redisClusterManager = ZKRedisClusterManager.I; - + ListenerDispatcher listenerDispatcher = new ListenerDispatcher(); @Before public void init() { - RedisServer node = new RedisServer("127.0.0.1", 6379, "shinemoIpo"); - RedisGroup group = new RedisGroup(); - group.addRedisNode(node); - redisClusterManager.addGroup(group); } @Test public void subpubTest() { - RedisManager.I.subscribe(Subscriber.holder, "/hello/123"); - RedisManager.I.subscribe(Subscriber.holder, "/hello/124"); + + RedisManager.I.subscribe(listenerDispatcher.getSubscriber(), "/hello/123"); + RedisManager.I.subscribe(listenerDispatcher.getSubscriber(), "/hello/124"); RedisManager.I.publish("/hello/123", "123"); RedisManager.I.publish("/hello/124", "124"); } @@ -53,8 +45,8 @@ public void subpubTest() { public void pubsubTest() { RedisManager.I.publish("/hello/123", "123"); RedisManager.I.publish("/hello/124", "124"); - RedisManager.I.subscribe(Subscriber.holder, "/hello/123"); - RedisManager.I.subscribe(Subscriber.holder, "/hello/124"); + RedisManager.I.subscribe(listenerDispatcher.getSubscriber(), "/hello/123"); + RedisManager.I.subscribe(listenerDispatcher.getSubscriber(), "/hello/124"); } @Test @@ -65,8 +57,8 @@ public void pubTest() { @Test public void subTest() { - RedisManager.I.subscribe(Subscriber.holder, "/hello/123"); - RedisManager.I.subscribe(Subscriber.holder, "/hello/124"); + RedisManager.I.subscribe(listenerDispatcher.getSubscriber(), "/hello/123"); + RedisManager.I.subscribe(listenerDispatcher.getSubscriber(), "/hello/124"); LockSupport.park(); } diff --git a/mpush-test/src/test/java/com/mpush/test/redis/RedisClusterTest.java b/mpush-test/src/main/java/com/mpush/test/redis/RedisClusterTest.java similarity index 93% rename from mpush-test/src/test/java/com/mpush/test/redis/RedisClusterTest.java rename to mpush-test/src/main/java/com/mpush/test/redis/RedisClusterTest.java index 7069153a..726bf53b 100644 --- a/mpush-test/src/test/java/com/mpush/test/redis/RedisClusterTest.java +++ b/mpush-test/src/main/java/com/mpush/test/redis/RedisClusterTest.java @@ -19,10 +19,10 @@ package com.mpush.test.redis; -import com.mpush.cache.redis.RedisClient; import com.mpush.tools.Jsons; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.junit.Before; import org.junit.Test; import redis.clients.jedis.HostAndPort; @@ -46,7 +46,7 @@ public void init() { jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7003)); jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7004)); jedisClusterNodes.add(new HostAndPort("127.0.0.1", 7005)); - cluster = new JedisCluster(jedisClusterNodes, RedisClient.CONFIG); + cluster = new JedisCluster(jedisClusterNodes, new GenericObjectPoolConfig()); } @Test diff --git a/mpush-test/src/test/java/com/mpush/test/redis/RedisUtilTest.java b/mpush-test/src/main/java/com/mpush/test/redis/RedisUtilTest.java similarity index 94% rename from mpush-test/src/test/java/com/mpush/test/redis/RedisUtilTest.java rename to mpush-test/src/main/java/com/mpush/test/redis/RedisUtilTest.java index 89ef5cb3..24859f82 100644 --- a/mpush-test/src/test/java/com/mpush/test/redis/RedisUtilTest.java +++ b/mpush-test/src/main/java/com/mpush/test/redis/RedisUtilTest.java @@ -15,12 +15,13 @@ * * Contributors: * ohun@live.cn (夜色) - */ + *//* + package com.mpush.test.redis; import com.google.common.collect.Lists; -import com.mpush.cache.redis.RedisClient; +import com.mpush.cache.redis.client.RedisClient; import com.mpush.cache.redis.RedisServer; import org.apache.commons.lang3.builder.ToStringBuilder; import org.junit.Test; @@ -34,7 +35,7 @@ public class RedisUtilTest { - RedisServer node = new RedisServer("127.0.0.1", 6379, "shinemoIpo"); + RedisServer node = new RedisServer("127.0.0.1", 6379); List nodeList = Lists.newArrayList(node); @@ -192,9 +193,9 @@ public void hashTest() { @Test public void testSet() { -// System.out.println(RedisClient.sCard(node, RedisKey.getUserOnlineKey())); +// System.out.println(RedisClient.sCard(node, RedisKey.getOnlineUserListKey())); -// List onlineUserIdList = RedisClient.sScan(node, RedisKey.getUserOnlineKey(), String.class, 0); +// List onlineUserIdList = RedisClient.sScan(node, RedisKey.getOnlineUserListKey(), String.class, 0); // System.out.println(onlineUserIdList.size()); } @@ -206,10 +207,11 @@ public void testlist() { @Test public void testsortedset() { -// RedisClient.zAdd(nodeList, RedisKey.getUserOnlineKey(), "doctor1test"); +// RedisClient.zAdd(nodeList, RedisKey.getOnlineUserListKey(), "doctor1test"); -// long len =RedisClient.zCard(node, RedisKey.getUserOnlineKey()); +// long len =RedisClient.zCard(node, RedisKey.getOnlineUserListKey()); // System.out.println(len); } } +*/ diff --git a/mpush-test/src/test/java/com/mpush/test/redis/User.java b/mpush-test/src/main/java/com/mpush/test/redis/User.java similarity index 100% rename from mpush-test/src/test/java/com/mpush/test/redis/User.java rename to mpush-test/src/main/java/com/mpush/test/redis/User.java diff --git a/mpush-test/src/test/java/com/mpush/test/sever/ServerTestMain.java b/mpush-test/src/main/java/com/mpush/test/sever/ServerTestMain.java similarity index 69% rename from mpush-test/src/test/java/com/mpush/test/sever/ServerTestMain.java rename to mpush-test/src/main/java/com/mpush/test/sever/ServerTestMain.java index 199de67d..a0201874 100644 --- a/mpush-test/src/test/java/com/mpush/test/sever/ServerTestMain.java +++ b/mpush-test/src/main/java/com/mpush/test/sever/ServerTestMain.java @@ -20,6 +20,9 @@ package com.mpush.test.sever; import com.mpush.bootstrap.Main; +import org.junit.Test; + +import java.util.concurrent.locks.LockSupport; /** * Created by yxx on 2016/5/16. @@ -27,7 +30,21 @@ * @author ohun@live.cn */ public class ServerTestMain { + public static void main(String[] args) { - Main.main(args); + start(); + } + + @Test + public void testServer() { + start(); + LockSupport.park(); } + + public static void start() { + System.setProperty("io.netty.leakDetection.level", "PARANOID"); + System.setProperty("io.netty.noKeySetOptimization", "false"); + Main.main(null); + } + } diff --git a/mpush-test/src/main/java/com/mpush/test/spi/FileCacheManger.java b/mpush-test/src/main/java/com/mpush/test/spi/FileCacheManger.java new file mode 100644 index 00000000..9563b869 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/spi/FileCacheManger.java @@ -0,0 +1,213 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.spi; + +import com.mpush.api.Constants; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.tools.Jsons; +import com.mpush.tools.log.Logs; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + +/** + * Created by ohun on 2016/12/28. + * + * @author ohun@live.cn (夜色) + */ +@SuppressWarnings("unchecked") +public final class FileCacheManger implements CacheManager { + public static final FileCacheManger I = new FileCacheManger(); + private Map cache = new ConcurrentHashMap<>(); + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + private long lastModified = 0; + private Path cacheFile; + + @Override + public void init() { + Logs.Console.warn("你正在使用的CacheManager只能用于源码测试,生产环境请使用redis 3.x."); + try { + Path dir = Paths.get(this.getClass().getResource("/").toURI()); + this.cacheFile = Paths.get(dir.toString(), "cache.dat"); + + if (!Files.exists(cacheFile)) { + Files.createFile(cacheFile); + } + + } catch (Exception e) { + e.printStackTrace(); + } + loadFormFile(); + executor.scheduleAtFixedRate(this::loadFormFile, 0, 1, TimeUnit.SECONDS); + } + + @Override + public void destroy() { + executor.shutdown(); + } + + @Override + public void del(String key) { + cache.remove(key); + writeToFile(); + } + + @Override + public long hincrBy(String key, String field, long value) { + Map fields = ((Map) cache.computeIfAbsent(key, k -> new ConcurrentHashMap<>())); + Number num = (Number) fields.get(field); + long result = num.longValue() + 1; + fields.put(field, result); + executor.execute(this::writeToFile); + return result; + } + + @Override + public void set(String key, String value) { + cache.put(key, value); + executor.execute(this::writeToFile); + } + + @Override + public void set(String key, String value, int expireTime) { + cache.put(key, value); + executor.execute(this::writeToFile); + } + + @Override + public void set(String key, Object value, int expireTime) { + cache.put(key, value); + executor.execute(this::writeToFile); + } + + @Override + public T get(String key, Class tClass) { + Object obj = cache.get(key); + if (obj == null) return null; + return Jsons.fromJson(obj.toString(), tClass); + } + + @Override + public void hset(String key, String field, String value) { + ((Map) cache.computeIfAbsent(key, k -> new ConcurrentHashMap<>())).put(field, value); + executor.execute(this::writeToFile); + } + + @Override + public void hset(String key, String field, Object value) { + ((Map) cache.computeIfAbsent(key, k -> new ConcurrentHashMap<>())).put(field, value); + executor.execute(this::writeToFile); + } + + @Override + public T hget(String key, String field, Class tClass) { + Object obj = ((Map) cache.computeIfAbsent(key, k -> new ConcurrentHashMap<>())).get(field); + if (obj == null) return null; + return Jsons.fromJson(obj.toString(), tClass); + } + + @Override + public void hdel(String key, String field) { + Object map = cache.get(key); + if (map != null) { + ((Map) map).remove(field); + } + } + + @Override + public Map hgetAll(String key, Class clazz) { + Map m = (Map) cache.get(key); + if (m == null || m.isEmpty()) return Collections.emptyMap(); + + Map result = new HashMap<>(); + for (Map.Entry o : m.entrySet()) { + result.put(o.getKey(), Jsons.fromJson(String.valueOf(o.getValue()), clazz)); + } + return result; + } + + @Override + public void zAdd(String key, String value) { + + } + + @Override + public Long zCard(String key) { + return 0L; + } + + @Override + public void zRem(String key, String value) { + + } + + @Override + public List zrange(String key, int start, int end, Class clazz) { + return Collections.emptyList(); + } + + @Override + public void lpush(String key, String... value) { + + } + + @Override + public List lrange(String key, int start, int end, Class clazz) { + return Collections.emptyList(); + } + + private synchronized void loadFormFile() { + try { + long lastModified = Files.getLastModifiedTime(cacheFile).toMillis(); + if (this.lastModified < lastModified) { + byte[] bytes = Files.readAllBytes(cacheFile); + if (bytes != null && bytes.length > 0) { + cache = Jsons.fromJson(bytes, ConcurrentHashMap.class); + } + this.lastModified = lastModified; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private synchronized void writeToFile() { + try { + Files.write(cacheFile, Jsons.toJson(cache).getBytes(Constants.UTF_8)); + this.lastModified = Files.getLastModifiedTime(cacheFile).toMillis(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void main(String[] args) { + FileCacheManger cacheManger = new FileCacheManger(); + cacheManger.init(); + cacheManger.set("1", "1"); + cacheManger.set("2", "2"); + cacheManger.destroy(); + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/spi/FileSrd.java b/mpush-test/src/main/java/com/mpush/test/spi/FileSrd.java new file mode 100644 index 00000000..0ee43903 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/spi/FileSrd.java @@ -0,0 +1,87 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.spi; + +import com.google.common.collect.Lists; +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; +import com.mpush.api.srd.*; +import com.mpush.tools.Jsons; +import com.mpush.tools.log.Logs; + +import java.util.List; + +/** + * Created by ohun on 2016/12/28. + * + * @author ohun@live.cn (夜色) + */ +public final class FileSrd extends BaseService implements ServiceRegistry, ServiceDiscovery { + + public static final FileSrd I = new FileSrd(); + + @Override + public void start(Listener listener) { + if (isRunning()) { + listener.onSuccess(); + } else { + super.start(listener); + } + } + + @Override + public void stop(Listener listener) { + if (isRunning()) { + super.stop(listener); + } else { + listener.onSuccess(); + } + } + + @Override + public void init() { + Logs.Console.warn("你正在使用的ServiceRegistry和ServiceDiscovery只能用于源码测试,生产环境请使用zookeeper."); + } + + @Override + public void register(ServiceNode node) { + FileCacheManger.I.hset(node.serviceName(), node.nodeId(), Jsons.toJson(node)); + } + + @Override + public void deregister(ServiceNode node) { + FileCacheManger.I.hdel(node.serviceName(), node.nodeId()); + } + + @Override + public List lookup(String path) { + return Lists.newArrayList(FileCacheManger.I.hgetAll(path, CommonServiceNode.class).values()); + } + + @Override + public void subscribe(String path, ServiceListener listener) { + + } + + @Override + public void unsubscribe(String path, ServiceListener listener) { + + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/spi/SimpleCacheMangerFactory.java b/mpush-test/src/main/java/com/mpush/test/spi/SimpleCacheMangerFactory.java new file mode 100644 index 00000000..598e2ac6 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/spi/SimpleCacheMangerFactory.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.spi; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.CacheManager; +import com.mpush.api.spi.common.CacheManagerFactory; + +/** + * Created by ohun on 2016/12/28. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 2) +public final class SimpleCacheMangerFactory implements CacheManagerFactory { + @Override + public CacheManager get() { + return FileCacheManger.I; + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/spi/SimpleDiscoveryFactory.java b/mpush-test/src/main/java/com/mpush/test/spi/SimpleDiscoveryFactory.java new file mode 100644 index 00000000..3165d582 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/spi/SimpleDiscoveryFactory.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.spi; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.api.srd.ServiceDiscovery; + +/** + * Created by ohun on 2016/12/28. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 2) +public final class SimpleDiscoveryFactory implements ServiceDiscoveryFactory { + @Override + public ServiceDiscovery get() { + return FileSrd.I; + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/spi/SimpleMQClientFactory.java b/mpush-test/src/main/java/com/mpush/test/spi/SimpleMQClientFactory.java new file mode 100644 index 00000000..0d49236e --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/spi/SimpleMQClientFactory.java @@ -0,0 +1,49 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.spi; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.MQClient; +import com.mpush.api.spi.common.MQMessageReceiver; + +/** + * Created by ohun on 2016/12/28. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 2) +public final class SimpleMQClientFactory implements com.mpush.api.spi.common.MQClientFactory { + private MQClient mqClient = new MQClient() { + @Override + public void subscribe(String topic, MQMessageReceiver receiver) { + System.err.println("subscribe " + topic); + } + + @Override + public void publish(String topic, Object message) { + System.err.println("publish " + topic + " " + message); + } + }; + + @Override + public MQClient get() { + return mqClient; + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/spi/SimpleRegistryFactory.java b/mpush-test/src/main/java/com/mpush/test/spi/SimpleRegistryFactory.java new file mode 100644 index 00000000..28ffdb0d --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/spi/SimpleRegistryFactory.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.spi; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.ServiceRegistryFactory; +import com.mpush.api.srd.ServiceRegistry; + +/** + * Created by ohun on 2016/12/28. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 2) +public final class SimpleRegistryFactory implements ServiceRegistryFactory { + @Override + public ServiceRegistry get() { + return FileSrd.I; + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/udp/MulticastTest.java b/mpush-test/src/main/java/com/mpush/test/udp/MulticastTest.java new file mode 100644 index 00000000..58dcd434 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/udp/MulticastTest.java @@ -0,0 +1,71 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.udp; + +import com.mpush.tools.Utils; +import org.junit.Test; + +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.DatagramChannel; + +/** + * Created by ohun on 16/10/21. + * + * @author ohun@live.cn (夜色) + */ +public final class MulticastTest { + @Test + public void TestServer() throws Exception { + //接受组播和发送组播的数据报服务都要把组播地址添加进来 + String host = "239.239.239.88";//多播地址 + int port = 9998; + InetAddress group = InetAddress.getByName(host); + + DatagramChannel channel = DatagramChannel.open(StandardProtocolFamily.INET); + channel.bind(new InetSocketAddress(port)); + channel.join(group, Utils.getLocalNetworkInterface()); + ByteBuffer buffer = ByteBuffer.allocate(1024); + SocketAddress sender = channel.receive(buffer); + buffer.flip(); + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + System.out.println(new String(data)); + + } + + @Test + public void testSend() throws Exception { + String host = "239.239.239.99";//多播地址 + int port = 9999; + InetAddress group = InetAddress.getByName(host); + String message = "test-multicastSocket"; + + DatagramChannel channel = DatagramChannel.open(StandardProtocolFamily.INET); + channel.configureBlocking(true); + channel.bind(new InetSocketAddress(port)); + channel.join(group, Utils.getLocalNetworkInterface()); + + InetSocketAddress sender = new InetSocketAddress("239.239.239.99", 4000); + channel.send(ByteBuffer.wrap(message.getBytes()), sender); + + channel.close(); + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/udp/MulticastTest2.java b/mpush-test/src/main/java/com/mpush/test/udp/MulticastTest2.java new file mode 100644 index 00000000..ef1840ae --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/udp/MulticastTest2.java @@ -0,0 +1,125 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.udp; + +import com.mpush.tools.Utils; +import org.junit.Test; + +import java.io.IOException; +import java.net.*; + +/** + * Created by ohun on 16/10/21. + * + * @author ohun@live.cn (夜色) + */ +public final class MulticastTest2 { + @Test + public void TestServer() throws Exception { + //接受组播和发送组播的数据报服务都要把组播地址添加进来 + String host = "239.239.239.99";//多播地址 + int port = 9998; + int length = 1024; + byte[] buf = new byte[length]; + MulticastSocket ms = null; + DatagramPacket dp = null; + StringBuffer sbuf = new StringBuffer(); + try { + ms = new MulticastSocket(port); + dp = new DatagramPacket(buf, length); + + //加入多播地址 + ms.joinGroup(new InetSocketAddress(host, port), Utils.getLocalNetworkInterface()); + System.out.println("监听多播端口打开:"); + ms.receive(dp); + ms.close(); + int i; + for (i = 0; i < 1024; i++) { + if (buf[i] == 0) { + break; + } + sbuf.append((char) buf[i]); + } + System.out.println("收到多播消息:" + sbuf.toString()); + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + @Test + public void testSend2() throws Exception { + String host = "239.239.239.99";//多播地址 + int port = 9998; + String message = "test-multicastSocket"; + //接受组播和发送组播的数据报服务都要把组播地址添加进来 + int length = 1024; + byte[] buf = new byte[length]; + MulticastSocket ms = null; + DatagramPacket dp = null; + StringBuffer sbuf = new StringBuffer(); + try { + ms = new MulticastSocket(port); + dp = new DatagramPacket(buf, length); + InetAddress group = InetAddress.getByName(host); + //加入多播地址 + ms.joinGroup(new InetSocketAddress("239.239.239.88", 9999), Utils.getLocalNetworkInterface()); + System.out.println("监听多播端口打开:"); + DatagramPacket dp2 = new DatagramPacket(message.getBytes(), message.length(), group, port); + ms.send(dp2); + ms.receive(dp); + ms.close(); + int i; + for (i = 0; i < 1024; i++) { + if (buf[i] == 0) { + break; + } + sbuf.append((char) buf[i]); + } + System.out.println("收到多播消息:" + sbuf.toString()); + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + @Test + public void testSend() throws Exception { + String host = "239.239.239.99";//多播地址 + int port = 9998; + String message = "test-multicastSocket"; + try { + InetAddress group = InetAddress.getByName(host); + MulticastSocket s = new MulticastSocket(); + //加入多播组 + s.joinGroup(new InetSocketAddress(host, port), Utils.getLocalNetworkInterface()); + DatagramPacket dp = new DatagramPacket(message.getBytes(), message.length(), group, port); + s.send(dp); + s.close(); + } catch (UnknownHostException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + } +} diff --git a/mpush-test/src/main/java/com/mpush/test/util/IPTest.java b/mpush-test/src/main/java/com/mpush/test/util/IPTest.java new file mode 100644 index 00000000..47b3e974 --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/util/IPTest.java @@ -0,0 +1,38 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.util; + +import com.mpush.tools.Utils; +import org.junit.Test; + +/** + * Created by ohun on 16/9/8. + * + * @author ohun@live.cn (夜色) + */ +public class IPTest { + @Test + public void getLocalIP() throws Exception { + System.out.println(Utils.lookupLocalIp()); + System.out.println(Utils.lookupExtranetIp()); + + } +} + diff --git a/mpush-test/src/test/java/com/mpush/test/util/TelnetTest.java b/mpush-test/src/main/java/com/mpush/test/util/TelnetTest.java similarity index 100% rename from mpush-test/src/test/java/com/mpush/test/util/TelnetTest.java rename to mpush-test/src/main/java/com/mpush/test/util/TelnetTest.java diff --git a/mpush-test/src/main/java/com/mpush/test/zk/ZKClientTest.java b/mpush-test/src/main/java/com/mpush/test/zk/ZKClientTest.java new file mode 100644 index 00000000..d31c307a --- /dev/null +++ b/mpush-test/src/main/java/com/mpush/test/zk/ZKClientTest.java @@ -0,0 +1,94 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.test.zk; + +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.api.spi.common.ServiceRegistryFactory; +import com.mpush.api.srd.*; +import com.mpush.common.ServerNodes; +import com.mpush.zk.ZKClient; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.locks.LockSupport; + +import static org.apache.curator.utils.ZKPaths.PATH_SEPARATOR; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +public final class ZKClientTest { + public static void main(String[] args) { + ServiceRegistry registry = ServiceRegistryFactory.create(); + registry.syncStart(); + + registry.register(ServerNodes.gs()); + registry.register(ServerNodes.gs()); + registry.deregister(ServerNodes.gs()); + LockSupport.park(); + } + + @Test + public void testDiscovery() { + ServiceDiscovery discovery = ServiceDiscoveryFactory.create(); + discovery.syncStart(); + + System.err.println(discovery.lookup(ServiceNames.GATEWAY_SERVER)); + discovery.subscribe(ServiceNames.GATEWAY_SERVER, new ServiceListener() { + @Override + public void onServiceAdded(String path, ServiceNode node) { + System.err.println(path + "," + node); + System.err.println(discovery.lookup(ServiceNames.GATEWAY_SERVER)); + } + + @Override + public void onServiceUpdated(String path, ServiceNode node) { + System.err.println(path + "," + node); + System.err.println(discovery.lookup(ServiceNames.GATEWAY_SERVER)); + } + + @Override + public void onServiceRemoved(String path, ServiceNode node) { + System.err.println(path + "," + node); + System.err.println(discovery.lookup(ServiceNames.GATEWAY_SERVER)); + } + }); + LockSupport.park(); + } + + @Test + public void testZK() throws Exception { + ZKClient.I.syncStart(); + ZKClient.I.registerEphemeral(ServerNodes.gs().serviceName(), "3"); + ZKClient.I.registerEphemeral(ServerNodes.gs().serviceName(), "4"); + System.err.println("==================" + ZKClient.I.getChildrenKeys(ServiceNames.GATEWAY_SERVER)); + List rawData = ZKClient.I.getChildrenKeys(ServiceNames.GATEWAY_SERVER); + if (rawData == null || rawData.isEmpty()) { + return; + } + for (String raw : rawData) { + String fullPath = ServiceNames.GATEWAY_SERVER + PATH_SEPARATOR + raw; + System.err.println("==================" + ZKClient.I.get(fullPath)); + } + + } +} diff --git a/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.CacheManagerFactory b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.CacheManagerFactory new file mode 100644 index 00000000..d18ebe2f --- /dev/null +++ b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.CacheManagerFactory @@ -0,0 +1 @@ +com.mpush.test.spi.SimpleCacheMangerFactory \ No newline at end of file diff --git a/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.MQClientFactory b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.MQClientFactory new file mode 100644 index 00000000..8802ab44 --- /dev/null +++ b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.MQClientFactory @@ -0,0 +1 @@ +com.mpush.test.spi.SimpleMQClientFactory \ No newline at end of file diff --git a/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceDiscoveryFactory b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceDiscoveryFactory new file mode 100644 index 00000000..ec8badc5 --- /dev/null +++ b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceDiscoveryFactory @@ -0,0 +1 @@ +com.mpush.test.spi.SimpleDiscoveryFactory \ No newline at end of file diff --git a/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceRegistryFactory b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceRegistryFactory new file mode 100644 index 00000000..4f35811f --- /dev/null +++ b/mpush-test/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceRegistryFactory @@ -0,0 +1 @@ +com.mpush.test.spi.SimpleRegistryFactory \ No newline at end of file diff --git a/mpush-test/src/main/resources/application.conf b/mpush-test/src/main/resources/application.conf new file mode 100644 index 00000000..fe0f38e9 --- /dev/null +++ b/mpush-test/src/main/resources/application.conf @@ -0,0 +1,20 @@ +mp.home=${user.dir}/target +mp.log-level=debug +mp.log-conf-path=logback.xml +mp.core.min-heartbeat=30s +mp.core.max-heartbeat=30s +mp.core.compress-threshold=10k +mp.zk.server-address="127.0.0.1:2181" +mp.redis {// redis 集群配置 + nodes:["127.0.0.1:6379"]//格式是ip:port,密码可以没有ip:port +} +mp.http.proxy-enabled=true + +mp.net { + gateway-server-net=tcp //网关服务使用的网络类型tcp/udp + connect-server-port=3000 //长链接服务对外端口, 公网端口 + gateway-server-port=3001 //网关服务端口, 内部端口 + gateway-client-port=4000 //UDP客户端端口, 内部端口 + admin-server-port=3002 //控制台服务端口, 内部端口 + ws-server-port=8008 //websocket对外端口, 0表示禁用websocket +} diff --git a/mpush-test/src/main/resources/logback.xml b/mpush-test/src/main/resources/logback.xml new file mode 100644 index 00000000..fdcc5c26 --- /dev/null +++ b/mpush-test/src/main/resources/logback.xml @@ -0,0 +1,193 @@ + + + + + + System.err + + WARN + + + %d{HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n + + + + + + + info + ACCEPT + DENY + + System.out + + + %d{HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n + + + + + + + debug + ACCEPT + DENY + + System.out + + + %d{HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n + + + + + + System.out + + + DEBUG + + + %d{HH:mm:ss.SSS} -[monitor]- %msg%n + + + + + + System.out + + + DEBUG + + + %d{HH:mm:ss.SSS} -[connection]- %msg%n + + + + + + System.out + + + DEBUG + + + %d{HH:mm:ss.SSS} -[push]- %msg%n + + + + + + System.out + + DEBUG + + + %d{HH:mm:ss.SSS} -[heartbeat]- %msg%n + + + + + + System.out + + DEBUG + + + %d{HH:mm:ss.SSS} -[redis]- %msg%n + + + + + + System.out + + DEBUG + + + %d{HH:mm:ss.SSS} -[http]- %msg%n + + + + + + System.out + + DEBUG + + + %d{HH:mm:ss.SSS} -[zk]- %msg%n + + + + + + System.err + + DEBUG + + + %d{HH:mm:ss.SSS} -[profile]- %msg%n + + + + + System.err + + DEBUG + + + %d{HH:mm:ss.SSS} -[console]- %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mpush-test/src/test/java/com/mpush/test/client/ConnClientTestMain.java b/mpush-test/src/test/java/com/mpush/test/client/ConnClientTestMain.java deleted file mode 100644 index c8913e04..00000000 --- a/mpush-test/src/test/java/com/mpush/test/client/ConnClientTestMain.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.test.client; - -import com.mpush.api.service.Client; -import com.mpush.client.connect.ClientConfig; -import com.mpush.client.connect.ConnectClient; -import com.mpush.common.security.CipherBox; -import com.mpush.tools.log.Logs; -import com.mpush.zk.node.ZKServerNode; - -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -public class ConnClientTestMain { - - public static void main(String[] args) throws Exception { - Logs.init(); - ConnectClientBoot main = new ConnectClientBoot(); - main.run(); - - List serverList = main.getServers(); - - int index = (int) ((Math.random() % serverList.size()) * serverList.size()); - ZKServerNode server = serverList.get(index); - //server = new ZKServerNode("127.0.0.1", 3000, "127.0.0.1", null); - for (int i = 0; i < 1; i++) { - String clientVersion = "1.0." + i; - String osName = "android"; - String osVersion = "1.0.1"; - String userId = "user-" + i; - String deviceId = "test-device-id-" + i; - byte[] clientKey = CipherBox.I.randomAESKey(); - byte[] iv = CipherBox.I.randomAESIV(); - - ClientConfig config = new ClientConfig(); - config.setClientKey(clientKey); - config.setIv(iv); - config.setClientVersion(clientVersion); - config.setDeviceId(deviceId); - config.setOsName(osName); - config.setOsVersion(osVersion); - config.setUserId(userId); - Client client = new ConnectClient(server.getExtranetIp(), server.getPort(), config); - client.start().get(10, TimeUnit.SECONDS); - } - - LockSupport.park(); - - } - -} diff --git a/mpush-test/src/test/java/com/mpush/test/client/ConnClientTestMain2.java b/mpush-test/src/test/java/com/mpush/test/client/ConnClientTestMain2.java deleted file mode 100644 index 5abd1cc5..00000000 --- a/mpush-test/src/test/java/com/mpush/test/client/ConnClientTestMain2.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.test.client; - -import com.mpush.api.service.Client; -import com.mpush.client.connect.ClientConfig; -import com.mpush.client.connect.ConnectClient; -import com.mpush.common.security.CipherBox; -import com.mpush.tools.log.Logs; -import com.mpush.zk.node.ZKServerNode; - -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -public class ConnClientTestMain2 { - - public static void main(String[] args) throws Exception { - Logs.init(); - ConnectClientBoot main = new ConnectClientBoot(); - main.run(); - - List serverList = main.getServers(); - - int index = (int) ((Math.random() % serverList.size()) * serverList.size()); - ZKServerNode server = serverList.get(index); - //server = new ZKServerNode("127.0.0.1", 3000, "127.0.0.1", null); - - ClientConfig config = new ClientConfig(); - config.setClientKey(CipherBox.I.randomAESKey()); - config.setIv(CipherBox.I.randomAESIV()); - config.setClientVersion("1.0.0"); - config.setDeviceId("android-device-id-1"); - config.setOsName("android"); - config.setOsVersion("1.0.1"); - config.setUserId("user-0"); - Client client = new ConnectClient(server.getExtranetIp(), server.getPort(), config); - client.start().get(10, TimeUnit.SECONDS); - - config = new ClientConfig(); - config.setClientKey(CipherBox.I.randomAESKey()); - config.setIv(CipherBox.I.randomAESIV()); - config.setClientVersion("1.0.0"); - config.setDeviceId("pc-device-id-2"); - config.setOsName("pc"); - config.setOsVersion("1.0.1"); - config.setUserId("user-0"); - client = new ConnectClient(server.getExtranetIp(), server.getPort(), config); - client.start().get(10, TimeUnit.SECONDS); - - LockSupport.park(); - } - -} diff --git a/mpush-test/src/test/java/com/mpush/test/client/ConnectClientBoot.java b/mpush-test/src/test/java/com/mpush/test/client/ConnectClientBoot.java deleted file mode 100644 index 940dd8c3..00000000 --- a/mpush-test/src/test/java/com/mpush/test/client/ConnectClientBoot.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.test.client; - -import com.google.common.collect.Lists; -import com.mpush.cache.redis.manager.RedisManager; -import com.mpush.zk.ZKClient; -import com.mpush.zk.listener.ZKServerNodeWatcher; -import com.mpush.zk.node.ZKServerNode; - -import java.util.List; - -public class ConnectClientBoot { - private final ZKServerNodeWatcher listener = ZKServerNodeWatcher.buildConnect(); - - public void run() { - ZKClient.I.start(); - RedisManager.I.init(); - listener.beginWatch(); - } - - public List getServers() { - return Lists.newArrayList(listener.getCache().values()); - } -} \ No newline at end of file diff --git a/mpush-test/src/test/java/com/mpush/test/connection/mpns/ConnClientTestMain.java b/mpush-test/src/test/java/com/mpush/test/connection/mpns/ConnClientTestMain.java deleted file mode 100644 index 9452bccc..00000000 --- a/mpush-test/src/test/java/com/mpush/test/connection/mpns/ConnClientTestMain.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.test.connection.mpns; - -import com.mpush.api.service.Client; -import com.mpush.client.connect.ClientConfig; -import com.mpush.client.connect.ConnectClient; -import com.mpush.common.security.CipherBox; -import com.mpush.zk.node.ZKServerNode; - -import java.util.List; -import java.util.concurrent.locks.LockSupport; - -public class ConnClientTestMain { - - public static void main(String[] args) throws InterruptedException { - - ConnectTestClientBoot main = new ConnectTestClientBoot(); - main.start(); - - List serverList = main.getServers(); - - int index = (int) ((Math.random() % serverList.size()) * serverList.size()); - ZKServerNode server = serverList.get(index); - - for (int i = 0; i < 1000; i++) { - String clientVersion = "1.0." + i; - String osName = "android"; - String osVersion = "1.0.1"; - String userId = "uh-" + i; - String deviceId = "test-device-id-" + i; - String cipher = ""; - byte[] clientKey = CipherBox.I.randomAESKey(); - byte[] iv = CipherBox.I.randomAESIV(); - - ClientConfig config = new ClientConfig(); - config.setClientKey(clientKey); - config.setIv(iv); - config.setClientVersion(clientVersion); - config.setDeviceId(deviceId); - config.setOsName(osName); - config.setOsVersion(osVersion); - config.setUserId(userId); - config.setCipher(cipher); - Client client = new ConnectClient(server.getIp(), server.getPort(), config); - Thread.sleep(100); - } - - LockSupport.park(); - } - -} diff --git a/mpush-test/src/test/java/com/mpush/test/connection/mpns/ConnectTestClientBoot.java b/mpush-test/src/test/java/com/mpush/test/connection/mpns/ConnectTestClientBoot.java deleted file mode 100644 index d927e1c6..00000000 --- a/mpush-test/src/test/java/com/mpush/test/connection/mpns/ConnectTestClientBoot.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.test.connection.mpns; - -import com.google.common.collect.Lists; -import com.mpush.zk.listener.ZKServerNodeWatcher; -import com.mpush.zk.node.ZKServerNode; - -import java.util.List; - -public class ConnectTestClientBoot { - private final ZKServerNodeWatcher listener = ZKServerNodeWatcher.buildConnect(); - - public void start() { - listener.beginWatch(); - } - - public List getServers() { - return Lists.newArrayList(listener.getCache().values()); - } -} diff --git a/mpush-test/src/test/java/com/mpush/test/push/PushClientTestMain.java b/mpush-test/src/test/java/com/mpush/test/push/PushClientTestMain.java deleted file mode 100644 index fbbe8199..00000000 --- a/mpush-test/src/test/java/com/mpush/test/push/PushClientTestMain.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.test.push; - -import com.mpush.api.push.PushContent; -import com.mpush.api.push.PushContent.PushType; -import com.mpush.api.push.PushSender; -import com.mpush.api.router.ClientLocation; -import com.mpush.tools.Jsons; -import com.mpush.tools.log.Logs; - -import java.util.Arrays; -import java.util.concurrent.locks.LockSupport; - -/** - * Created by ohun on 2016/1/7. - * - * @author ohun@live.cn - */ -public class PushClientTestMain { - public static void main(String[] args) throws Exception { - Logs.init(); - PushSender sender = PushSender.create(); - sender.start().get(); - for (int i = 0; i < 1; i++) { - PushContent content = PushContent.build(PushType.MESSAGE, "this a first push." + i); - content.setMsgId("msgId_" + (i % 2)); - Thread.sleep(1000); - sender.send(Jsons.toJson(content), Arrays.asList("user-0","doctor43test"), new PushSender.Callback() { - @Override - public void onSuccess(String userId, ClientLocation location) { - System.err.println("push onSuccess userId=" + userId); - } - - @Override - public void onFailure(String userId, ClientLocation location) { - System.err.println("push onFailure userId=" + userId); - } - - @Override - public void onOffline(String userId, ClientLocation location) { - System.err.println("push onOffline userId=" + userId); - } - - @Override - public void onTimeout(String userId, ClientLocation location) { - System.err.println("push onTimeout userId=" + userId); - } - }); - } - LockSupport.park(); - } - -} \ No newline at end of file diff --git a/mpush-test/src/test/java/com/mpush/test/redis/ConsistentHashTest.java b/mpush-test/src/test/java/com/mpush/test/redis/ConsistentHashTest.java deleted file mode 100644 index 69be00b2..00000000 --- a/mpush-test/src/test/java/com/mpush/test/redis/ConsistentHashTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.test.redis; - -import com.mpush.cache.redis.hash.ConsistentHash; -import com.mpush.cache.redis.hash.Node; -import org.junit.Test; -import redis.clients.util.Hashing; -import redis.clients.util.MurmurHash; - -import java.util.*; - -public class ConsistentHashTest { - - private static final String IP_PREFIX = "192.168.1.";// 机器节点IP前缀 - - @Test - public void test() { - Map map = new HashMap();// 每台真实机器节点上保存的记录条数 - List nodes = new ArrayList();// 真实机器节点 - // 10台真实机器节点集群 - for (int i = 1; i <= 10; i++) { - map.put(IP_PREFIX + i, 0);// 每台真实机器节点上保存的记录条数初始为0 - Node node = new Node(IP_PREFIX + i, "node" + i); - nodes.add(node); - } - Hashing hashFunction = new MurmurHash(); // hash函数实例 - ConsistentHash consistentHash = new ConsistentHash(hashFunction, 100, Collections.unmodifiableCollection(nodes));// 每台真实机器引入100个虚拟节点 - // 将5000条记录尽可能均匀的存储到10台机器节点 - for (int i = 0; i < 5000; i++) { - // 产生随机一个字符串当做一条记录,可以是其它更复杂的业务对象,比如随机字符串相当于 - String data = UUID.randomUUID().toString() + i; - // 通过记录找到真实机器节点 - Node node = consistentHash.get(data); - // 再这里可以能过其它工具将记录存储真实机器节点上,比如MemoryCache等 - // 每台真实机器节点上保存的记录条数加1 - map.put(node.getIp(), map.get(node.getIp()) + 1); - } - // 打印每台真实机器节点保存的记录条数 - for (int i = 1; i <= 10; i++) { - System.out.println(IP_PREFIX + i + "节点记录条数:" - + map.get("192.168.1." + i)); - } - - } - -} diff --git a/mpush-test/src/test/resources/application.conf b/mpush-test/src/test/resources/application.conf deleted file mode 100644 index 4c50b7c1..00000000 --- a/mpush-test/src/test/resources/application.conf +++ /dev/null @@ -1,6 +0,0 @@ -mp.log.dir=${user.dir}/mpush-test/target/logs -mp.log.level=debug -mp.zk.namespace=mpush -mp.zk.server-address="111.1.57.148:5666" -mp.net.public-host-mapping={"172.17.42.1":"111.1.57.148"} -mp.core.compress-threshold=10k \ No newline at end of file diff --git a/mpush-test/src/test/resources/logback.xml b/mpush-test/src/test/resources/logback.xml deleted file mode 100644 index 0d67de0e..00000000 --- a/mpush-test/src/test/resources/logback.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - System.out - UTF-8 - - DEBUG - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - System.err - UTF-8 - - debug - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - - - - - diff --git a/mpush-test/src/test/resources/services/com.mpush.api.spi.PusherFactory b/mpush-test/src/test/resources/services/com.mpush.api.spi.PusherFactory deleted file mode 100644 index 93607f48..00000000 --- a/mpush-test/src/test/resources/services/com.mpush.api.spi.PusherFactory +++ /dev/null @@ -1 +0,0 @@ -com.mpush.client.push.PushClientFactory diff --git a/mpush-tools/pom.xml b/mpush-tools/pom.xml index 31ae2c40..52338882 100644 --- a/mpush-tools/pom.xml +++ b/mpush-tools/pom.xml @@ -2,18 +2,21 @@ + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} mpush-tools - ${mpush-tools-version} jar mpush-tools + MPUSH消息推送系统工具库 + https://github.com/mpusher/mpush + @@ -34,29 +37,25 @@ - com.google.code.gson - gson + ${project.groupId} + mpush-api com.google.guava guava - org.apache.commons - commons-lang3 + com.alibaba + fastjson - org.aeonbits.owner - owner + org.apache.commons + commons-lang3 com.typesafe config - - ${mpush.groupId} - mpush-api - org.slf4j slf4j-api @@ -65,6 +64,14 @@ org.slf4j jcl-over-slf4j + + org.slf4j + log4j-over-slf4j + + + org.slf4j + jul-to-slf4j + ch.qos.logback logback-classic @@ -77,5 +84,9 @@ log4j log4j + + org.javassist + javassist + diff --git a/mpush-tools/src/main/java/com/mpush/tools/Jsons.java b/mpush-tools/src/main/java/com/mpush/tools/Jsons.java index b5963e34..f32b3f50 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/Jsons.java +++ b/mpush-tools/src/main/java/com/mpush/tools/Jsons.java @@ -19,30 +19,28 @@ package com.mpush.tools; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; +import com.alibaba.fastjson.JSON; import com.mpush.api.Constants; import com.mpush.tools.common.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Type; -import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Created by xiaoxu.yxx on 15/8/7. + * + * @author ohun@live.cn (夜色) */ public final class Jsons { private static final Logger LOGGER = LoggerFactory.getLogger(Jsons.class); - public static final Gson GSON = new GsonBuilder().create(); public static String toJson(Object bean) { - try { - return GSON.toJson(bean); + return JSON.toJSONString(bean); } catch (Exception e) { LOGGER.error("Jsons.toJson ex, bean=" + bean, e); } @@ -52,7 +50,7 @@ public static String toJson(Object bean) { public static T fromJson(String json, Class clazz) { try { - return GSON.fromJson(json, clazz); + return JSON.parseObject(json, clazz); } catch (Exception e) { LOGGER.error("Jsons.fromJson ex, json=" + json + ", clazz=" + clazz, e); } @@ -63,11 +61,9 @@ public static T fromJson(byte[] json, Class clazz) { return fromJson(new String(json, Constants.UTF_8), clazz); } - public static List fromJsonToList(String json, Class type) { + public static List fromJsonToList(String json, Class type) { try { - T[] list = GSON.fromJson(json, type); - if (list == null) return null; - return Arrays.asList(list); + return JSON.parseArray(json, type); } catch (Exception e) { LOGGER.error("Jsons.fromJsonToList ex, json=" + json + ", type=" + type, e); } @@ -76,7 +72,7 @@ public static List fromJsonToList(String json, Class type) { public static T fromJson(String json, Type type) { try { - return GSON.fromJson(json, type); + return JSON.parseObject(json, type); } catch (Exception e) { LOGGER.error("Jsons.fromJson ex, json=" + json + ", type=" + type, e); } diff --git a/mpush-tools/src/main/java/com/mpush/tools/Utils.java b/mpush-tools/src/main/java/com/mpush/tools/Utils.java index 6a041dfd..23978eed 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/Utils.java +++ b/mpush-tools/src/main/java/com/mpush/tools/Utils.java @@ -19,18 +19,20 @@ package com.mpush.tools; -import com.mpush.tools.common.Profiler; +import com.mpush.tools.thread.NamedThreadFactory; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.SingleThreadEventLoop; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.ThreadProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NetworkInterface; -import java.net.Socket; +import java.net.*; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ThreadPoolExecutor; import java.util.regex.Pattern; /** @@ -47,6 +49,12 @@ public final class Utils { private static String EXTRANET_IP; + private static final NamedThreadFactory NAMED_THREAD_FACTORY = new NamedThreadFactory(); + + public static Thread newThread(String name, Runnable target) { + return NAMED_THREAD_FACTORY.newThread(name, target); + } + public static boolean isLocalHost(String host) { return host == null || host.length() == 0 @@ -55,71 +63,82 @@ public static boolean isLocalHost(String host) { || (LOCAL_IP_PATTERN.matcher(host).matches()); } - public static String getLocalIp() { + public static String lookupLocalIp() { if (LOCAL_IP == null) { - LOCAL_IP = getInetAddress(); + LOCAL_IP = getInetAddress(true); } return LOCAL_IP; } + public static NetworkInterface getLocalNetworkInterface() { + Enumeration interfaces; + try { + interfaces = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + throw new RuntimeException("NetworkInterface not found", e); + } + while (interfaces.hasMoreElements()) { + NetworkInterface networkInterface = interfaces.nextElement(); + Enumeration addresses = networkInterface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + if (address.isLoopbackAddress()) continue; + if (address.getHostAddress().contains(":")) continue; + if (address.isSiteLocalAddress()) return networkInterface; + } + } + throw new RuntimeException("NetworkInterface not found"); + } + + public static InetAddress getInetAddress(String host) { + try { + return InetAddress.getByName(host); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("UnknownHost " + host, e); + } + } + /** - * 获取本机ip * 只获取第一块网卡绑定的ip地址 * - * @return + * @param getLocal 局域网IP + * @return ip */ - public static String getInetAddress() { + public static String getInetAddress(boolean getLocal) { try { - Profiler.enter("start get inet addresss"); Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); - InetAddress address = null; while (interfaces.hasMoreElements()) { - NetworkInterface ni = interfaces.nextElement(); - Enumeration addresses = ni.getInetAddresses(); + Enumeration addresses = interfaces.nextElement().getInetAddresses(); while (addresses.hasMoreElements()) { - address = addresses.nextElement(); - if (!address.isLoopbackAddress() && address.getHostAddress().indexOf(":") == -1 && address.isSiteLocalAddress()) { - return address.getHostAddress(); + InetAddress address = addresses.nextElement(); + if (address.isLoopbackAddress()) continue; + if (address.getHostAddress().contains(":")) continue; + if (getLocal) { + if (address.isSiteLocalAddress()) { + return address.getHostAddress(); + } + } else { + if (!address.isSiteLocalAddress() && !address.isLoopbackAddress()) { + return address.getHostAddress(); + } } } } - LOGGER.warn("getInetAddress is null"); - return "127.0.0.1"; + LOGGER.debug("getInetAddress is null, getLocal={}", getLocal); + return getLocal ? "127.0.0.1" : null; } catch (Throwable e) { LOGGER.error("getInetAddress exception", e); - return "127.0.0.1"; - } finally { - Profiler.release(); + return getLocal ? "127.0.0.1" : null; } } - public static String getExtranetIp() { + public static String lookupExtranetIp() { if (EXTRANET_IP == null) { - EXTRANET_IP = getExtranetAddress(); + EXTRANET_IP = getInetAddress(false); } return EXTRANET_IP; } - public static String getExtranetAddress() { - try { - Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); - InetAddress address = null; - while (interfaces.hasMoreElements()) { - NetworkInterface ni = interfaces.nextElement(); - Enumeration addresses = ni.getInetAddresses(); - while (addresses.hasMoreElements()) { - address = addresses.nextElement(); - if (!address.isLoopbackAddress() && address.getHostAddress().indexOf(":") == -1 && !address.isSiteLocalAddress()) { - return address.getHostAddress(); - } - } - } - LOGGER.warn("getExtranetAddress is null"); - } catch (Throwable e) { - LOGGER.error("getExtranetAddress exception", e); - } - return null; - } public static String headerToString(Map headers) { if (headers != null && headers.size() > 0) { @@ -167,4 +186,34 @@ public static boolean checkHealth(String ip, int port) { return false; } } + + public static Map getPoolInfo(ThreadPoolExecutor executor) { + Map info = new HashMap<>(5); + info.put("corePoolSize", executor.getCorePoolSize()); + info.put("maxPoolSize", executor.getMaximumPoolSize()); + info.put("activeCount(workingThread)", executor.getActiveCount()); + info.put("poolSize(workThread)", executor.getPoolSize()); + info.put("queueSize(blockedTask)", executor.getQueue().size()); + return info; + } + + public static Map getPoolInfo(EventLoopGroup executors) { + Map info = new HashMap<>(3); + int poolSize = 0, queueSize = 0, activeCount = 0; + for (EventExecutor e : executors) { + poolSize++; + if (e instanceof SingleThreadEventLoop) { + SingleThreadEventLoop executor = (SingleThreadEventLoop) e; + queueSize += executor.pendingTasks(); + ThreadProperties tp = executor.threadProperties(); + if (tp.state() == Thread.State.RUNNABLE) { + activeCount++; + } + } + } + info.put("poolSize(workThread)", poolSize); + info.put("activeCount(workingThread)", activeCount); + info.put("queueSize(blockedTask)", queueSize); + return info; + } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/DefaultJsonFactory.java b/mpush-tools/src/main/java/com/mpush/tools/common/DefaultJsonFactory.java new file mode 100644 index 00000000..3d5935a3 --- /dev/null +++ b/mpush-tools/src/main/java/com/mpush/tools/common/DefaultJsonFactory.java @@ -0,0 +1,24 @@ +package com.mpush.tools.common; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.Json; +import com.mpush.api.spi.common.JsonFactory; +import com.mpush.tools.Jsons; + +@Spi +public final class DefaultJsonFactory implements JsonFactory, Json { + @Override + public T fromJson(String json, Class clazz) { + return Jsons.fromJson(json, clazz); + } + + @Override + public String toJson(Object json) { + return Jsons.toJson(json); + } + + @Override + public Json get() { + return this; + } +} \ No newline at end of file diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/GenericsUtil.java b/mpush-tools/src/main/java/com/mpush/tools/common/GenericsUtil.java deleted file mode 100644 index f8db214d..00000000 --- a/mpush-tools/src/main/java/com/mpush/tools/common/GenericsUtil.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.tools.common; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; - - -public class GenericsUtil { - - /** - * 通过反射,获得指定类的父类的泛型参数的实际类型. 如BuyerServiceBean extends DaoSupport - * - * @param clazz clazz 需要反射的类,该类必须继承范型父类 - * @param index 泛型参数所在索引,从0开始. - * @return 范型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回Object.class - */ - public static Class getSuperClassGenericType(Class clazz, int index) { - Type genType = clazz.getGenericSuperclass();//得到泛型父类 - //如果没有实现ParameterizedType接口,即不支持泛型,直接返回Object.class - - if (!(genType instanceof ParameterizedType)) { - return Object.class; - } - //返回表示此类型实际类型参数的Type对象的数组,数组里放的都是对应类型的Class, 如BuyerServiceBean extends DaoSupport就返回Buyer和Contact类型 - Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); - if (index >= params.length || index < 0) { - throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数")); - } - if (!(params[index] instanceof Class)) { - return Object.class; - } - return (Class) params[index]; - } - - /** - * 通过反射,获得指定类的父类的第一个泛型参数的实际类型. 如BuyerServiceBean extends DaoSupport - * - * @param clazz clazz 需要反射的类,该类必须继承泛型父类 - * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回Object.class - */ - public static Class getSuperClassGenericType(Class clazz) { - return getSuperClassGenericType(clazz, 0); - } - - /** - * 通过反射,获得方法返回值泛型参数的实际类型. 如: public Map getNames(){} - * - * @param method 方法 - * @param index 泛型参数所在索引,从0开始. - * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回Object.class - */ - public static Class getMethodGenericReturnType(Method method, int index) { - Type returnType = method.getGenericReturnType(); - if (returnType instanceof ParameterizedType) { - ParameterizedType type = (ParameterizedType) returnType; - Type[] typeArguments = type.getActualTypeArguments(); - if (index >= typeArguments.length || index < 0) { - throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数")); - } - return (Class) typeArguments[index]; - } - return Object.class; - } - - /** - * 通过反射,获得方法返回值第一个泛型参数的实际类型. 如: public Map getNames(){} - * - * @param method 方法 - * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回Object.class - */ - public static Class getMethodGenericReturnType(Method method) { - return getMethodGenericReturnType(method, 0); - } - - /** - * 通过反射,获得方法输入参数第index个输入参数的所有泛型参数的实际类型. 如: public void add(Map maps, List names){} - * - * @param method 方法 - * @param index 第几个输入参数 - * @return 输入参数的泛型参数的实际类型集合, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回空集合 - */ - public static List> getMethodGenericParameterTypes(Method method, int index) { - List> results = new ArrayList>(); - Type[] genericParameterTypes = method.getGenericParameterTypes(); - if (index >= genericParameterTypes.length || index < 0) { - throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数")); - } - Type genericParameterType = genericParameterTypes[index]; - if (genericParameterType instanceof ParameterizedType) { - ParameterizedType aType = (ParameterizedType) genericParameterType; - Type[] parameterArgTypes = aType.getActualTypeArguments(); - for (Type parameterArgType : parameterArgTypes) { - Class parameterArgClass = (Class) parameterArgType; - results.add(parameterArgClass); - } - return results; - } - return results; - } - - /** - * 通过反射,获得方法输入参数第一个输入参数的所有泛型参数的实际类型. 如: public void add(Map maps, List names){} - * - * @param method 方法 - * @return 输入参数的泛型参数的实际类型集合, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回空集合 - */ - public static List> getMethodGenericParameterTypes(Method method) { - return getMethodGenericParameterTypes(method, 0); - } - - /** - * 通过反射,获得Field泛型参数的实际类型. 如: public Map names; - * - * @param field 字段 - * @param index 泛型参数所在索引,从0开始. - * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回Object.class - */ - public static Class getFieldGenericType(Field field, int index) { - Type genericFieldType = field.getGenericType(); - - if (genericFieldType instanceof ParameterizedType) { - ParameterizedType aType = (ParameterizedType) genericFieldType; - Type[] fieldArgTypes = aType.getActualTypeArguments(); - if (index >= fieldArgTypes.length || index < 0) { - throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数")); - } - return (Class) fieldArgTypes[index]; - } - return Object.class; - } - - /** - * 通过反射,获得Field泛型参数的实际类型. 如: public Map names; - * - * @param field 字段 - * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回Object.class - */ - public static Class getFieldGenericType(Field field) { - return getFieldGenericType(field, 0); - } - -} diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/Holder.java b/mpush-tools/src/main/java/com/mpush/tools/common/Holder.java new file mode 100644 index 00000000..f271d2e3 --- /dev/null +++ b/mpush-tools/src/main/java/com/mpush/tools/common/Holder.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.tools.common; + +/** + * Created by ohun on 16/10/22. + * + * @author ohun@live.cn (夜色) + */ +public final class Holder { + private T t; + + public Holder() { + } + + public Holder(T t) { + this.t = t; + } + + public static Holder of(T t) { + return new Holder<>(t); + } + + public T get() { + return t; + } + + public void set(T t) { + this.t = t; + } +} diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/IOUtils.java b/mpush-tools/src/main/java/com/mpush/tools/common/IOUtils.java index da840eec..34a4533b 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/common/IOUtils.java +++ b/mpush-tools/src/main/java/com/mpush/tools/common/IOUtils.java @@ -50,7 +50,7 @@ public static void close(Closeable closeable) { public static byte[] compress(byte[] data) { - Profiler.enter("start compress"); + Profiler.enter("time cost on [compress]"); ByteArrayOutputStream out = new ByteArrayOutputStream(data.length / 4); DeflaterOutputStream zipOut = new DeflaterOutputStream(out); @@ -69,7 +69,7 @@ public static byte[] compress(byte[] data) { } public static byte[] decompress(byte[] data) { - Profiler.enter("start decompress"); + Profiler.enter("time cost on [decompress]"); InflaterInputStream zipIn = new InflaterInputStream(new ByteArrayInputStream(data)); ByteArrayOutputStream out = new ByteArrayOutputStream(data.length * 4); byte[] buffer = new byte[1024]; diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/JVMUtil.java b/mpush-tools/src/main/java/com/mpush/tools/common/JVMUtil.java index 34159ba4..82ffb5e6 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/common/JVMUtil.java +++ b/mpush-tools/src/main/java/com/mpush/tools/common/JVMUtil.java @@ -19,13 +19,17 @@ package com.mpush.tools.common; +import com.mpush.tools.Utils; import com.sun.management.HotSpotDiagnosticMXBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.MBeanServer; import javax.management.ObjectName; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; import java.lang.management.*; import java.security.AccessController; import java.security.PrivilegedExceptionAction; @@ -107,23 +111,14 @@ public static void jstack(OutputStream stream) throws Exception { } public static void dumpJstack(final String jvmPath) { - new Thread((new Runnable() { - @Override - public void run() { - String logPath = jvmPath; - FileOutputStream jstackStream = null; - try { - jstackStream = new FileOutputStream(new File(logPath, System.currentTimeMillis() + "-jstack.LOGGER")); - JVMUtil.jstack(jstackStream); + Utils.newThread("dump-jstack-t", (() -> { + File path = new File(jvmPath); + if (path.exists() || path.mkdirs()) { + File file = new File(path, System.currentTimeMillis() + "-jstack.log"); + try (FileOutputStream out = new FileOutputStream(file)) { + JVMUtil.jstack(out); } catch (Throwable t) { LOGGER.error("Dump JVM cache Error!", t); - } finally { - if (jstackStream != null) { - try { - jstackStream.close(); - } catch (IOException e) { - } - } } } })).start(); @@ -163,7 +158,7 @@ private static void initHotSpotMBean() throws Exception { } public static void jMap(String fileName, boolean live) { - File f = new File(fileName, System.currentTimeMillis() + "-jmap.LOGGER"); + File f = new File(fileName, System.currentTimeMillis() + "-jmap.log"); String currentFileName = f.getPath(); try { initHotSpotMBean(); @@ -177,11 +172,6 @@ public static void jMap(String fileName, boolean live) { } public static void dumpJmap(final String jvmPath) { - new Thread(new Runnable() { - @Override - public void run() { - jMap(jvmPath, false); - } - }).start(); + Utils.newThread("dump-jmap-t", () -> jMap(jvmPath, false)).start(); } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/Profiler.java b/mpush-tools/src/main/java/com/mpush/tools/common/Profiler.java index 0d8f29c2..66d95da5 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/common/Profiler.java +++ b/mpush-tools/src/main/java/com/mpush/tools/common/Profiler.java @@ -20,6 +20,7 @@ package com.mpush.tools.common; +import com.mpush.tools.config.CC; import org.apache.commons.lang3.StringUtils; import java.text.MessageFormat; @@ -32,15 +33,21 @@ */ @SuppressWarnings(value = {"rawtypes", "unchecked"}) public class Profiler { + private static volatile boolean enabled = CC.mp.monitor.profile_enabled; private static final ThreadLocal entryStack = new ThreadLocal(); public static final String EMPTY_STRING = ""; + public static void enable(boolean enabled) { + Profiler.enabled = enabled; + reset(); + } + /** * 开始计时。 */ public static void start() { - start((String) null); + start(EMPTY_STRING); } /** @@ -48,9 +55,8 @@ public static void start() { * * @param message 第一个entry的信息 */ - - public static void start(String message) { - entryStack.set(new Entry(message, null, null)); + public static void start(String message, Object... args) { + if (enabled) entryStack.set(new Entry(String.format(message, args), null, null)); } /** @@ -59,18 +65,17 @@ public static void start(String message) { * @param message 第一个entry的信息 */ public static void start(Message message) { - entryStack.set(new Entry(message, null, null)); + if (enabled) entryStack.set(new Entry(message, null, null)); } /** * 清除计时器。 - *

*

* 清除以后必须再次调用start方可重新计时。 *

*/ public static void reset() { - entryStack.set(null); + entryStack.remove(); } /** @@ -79,10 +84,12 @@ public static void reset() { * @param message 新entry的信息 */ public static void enter(String message) { - Entry currentEntry = getCurrentEntry(); + if (enabled) { + Entry currentEntry = getCurrentEntry(); - if (currentEntry != null) { - currentEntry.enterSubEntry(message); + if (currentEntry != null) { + currentEntry.enterSubEntry(message); + } } } @@ -92,10 +99,12 @@ public static void enter(String message) { * @param message 新entry的信息 */ public static void enter(Message message) { - Entry currentEntry = getCurrentEntry(); + if (enabled) { + Entry currentEntry = getCurrentEntry(); - if (currentEntry != null) { - currentEntry.enterSubEntry(message); + if (currentEntry != null) { + currentEntry.enterSubEntry(message); + } } } @@ -103,10 +112,12 @@ public static void enter(Message message) { * 结束最近的一个entry,记录结束时间。 */ public static void release() { - Entry currentEntry = getCurrentEntry(); + if (enabled) { + Entry currentEntry = getCurrentEntry(); - if (currentEntry != null) { - currentEntry.release(); + if (currentEntry != null) { + currentEntry.release(); + } } } @@ -116,13 +127,16 @@ public static void release() { * @return 耗费的总时间,如果未开始计时,则返回-1 */ public static long getDuration() { - Entry entry = (Entry) entryStack.get(); + if (enabled) { + Entry entry = (Entry) entryStack.get(); - if (entry != null) { - return entry.getDuration(); - } else { - return -1; + if (entry != null) { + return entry.getDuration(); + } else { + return -1; + } } + return -1; } /** @@ -217,9 +231,6 @@ private Entry(Object message, Entry parentEntry, Entry firstEntry) { : firstEntry.startTime; } - /** - * 取得entry的信息。 - */ public String getMessage() { String messageString = null; @@ -418,7 +429,7 @@ public String toString() { * @return 字符串表示的entry */ private String toString(String prefix1, String prefix2) { - StringBuffer buffer = new StringBuffer(); + StringBuilder buffer = new StringBuilder(); toString(buffer, prefix1, prefix2); @@ -432,7 +443,7 @@ private String toString(String prefix1, String prefix2) { * @param prefix1 首行前缀 * @param prefix2 后续行前缀 */ - private void toString(StringBuffer buffer, String prefix1, String prefix2) { + private void toString(StringBuilder buffer, String prefix1, String prefix2) { buffer.append(prefix1); String message = getMessage(); diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/Reflects.java b/mpush-tools/src/main/java/com/mpush/tools/common/Reflects.java new file mode 100644 index 00000000..08d70570 --- /dev/null +++ b/mpush-tools/src/main/java/com/mpush/tools/common/Reflects.java @@ -0,0 +1,45 @@ +package com.mpush.tools.common; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Reflects { + + public static Class getSuperClassGenericType(final Class clazz, int index) { + return getGenericType(clazz.getGenericSuperclass(), index); + } + + public static Class getFieldGenericType(final Field field, final int index) { + return getGenericType(field.getGenericType(), index); + } + + public static List getMethodGenericTypes(final Method method, final int paramIndex) { + return getGenericTypes(method.getGenericParameterTypes()[paramIndex]); + } + + + public static Class getGenericType(Type genType, int index) { + List params = getGenericTypes(genType); + if (index >= params.size() || index < 0) return null; + return params.get(index); + } + + public static List getGenericTypes(Type genType) { + if (!(genType instanceof ParameterizedType)) return Collections.emptyList(); + Type[] types = ((ParameterizedType) genType).getActualTypeArguments(); + List list = new ArrayList(types.length); + for (Type type : types) { + if (type instanceof Class) list.add((Class) type); + else if (type instanceof ParameterizedType) { + Type type1 = ((ParameterizedType) type).getRawType(); + if (type1 instanceof Class) list.add((Class) type1); + } + } + return list; + } +} \ No newline at end of file diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/RollingNumber.java b/mpush-tools/src/main/java/com/mpush/tools/common/RollingNumber.java new file mode 100644 index 00000000..a650cd97 --- /dev/null +++ b/mpush-tools/src/main/java/com/mpush/tools/common/RollingNumber.java @@ -0,0 +1,624 @@ +/** + * Copyright 2012 Netflix, Inc. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mpush.tools.common; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.atomic.LongAdder; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * A number which can be used to track counters (increment) or set values over time. + *

+ * It is "rolling" in the sense that a 'timeInMilliseconds' is given that you want to track (such as 10 seconds) and then that is broken into buckets (defaults to 10) so that the 10 second window + * doesn't empty out and restart every 10 seconds, but instead every 1 second you have a new bucket added and one dropped so that 9 of the buckets remain and only the newest starts from scratch. + *

+ * This is done so that the statistics are gathered over a rolling 10 second window with data being added/dropped in 1 second intervals (or whatever granularity is defined by the arguments) rather + * than each 10 second window starting at 0 again. + *

+ * Performance-wise this class is optimized for writes, not reads. This is done because it expects far higher write volume (thousands/second) than reads (a few per second). + *

+ * For example, on each read to getSum/getCount it will iterate buckets to sum the data so that on writes we don't need to maintain the overall sum and pay the synchronization cost at each write to + * ensure the sum is up-to-date when the read can easily iterate each bucket to get the sum when it needs it. + *

+ * See UnitTest for usage and expected behavior examples. + * + */ +public class RollingNumber { + + private static final Time ACTUAL_TIME = new ActualTime(); + private final Time time; + final int timeInMilliseconds; + final int numberOfBuckets; + final int bucketSizeInMillseconds; + + final BucketCircularArray buckets; + private final CumulativeSum cumulativeSum = new CumulativeSum(); + + public RollingNumber(int timeInMilliseconds, int numberOfBuckets) { + this(ACTUAL_TIME, timeInMilliseconds, numberOfBuckets); + } + + /* package for testing */ RollingNumber(Time time, int timeInMilliseconds, int numberOfBuckets) { + this.time = time; + this.timeInMilliseconds = timeInMilliseconds; + this.numberOfBuckets = numberOfBuckets; + + if (timeInMilliseconds % numberOfBuckets != 0) { + throw new IllegalArgumentException("The timeInMilliseconds must divide equally into numberOfBuckets. For example 1000/10 is ok, 1000/11 is not."); + } + this.bucketSizeInMillseconds = timeInMilliseconds / numberOfBuckets; + + buckets = new BucketCircularArray(numberOfBuckets); + } + + /** + * Increment the counter in the current bucket by one for the given {@link Event} type. + *

+ * The {@link Event} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to increment + */ + public void increment(Event type) { + getCurrentBucket().getAdder(type).increment(); + } + + /** + * Add to the counter in the current bucket for the given {@link Event} type. + *

+ * The {@link Event} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to add to + * @param value + * long value to be added to the current bucket + */ + public void add(Event type, long value) { + getCurrentBucket().getAdder(type).add(value); + } + + + /** + * Force a reset of all rolling counters (clear all buckets) so that statistics start being gathered from scratch. + *

+ * This does NOT reset the CumulativeSum values. + */ + public void reset() { + // if we are resetting, that means the lastBucket won't have a chance to be captured in CumulativeSum, so let's do it here + Bucket lastBucket = buckets.peekLast(); + if (lastBucket != null) { + cumulativeSum.addBucket(lastBucket); + } + + // clear buckets so we start over again + buckets.clear(); + } + + /** + * Get the cumulative sum of all buckets ever since the JVM started without rolling for the given {@link Event} type. + *

+ * See {@link #getRollingSum(Event)} for the rolling sum. + *

+ * The {@link Event} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type HystrixRollingNumberEvent defining which counter to retrieve values from + * @return cumulative sum of all increments and adds for the given {@link Event} counter type + */ + public long getCumulativeSum(Event type) { + // this isn't 100% atomic since multiple threads can be affecting latestBucket & cumulativeSum independently + // but that's okay since the count is always a moving target and we're accepting a "point in time" best attempt + // we are however putting 'getValueOfLatestBucket' first since it can have side-affects on cumulativeSum whereas the inverse is not true + return getValueOfLatestBucket(type) + cumulativeSum.get(type); + } + + /** + * Get the sum of all buckets in the rolling counter for the given {@link Event} type. + *

+ * The {@link Event} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to retrieve values from + * @return + * value from the given {@link Event} counter type + */ + public long getRollingSum(Event type) { + Bucket lastBucket = getCurrentBucket(); + if (lastBucket == null) + return 0; + + long sum = 0; + for (Bucket b : buckets) { + sum += b.getAdder(type).sum(); + } + return sum; + } + + /** + * Get the value of the latest (current) bucket in the rolling counter for the given {@link Event} type. + *

+ * The {@link Event} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to retrieve value from + * @return + * value from latest bucket for given {@link Event} counter type + */ + public long getValueOfLatestBucket(Event type) { + Bucket lastBucket = getCurrentBucket(); + if (lastBucket == null) + return 0; + // we have bucket data so we'll return the lastBucket + return lastBucket.get(type); + } + + /** + * Get an array of values for all buckets in the rolling counter for the given {@link Event} type. + *

+ * Index 0 is the oldest bucket. + *

+ * The {@link Event} must be a "counter" type HystrixRollingNumberEvent.isCounter() == true. + * + * @param type + * HystrixRollingNumberEvent defining which counter to retrieve values from + * @return array of values from each of the rolling buckets for given {@link Event} counter type + */ + public long[] getValues(Event type) { + Bucket lastBucket = getCurrentBucket(); + if (lastBucket == null) + return new long[0]; + + // get buckets as an array (which is a copy of the current state at this point in time) + Bucket[] bucketArray = buckets.getArray(); + + // we have bucket data so we'll return an array of values for all buckets + long values[] = new long[bucketArray.length]; + int i = 0; + for (Bucket bucket : bucketArray) { + if (type.isCounter()) { + values[i++] = bucket.getAdder(type).sum(); + } + } + return values; + } + + /** + * Get the max value of values in all buckets for the given {@link Event} type. + *

+ * The {@link Event} must be a "max updater" type HystrixRollingNumberEvent.isMaxUpdater() == true. + * + * @param type + * HystrixRollingNumberEvent defining which "max updater" to retrieve values from + * @return max value for given {@link Event} type during rolling window + */ + public long getRollingMaxValue(Event type) { + long values[] = getValues(type); + if (values.length == 0) { + return 0; + } else { + Arrays.sort(values); + return values[values.length - 1]; + } + } + + private ReentrantLock newBucketLock = new ReentrantLock(); + + /* package for testing */Bucket getCurrentBucket() { + long currentTime = time.getCurrentTimeInMillis(); + + /* a shortcut to try and get the most common result of immediately finding the current bucket */ + + /** + * Retrieve the latest bucket if the given time is BEFORE the end of the bucket window, otherwise it returns NULL. + * + * NOTE: This is thread-safe because it's accessing 'buckets' which is a LinkedBlockingDeque + */ + Bucket currentBucket = buckets.peekLast(); + if (currentBucket != null && currentTime < currentBucket.windowStart + this.bucketSizeInMillseconds) { + // if we're within the bucket 'window of time' return the current one + // NOTE: We do not worry if we are BEFORE the window in a weird case of where thread scheduling causes that to occur, + // we'll just use the latest as long as we're not AFTER the window + return currentBucket; + } + + /* if we didn't find the current bucket above, then we have to create one */ + + /** + * The following needs to be synchronized/locked even with a synchronized/thread-safe data structure such as LinkedBlockingDeque because + * the logic involves multiple steps to check existence, create an object then insert the object. The 'check' or 'insertion' themselves + * are thread-safe by themselves but not the aggregate algorithm, thus we put this entire block of logic inside synchronized. + * + * I am using a tryLock if/then (http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/Lock.html#tryLock()) + * so that a single thread will get the lock and as soon as one thread gets the lock all others will go the 'else' block + * and just return the currentBucket until the newBucket is created. This should allow the throughput to be far higher + * and only slow down 1 thread instead of blocking all of them in each cycle of creating a new bucket based on some testing + * (and it makes sense that it should as well). + * + * This means the timing won't be exact to the millisecond as to what data ends up in a bucket, but that's acceptable. + * It's not critical to have exact precision to the millisecond, as long as it's rolling, if we can instead reduce the impact synchronization. + * + * More importantly though it means that the 'if' block within the lock needs to be careful about what it changes that can still + * be accessed concurrently in the 'else' block since we're not completely synchronizing access. + * + * For example, we can't have a multi-step process to add a bucket, remove a bucket, then update the sum since the 'else' block of code + * can retrieve the sum while this is all happening. The trade-off is that we don't maintain the rolling sum and let readers just iterate + * bucket to calculate the sum themselves. This is an example of favoring write-performance instead of read-performance and how the tryLock + * versus a synchronized block needs to be accommodated. + */ + if (newBucketLock.tryLock()) { + try { + if (buckets.peekLast() == null) { + // the list is empty so create the first bucket + Bucket newBucket = new Bucket(currentTime); + buckets.addLast(newBucket); + return newBucket; + } else { + // We go into a loop so that it will create as many buckets as needed to catch up to the current time + // as we want the buckets complete even if we don't have transactions during a period of time. + for (int i = 0; i < numberOfBuckets; i++) { + // we have at least 1 bucket so retrieve it + Bucket lastBucket = buckets.peekLast(); + if (currentTime < lastBucket.windowStart + this.bucketSizeInMillseconds) { + // if we're within the bucket 'window of time' return the current one + // NOTE: We do not worry if we are BEFORE the window in a weird case of where thread scheduling causes that to occur, + // we'll just use the latest as long as we're not AFTER the window + return lastBucket; + } else if (currentTime - (lastBucket.windowStart + this.bucketSizeInMillseconds) > timeInMilliseconds) { + // the time passed is greater than the entire rolling counter so we want to clear it all and start from scratch + reset(); + // recursively call getCurrentBucket which will create a new bucket and return it + return getCurrentBucket(); + } else { // we're past the window so we need to create a new bucket + // create a new bucket and add it as the new 'last' + buckets.addLast(new Bucket(lastBucket.windowStart + this.bucketSizeInMillseconds)); + // add the lastBucket values to the cumulativeSum + cumulativeSum.addBucket(lastBucket); + } + } + // we have finished the for-loop and created all of the buckets, so return the lastBucket now + return buckets.peekLast(); + } + } finally { + newBucketLock.unlock(); + } + } else { + currentBucket = buckets.peekLast(); + if (currentBucket != null) { + // we didn't get the lock so just return the latest bucket while another thread creates the next one + return currentBucket; + } else { + // the rare scenario where multiple threads raced to create the very first bucket + // wait slightly and then use recursion while the other thread finishes creating a bucket + try { + Thread.sleep(5); + } catch (Exception e) { + // ignore + } + return getCurrentBucket(); + } + } + } + + /* package */static interface Time { + public long getCurrentTimeInMillis(); + } + + private static class ActualTime implements Time { + + @Override + public long getCurrentTimeInMillis() { + return System.currentTimeMillis(); + } + + } + + /** + * Counters for a given 'bucket' of time. + */ + /* package */static class Bucket { + final long windowStart; + final LongAdder[] adderForCounterType; + + Bucket(long startTime) { + this.windowStart = startTime; + + /* + * We support both LongAdder and LongMaxUpdater in a bucket but don't want the memory allocation + * of all types for each so we only allocate the objects if the HystrixRollingNumberEvent matches + * the correct type - though we still have the allocation of empty arrays to the given length + * as we want to keep using the type.ordinal() value for fast random access. + */ + + // initialize the array of LongAdders + adderForCounterType = new LongAdder[Event.values().length]; + for (Event type : Event.values()) { + if (type.isCounter()) { + adderForCounterType[type.ordinal()] = new LongAdder(); + } + } + } + + long get(Event type) { + if (type.isCounter()) { + return adderForCounterType[type.ordinal()].sum(); + } + throw new IllegalStateException("Unknown type of event: " + type.name()); + } + + LongAdder getAdder(Event type) { + if (!type.isCounter()) { + throw new IllegalStateException("Type is not a Counter: " + type.name()); + } + return adderForCounterType[type.ordinal()]; + } + + } + + /** + * Cumulative counters (from start of JVM) from each Type + */ + /* package */static class CumulativeSum { + final LongAdder[] adderForCounterType; + + CumulativeSum() { + + /* + * We support both LongAdder and LongMaxUpdater in a bucket but don't want the memory allocation + * of all types for each so we only allocate the objects if the HystrixRollingNumberEvent matches + * the correct type - though we still have the allocation of empty arrays to the given length + * as we want to keep using the type.ordinal() value for fast random access. + */ + + // initialize the array of LongAdders + adderForCounterType = new LongAdder[Event.values().length]; + for (Event type : Event.values()) { + if (type.isCounter()) { + adderForCounterType[type.ordinal()] = new LongAdder(); + } + } + + } + + public void addBucket(Bucket lastBucket) { + for (Event type : Event.values()) { + if (type.isCounter()) { + getAdder(type).add(lastBucket.getAdder(type).sum()); + } + } + } + + long get(Event type) { + if (type.isCounter()) { + return adderForCounterType[type.ordinal()].sum(); + } + throw new IllegalStateException("Unknown type of event: " + type.name()); + } + + LongAdder getAdder(Event type) { + if (!type.isCounter()) { + throw new IllegalStateException("Type is not a Counter: " + type.name()); + } + return adderForCounterType[type.ordinal()]; + } + } + + /** + * This is a circular array acting as a FIFO queue. + *

+ * It purposefully does NOT implement Deque or some other Collection interface as it only implements functionality necessary for this RollingNumber use case. + *

+ * Important Thread-Safety Note: This is ONLY thread-safe within the context of RollingNumber and the protection it gives in the getCurrentBucket method. It uses AtomicReference + * objects to ensure anything done outside of getCurrentBucket is thread-safe, and to ensure visibility of changes across threads (ie. volatility) but the addLast and removeFirst + * methods are NOT thread-safe for external access they depend upon the lock.tryLock() protection in getCurrentBucket which ensures only a single thread will access them at at time. + *

+ * benjchristensen => This implementation was chosen based on performance testing I did and documented at: http://benjchristensen.com/2011/10/08/atomiccirculararray/ + */ + /* package */static class BucketCircularArray implements Iterable { + private final AtomicReference state; + private final int dataLength; // we don't resize, we always stay the same, so remember this + private final int numBuckets; + + /** + * Immutable object that is atomically set every time the state of the BucketCircularArray changes + *

+ * This handles the compound operations + */ + private class ListState { + /* + * this is an AtomicReferenceArray and not a normal Array because we're copying the reference + * between ListState objects and multiple threads could maintain references across these + * compound operations so I want the visibility/concurrency guarantees + */ + private final AtomicReferenceArray data; + private final int size; + private final int tail; + private final int head; + + private ListState(AtomicReferenceArray data, int head, int tail) { + this.head = head; + this.tail = tail; + if (head == 0 && tail == 0) { + size = 0; + } else { + this.size = (tail + dataLength - head) % dataLength; + } + this.data = data; + } + + public Bucket tail() { + if (size == 0) { + return null; + } else { + // we want to get the last item, so size()-1 + return data.get(convert(size - 1)); + } + } + + private Bucket[] getArray() { + /* + * this isn't technically thread-safe since it requires multiple reads on something that can change + * but since we never clear the data directly, only increment/decrement head/tail we would never get a NULL + * just potentially return stale data which we are okay with doing + */ + ArrayList array = new ArrayList(); + for (int i = 0; i < size; i++) { + array.add(data.get(convert(i))); + } + return array.toArray(new Bucket[array.size()]); + } + + private ListState incrementTail() { + /* if incrementing results in growing larger than 'length' which is the max we should be at, then also increment head (equivalent of removeFirst but done atomically) */ + if (size == numBuckets) { + // increment tail and head + return new ListState(data, (head + 1) % dataLength, (tail + 1) % dataLength); + } else { + // increment only tail + return new ListState(data, head, (tail + 1) % dataLength); + } + } + + public ListState clear() { + return new ListState(new AtomicReferenceArray(dataLength), 0, 0); + } + + public ListState addBucket(Bucket b) { + /* + * We could in theory have 2 threads addBucket concurrently and this compound operation would interleave. + *

+ * This should NOT happen since getCurrentBucket is supposed to be executed by a single thread. + *

+ * If it does happen, it's not a huge deal as incrementTail() will be protected by compareAndSet and one of the two addBucket calls will succeed with one of the Buckets. + *

+ * In either case, a single Bucket will be returned as "last" and data loss should not occur and everything keeps in sync for head/tail. + *

+ * Also, it's fine to set it before incrementTail because nothing else should be referencing that index position until incrementTail occurs. + */ + data.set(tail, b); + return incrementTail(); + } + + // The convert() method takes a logical index (as if head was + // always 0) and calculates the index within elementData + private int convert(int index) { + return (index + head) % dataLength; + } + } + + BucketCircularArray(int size) { + AtomicReferenceArray _buckets = new AtomicReferenceArray(size + 1); // + 1 as extra room for the add/remove; + state = new AtomicReference(new ListState(_buckets, 0, 0)); + dataLength = _buckets.length(); + numBuckets = size; + } + + public void clear() { + while (true) { + /* + * it should be very hard to not succeed the first pass thru since this is typically is only called from + * a single thread protected by a tryLock, but there is at least 1 other place (at time of writing this comment) + * where reset can be called from (CircuitBreaker.markSuccess after circuit was tripped) so it can + * in an edge-case conflict. + * + * Instead of trying to determine if someone already successfully called clear() and we should skip + * we will have both calls reset the circuit, even if that means losing data added in between the two + * depending on thread scheduling. + * + * The rare scenario in which that would occur, we'll accept the possible data loss while clearing it + * since the code has stated its desire to clear() anyways. + */ + ListState current = state.get(); + ListState newState = current.clear(); + if (state.compareAndSet(current, newState)) { + return; + } + } + } + + /** + * Returns an iterator on a copy of the internal array so that the iterator won't fail by buckets being added/removed concurrently. + */ + public Iterator iterator() { + return Collections.unmodifiableList(Arrays.asList(getArray())).iterator(); + } + + public void addLast(Bucket o) { + ListState currentState = state.get(); + // create new version of state (what we want it to become) + ListState newState = currentState.addBucket(o); + + /* + * use compareAndSet to set in case multiple threads are attempting (which shouldn't be the case because since addLast will ONLY be called by a single thread at a time due to protection + * provided in getCurrentBucket) + */ + if (state.compareAndSet(currentState, newState)) { + // we succeeded + return; + } else { + // we failed, someone else was adding or removing + // instead of trying again and risking multiple addLast concurrently (which shouldn't be the case) + // we'll just return and let the other thread 'win' and if the timing is off the next call to getCurrentBucket will fix things + return; + } + } + + public Bucket getLast() { + return peekLast(); + } + + public int size() { + // the size can also be worked out each time as: + // return (tail + data.length() - head) % data.length(); + return state.get().size; + } + + public Bucket peekLast() { + return state.get().tail(); + } + + private Bucket[] getArray() { + return state.get().getArray(); + } + + } + + public enum Event { + SUCCESS(1), FAILURE(1), TIMEOUT(1), SHORT_CIRCUITED(1), THREAD_POOL_REJECTED(1), SEMAPHORE_REJECTED(1), BAD_REQUEST(1), + FALLBACK_SUCCESS(1), FALLBACK_FAILURE(1), FALLBACK_REJECTION(1), FALLBACK_MISSING(1), EXCEPTION_THROWN(1), EMIT(1), FALLBACK_EMIT(1), + THREAD_EXECUTION(1), COLLAPSED(1), RESPONSE_FROM_CACHE(1), + COLLAPSER_REQUEST_BATCHED(1), COLLAPSER_BATCH(1); + + private final int type; + + private Event(int type) { + this.type = type; + } + + public boolean isCounter() { + return type == 1; + } + + public boolean isMaxUpdater() { + return type == 2; + } + } + +} \ No newline at end of file diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/TimeLine.java b/mpush-tools/src/main/java/com/mpush/tools/common/TimeLine.java index 112cf893..b70c77d0 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/common/TimeLine.java +++ b/mpush-tools/src/main/java/com/mpush/tools/common/TimeLine.java @@ -27,9 +27,9 @@ * @author ohun@live.cn (夜色) */ public final class TimeLine { - private static final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); private final TimePoint root = new TimePoint("root"); private final String name; + private int pointCount; private TimePoint current = root; public TimeLine() { @@ -40,16 +40,51 @@ public TimeLine(String name) { this.name = name; } + public void begin(String name) { + addTimePoint(name); + } + public void begin() { addTimePoint("begin"); } public void addTimePoint(String name) { current = current.next = new TimePoint(name); + pointCount++; + } + + public void addTimePoints(Object[] points) { + if (points != null) { + for (int i = 0; i < points.length; i++) { + current = current.next = new TimePoint((String) points[i], ((Number) points[++i]).longValue()); + pointCount++; + } + } } - public void end() { + public TimeLine end(String name) { + addTimePoint(name); + return this; + } + + public TimeLine end() { addTimePoint("end"); + return this; + } + + public TimeLine successEnd() { + addTimePoint("success-end"); + return this; + } + + public TimeLine failureEnd() { + addTimePoint("failure-end"); + return this; + } + + public TimeLine timeoutEnd() { + addTimePoint("timeout-end"); + return this; } public void clean() { @@ -60,7 +95,7 @@ public void clean() { public String toString() { StringBuilder sb = new StringBuilder(name); if (root.next != null) { - sb.append('[').append(current.point - root.next.point).append(']'); + sb.append('[').append(current.time - root.next.time).append(']').append("(ms)"); } sb.append('{'); TimePoint next = root; @@ -71,13 +106,30 @@ public String toString() { return sb.toString(); } + public Object[] getTimePoints() { + Object[] arrays = new Object[2 * pointCount]; + int i = 0; + TimePoint next = root; + while ((next = next.next) != null) { + arrays[i++] = next.name; + arrays[i++] = next.time; + } + return arrays; + } + private static class TimePoint { private final String name; - private final long point = System.currentTimeMillis(); - private TimePoint next; + private final long time; + private transient TimePoint next; public TimePoint(String name) { this.name = name; + this.time = System.currentTimeMillis(); + } + + public TimePoint(String name, long time) { + this.name = name; + this.time = time; } public void setNext(TimePoint next) { @@ -86,9 +138,8 @@ public void setNext(TimePoint next) { @Override public String toString() { - String header = name + "[" + formatter.format(new Date(point)) + "]"; - if (next == null) return header; - return header + " --" + (next.point - point) + "(ms)--> "; + if (next == null) return name; + return name + " --(" + (next.time - time) + "ms) --> "; } } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/common/URI.java b/mpush-tools/src/main/java/com/mpush/tools/common/URI.java new file mode 100644 index 00000000..bf25af79 --- /dev/null +++ b/mpush-tools/src/main/java/com/mpush/tools/common/URI.java @@ -0,0 +1,1016 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.tools.common; + +import com.mpush.api.Constants; + +import java.io.UnsupportedEncodingException; +import java.net.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +/** + * Created by ohun on 2016/12/20. + * + * @author ohun@live.cn (夜色) + */ +public final class URI { + + private final String protocol; + private final String username; + private final String password; + private final String host; + private final int port; + private final String path; + private final Map parameters; + + private volatile transient Map numbers; + private volatile transient Map urls; + private volatile transient String ip; + private volatile transient String full; + private volatile transient String identity; + private volatile transient String parameter; + private volatile transient String string; + + protected URI() { + this.protocol = null; + this.username = null; + this.password = null; + this.host = null; + this.port = 0; + this.path = null; + this.parameters = null; + } + + public URI(String protocol, String host, int port) { + this(protocol, null, null, host, port, null, (Map) null); + } + + public URI(String protocol, String host, int port, String[] pairs) { // 变长参数...与下面的path参数冲突,改为数组 + this(protocol, null, null, host, port, null, toStringMap(pairs)); + } + + public URI(String protocol, String host, int port, Map parameters) { + this(protocol, null, null, host, port, null, parameters); + } + + public URI(String protocol, String host, int port, String path) { + this(protocol, null, null, host, port, path, (Map) null); + } + + public URI(String protocol, String host, int port, String path, String... pairs) { + this(protocol, null, null, host, port, path, toStringMap(pairs)); + } + + public URI(String protocol, String host, int port, String path, Map parameters) { + this(protocol, null, null, host, port, path, parameters); + } + + public URI(String protocol, String username, String password, String host, int port, String path) { + this(protocol, username, password, host, port, path, (Map) null); + } + + public URI(String protocol, String username, String password, String host, int port, String path, String... pairs) { + this(protocol, username, password, host, port, path, toStringMap(pairs)); + } + + public URI(String protocol, String username, String password, String host, int port, String path, Map parameters) { + if ((username == null || username.length() == 0) && password != null && password.length() > 0) { + throw new IllegalArgumentException("Invalid url, password without username!"); + } + this.protocol = protocol; + this.username = username; + this.password = password; + this.host = host; + this.port = (port < 0 ? 0 : port); + this.path = path; + while (path != null && path.startsWith("/")) { + path = path.substring(1); + } + if (parameters == null) { + parameters = new HashMap(); + } else { + parameters = new HashMap(parameters); + } + this.parameters = Collections.unmodifiableMap(parameters); + } + + + public static URI valueOf(java.net.URI uri) { + String[] params = uri.getQuery() == null ? null : uri.getQuery().split("&"); + return new URI(uri.getScheme(), uri.getHost(), uri.getPort(), params); + } + + + /** + * Parse url string + * + * @param url URL string + * @return URL instance + * @see URI + */ + public static URI valueOf(String url) { + if (url == null || (url = url.trim()).length() == 0) { + throw new IllegalArgumentException("url == null"); + } + String protocol = null; + String username = null; + String password = null; + String host = null; + int port = 0; + String path = null; + Map parameters = null; + int i = url.indexOf('?'); + if (i >= 0) { + String[] parts = url.substring(i + 1).split("&"); + parameters = new HashMap(); + for (String part : parts) { + part = part.trim(); + if (part.length() > 0) { + int j = part.indexOf('='); + if (j >= 0) { + parameters.put(part.substring(0, j), part.substring(j + 1)); + } else { + parameters.put(part, part); + } + } + } + url = url.substring(0, i); + } + i = url.indexOf("://"); + if (i >= 0) { + if (i == 0) throw new IllegalStateException("url missing protocol: \"" + url + "\""); + protocol = url.substring(0, i); + url = url.substring(i + 3); + } else { + // case: file:/path/to/file.txt + i = url.indexOf(":/"); + if (i >= 0) { + if (i == 0) throw new IllegalStateException("url missing protocol: \"" + url + "\""); + protocol = url.substring(0, i); + url = url.substring(i + 1); + } + } + + i = url.indexOf("/"); + if (i >= 0) { + path = url.substring(i + 1); + url = url.substring(0, i); + } + i = url.indexOf("@"); + if (i >= 0) { + username = url.substring(0, i); + int j = username.indexOf(":"); + if (j >= 0) { + password = username.substring(j + 1); + username = username.substring(0, j); + } + url = url.substring(i + 1); + } + i = url.indexOf(":"); + if (i >= 0 && i < url.length() - 1) { + port = Integer.parseInt(url.substring(i + 1)); + url = url.substring(0, i); + } + if (url.length() > 0) host = url; + return new URI(protocol, username, password, host, port, path, parameters); + } + + public String getProtocol() { + return protocol; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getAuthority() { + if ((username == null || username.length() == 0) && (password == null || password.length() == 0)) { + return null; + } + return (username == null ? "" : username) + ":" + (password == null ? "" : password); + } + + public String getHost() { + return host; + } + + + public String getIp() { + if (ip == null) { + try { + ip = InetAddress.getByName(host).getHostAddress(); + } catch (UnknownHostException e) { + ip = host; + } + } + return ip; + } + + public int getPort() { + return port; + } + + public int getPort(int defaultPort) { + return port <= 0 ? defaultPort : port; + } + + public String getAddress() { + return port <= 0 ? host : host + ":" + port; + } + + + public String getPath() { + return path; + } + + public String getAbsolutePath() { + if (path != null && !path.startsWith("/")) { + return "/" + path; + } + return path; + } + + public URI setProtocol(String protocol) { + return new URI(protocol, username, password, host, port, path, getParameters()); + } + + public URI setUsername(String username) { + return new URI(protocol, username, password, host, port, path, getParameters()); + } + + public URI setPassword(String password) { + return new URI(protocol, username, password, host, port, path, getParameters()); + } + + public URI setAddress(String address) { + int i = address.lastIndexOf(':'); + String host; + int port = this.port; + if (i >= 0) { + host = address.substring(0, i); + port = Integer.parseInt(address.substring(i + 1)); + } else { + host = address; + } + return new URI(protocol, username, password, host, port, path, getParameters()); + } + + public URI setHost(String host) { + return new URI(protocol, username, password, host, port, path, getParameters()); + } + + public URI setPort(int port) { + return new URI(protocol, username, password, host, port, path, getParameters()); + } + + public URI setPath(String path) { + return new URI(protocol, username, password, host, port, path, getParameters()); + } + + public Map getParameters() { + return parameters; + } + + + public String getParameter(String key) { + return parameters.get(key); + } + + public String getParameter(String key, String defaultValue) { + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + return value; + } + + public String[] getParameter(String key, String[] defaultValue) { + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + return value.split(","); + } + + private Map getNumbers() { + if (numbers == null) { // 允许并发重复创建 + numbers = new ConcurrentHashMap(); + } + return numbers; + } + + + public double getParameter(String key, double defaultValue) { + Number n = getNumbers().get(key); + if (n != null) { + return n.doubleValue(); + } + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + double d = Double.parseDouble(value); + getNumbers().put(key, d); + return d; + } + + public float getParameter(String key, float defaultValue) { + Number n = getNumbers().get(key); + if (n != null) { + return n.floatValue(); + } + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + float f = Float.parseFloat(value); + getNumbers().put(key, f); + return f; + } + + public long getParameter(String key, long defaultValue) { + Number n = getNumbers().get(key); + if (n != null) { + return n.longValue(); + } + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + long l = Long.parseLong(value); + getNumbers().put(key, l); + return l; + } + + public int getParameter(String key, int defaultValue) { + Number n = getNumbers().get(key); + if (n != null) { + return n.intValue(); + } + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + int i = Integer.parseInt(value); + getNumbers().put(key, i); + return i; + } + + public short getParameter(String key, short defaultValue) { + Number n = getNumbers().get(key); + if (n != null) { + return n.shortValue(); + } + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + short s = Short.parseShort(value); + getNumbers().put(key, s); + return s; + } + + public byte getParameter(String key, byte defaultValue) { + Number n = getNumbers().get(key); + if (n != null) { + return n.byteValue(); + } + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + byte b = Byte.parseByte(value); + getNumbers().put(key, b); + return b; + } + + + public char getParameter(String key, char defaultValue) { + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + return value.charAt(0); + } + + public boolean getParameter(String key, boolean defaultValue) { + String value = getParameter(key); + if (value == null || value.length() == 0) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } + + public boolean hasParameter(String key) { + String value = getParameter(key); + return value != null && value.length() > 0; + } + + public String getMethodParameter(String method, String key) { + String value = parameters.get(method + "." + key); + if (value == null || value.length() == 0) { + return getParameter(key); + } + return value; + } + + public String getMethodParameter(String method, String key, String defaultValue) { + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + return value; + } + + public double getMethodParameter(String method, String key, double defaultValue) { + String methodKey = method + "." + key; + Number n = getNumbers().get(methodKey); + if (n != null) { + return n.intValue(); + } + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + double d = Double.parseDouble(value); + getNumbers().put(methodKey, d); + return d; + } + + public float getMethodParameter(String method, String key, float defaultValue) { + String methodKey = method + "." + key; + Number n = getNumbers().get(methodKey); + if (n != null) { + return n.intValue(); + } + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + float f = Float.parseFloat(value); + getNumbers().put(methodKey, f); + return f; + } + + public long getMethodParameter(String method, String key, long defaultValue) { + String methodKey = method + "." + key; + Number n = getNumbers().get(methodKey); + if (n != null) { + return n.intValue(); + } + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + long l = Long.parseLong(value); + getNumbers().put(methodKey, l); + return l; + } + + public int getMethodParameter(String method, String key, int defaultValue) { + String methodKey = method + "." + key; + Number n = getNumbers().get(methodKey); + if (n != null) { + return n.intValue(); + } + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + int i = Integer.parseInt(value); + getNumbers().put(methodKey, i); + return i; + } + + public short getMethodParameter(String method, String key, short defaultValue) { + String methodKey = method + "." + key; + Number n = getNumbers().get(methodKey); + if (n != null) { + return n.shortValue(); + } + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + short s = Short.parseShort(value); + getNumbers().put(methodKey, s); + return s; + } + + public byte getMethodParameter(String method, String key, byte defaultValue) { + String methodKey = method + "." + key; + Number n = getNumbers().get(methodKey); + if (n != null) { + return n.byteValue(); + } + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + byte b = Byte.parseByte(value); + getNumbers().put(methodKey, b); + return b; + } + + public double getMethodPositiveParameter(String method, String key, double defaultValue) { + if (defaultValue <= 0) { + throw new IllegalArgumentException("defaultValue <= 0"); + } + double value = getMethodParameter(method, key, defaultValue); + if (value <= 0) { + return defaultValue; + } + return value; + } + + public float getMethodPositiveParameter(String method, String key, float defaultValue) { + if (defaultValue <= 0) { + throw new IllegalArgumentException("defaultValue <= 0"); + } + float value = getMethodParameter(method, key, defaultValue); + if (value <= 0) { + return defaultValue; + } + return value; + } + + public long getMethodPositiveParameter(String method, String key, long defaultValue) { + if (defaultValue <= 0) { + throw new IllegalArgumentException("defaultValue <= 0"); + } + long value = getMethodParameter(method, key, defaultValue); + if (value <= 0) { + return defaultValue; + } + return value; + } + + public int getMethodPositiveParameter(String method, String key, int defaultValue) { + if (defaultValue <= 0) { + throw new IllegalArgumentException("defaultValue <= 0"); + } + int value = getMethodParameter(method, key, defaultValue); + if (value <= 0) { + return defaultValue; + } + return value; + } + + public short getMethodPositiveParameter(String method, String key, short defaultValue) { + if (defaultValue <= 0) { + throw new IllegalArgumentException("defaultValue <= 0"); + } + short value = getMethodParameter(method, key, defaultValue); + if (value <= 0) { + return defaultValue; + } + return value; + } + + public byte getMethodPositiveParameter(String method, String key, byte defaultValue) { + if (defaultValue <= 0) { + throw new IllegalArgumentException("defaultValue <= 0"); + } + byte value = getMethodParameter(method, key, defaultValue); + if (value <= 0) { + return defaultValue; + } + return value; + } + + public char getMethodParameter(String method, String key, char defaultValue) { + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + return value.charAt(0); + } + + public boolean getMethodParameter(String method, String key, boolean defaultValue) { + String value = getMethodParameter(method, key); + if (value == null || value.length() == 0) { + return defaultValue; + } + return Boolean.parseBoolean(value); + } + + //$NON-NLS-分组参数结束$ + + public boolean isLocalHost() { + return host != null + && (Pattern.compile("127(\\.\\d{1,3}){3}$").matcher(host).matches() + || host.equalsIgnoreCase("localhost")); + } + + public boolean isAnyHost() { + return Constants.ANY_HOST.equals(host); + } + + public URI addParameter(String key, boolean value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, char value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, byte value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, short value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, int value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, long value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, float value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, double value) { + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, Enum value) { + if (value == null) return this; + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, Number value) { + if (value == null) return this; + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, CharSequence value) { + if (value == null || value.length() == 0) return this; + return addParameter(key, String.valueOf(value)); + } + + public URI addParameter(String key, String value) { + if (key == null || key.length() == 0 || value == null || value.length() == 0) { + return this; + } + // 如果没有修改,直接返回。 + if (value.equals(getParameters().get(key))) { // value != null + return this; + } + + Map map = new HashMap(getParameters()); + map.put(key, value); + return new URI(protocol, username, password, host, port, path, map); + } + + public URI addParameterIfAbsent(String key, String value) { + if (key == null || key.length() == 0 || value == null || value.length() == 0) { + return this; + } + if (hasParameter(key)) { + return this; + } + Map map = new HashMap(getParameters()); + map.put(key, value); + return new URI(protocol, username, password, host, port, path, map); + } + + /** + * Add parameters to a new url. + * + * @param parameters parameters + * @return A new URL + */ + public URI addParameters(Map parameters) { + if (parameters == null || parameters.size() == 0) { + return this; + } + + boolean hasAndEqual = true; + for (Map.Entry entry : parameters.entrySet()) { + String value = getParameters().get(entry.getKey()); + if (value == null && entry.getValue() != null || !value.equals(entry.getValue())) { + hasAndEqual = false; + break; + } + } + // 如果没有修改,直接返回。 + if (hasAndEqual) return this; + + Map map = new HashMap(getParameters()); + map.putAll(parameters); + return new URI(protocol, username, password, host, port, path, map); + } + + public URI addParametersIfAbsent(Map parameters) { + if (parameters == null || parameters.size() == 0) { + return this; + } + Map map = new HashMap(parameters); + map.putAll(getParameters()); + return new URI(protocol, username, password, host, port, path, map); + } + + public URI addParameters(String... pairs) { + if (pairs == null || pairs.length == 0) { + return this; + } + if (pairs.length % 2 != 0) { + throw new IllegalArgumentException("Map pairs can not be odd number."); + } + Map map = new HashMap(); + int len = pairs.length / 2; + for (int i = 0; i < len; i++) { + map.put(pairs[2 * i], pairs[2 * i + 1]); + } + return addParameters(map); + } + + public URI removeParameter(String key) { + if (key == null || key.length() == 0) { + return this; + } + return removeParameters(key); + } + + public URI removeParameters(Collection keys) { + if (keys == null || keys.size() == 0) { + return this; + } + return removeParameters(keys.toArray(new String[0])); + } + + public URI removeParameters(String... keys) { + if (keys == null || keys.length == 0) { + return this; + } + Map map = new HashMap(getParameters()); + for (String key : keys) { + map.remove(key); + } + if (map.size() == getParameters().size()) { + return this; + } + return new URI(protocol, username, password, host, port, path, map); + } + + public URI clearParameters() { + return new URI(protocol, username, password, host, port, path, new HashMap()); + } + + public String getRawParameter(String key) { + if ("protocol".equals(key)) { + return protocol; + } + if ("username".equals(key)) { + return username; + } + if ("password".equals(key)) { + return password; + } + if ("host".equals(key)) { + return host; + } + if ("port".equals(key)) { + return String.valueOf(port); + } + if ("path".equals(key)) { + return path; + } + return getParameter(key); + } + + public Map toMap() { + Map map = new HashMap(parameters); + if (protocol != null) { + map.put("protocol", protocol); + } + if (username != null) { + map.put("username", username); + } + if (password != null) { + map.put("password", password); + } + if (host != null) { + map.put("host", host); + } + if (port > 0) { + map.put("port", String.valueOf(port)); + } + if (path != null) { + map.put("path", path); + } + return map; + } + + public String toString() { + if (string != null) { + return string; + } + return string = buildString(false, true); // no show username and password + } + + public String toString(String... parameters) { + return buildString(false, true, parameters); // no show username and password + } + + public String toIdentityString() { + if (identity != null) { + return identity; + } + return identity = buildString(true, false); // only return identity message, see the method "equals" and "hashCode" + } + + public String toIdentityString(String... parameters) { + return buildString(true, false, parameters); // only return identity message, see the method "equals" and "hashCode" + } + + public String toFullString() { + if (full != null) { + return full; + } + return full = buildString(true, true); + } + + public String toFullString(String... parameters) { + return buildString(true, true, parameters); + } + + public String toParameterString() { + if (parameter != null) { + return parameter; + } + return parameter = toParameterString(new String[0]); + } + + public String toParameterString(String... parameters) { + StringBuilder buf = new StringBuilder(); + buildParameters(buf, false, parameters); + return buf.toString(); + } + + private void buildParameters(StringBuilder buf, boolean concat, String[] parameters) { + if (getParameters() != null && getParameters().size() > 0) { + List includes = (parameters == null || parameters.length == 0 ? null : Arrays.asList(parameters)); + boolean first = true; + for (Map.Entry entry : new TreeMap(getParameters()).entrySet()) { + if (entry.getKey() != null && entry.getKey().length() > 0 && (includes == null || includes.contains(entry.getKey()))) { + if (first) { + if (concat) { + buf.append("?"); + } + first = false; + } else { + buf.append("&"); + } + buf.append(entry.getKey()); + buf.append("="); + buf.append(entry.getValue() == null ? "" : entry.getValue().trim()); + } + } + } + } + + private String buildString(boolean appendUser, boolean appendParameter, String... parameters) { + return buildString(appendUser, appendParameter, false, parameters); + } + + private String buildString(boolean appendUser, boolean appendParameter, boolean useIP, String... parameters) { + StringBuilder buf = new StringBuilder(); + if (protocol != null && protocol.length() > 0) { + buf.append(protocol); + buf.append("://"); + } + if (appendUser && username != null && username.length() > 0) { + buf.append(username); + if (password != null && password.length() > 0) { + buf.append(":"); + buf.append(password); + } + buf.append("@"); + } + String host; + if (useIP) { + host = getIp(); + } else { + host = getHost(); + } + if (host != null && host.length() > 0) { + buf.append(host); + if (port > 0) { + buf.append(":"); + buf.append(port); + } + } + String path = getPath(); + if (path != null && path.length() > 0) { + buf.append("/"); + buf.append(path); + } + if (appendParameter) { + buildParameters(buf, true, parameters); + } + return buf.toString(); + } + + public java.net.URI toJavaURI() { + return java.net.URI.create(toString()); + } + + public InetSocketAddress toInetSocketAddress() { + return new InetSocketAddress(host, port); + } + + public static Map toStringMap(String... pairs) { + Map parameters = new HashMap(); + if (pairs.length > 0) { + if (pairs.length % 2 != 0) { + throw new IllegalArgumentException("pairs must be even."); + } + for (int i = 0; i < pairs.length; i = i + 2) { + parameters.put(pairs[i], pairs[i + 1]); + } + } + return parameters; + } + + public static String encode(String value) { + if (value == null || value.length() == 0) { + return ""; + } + try { + return URLEncoder.encode(value, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + public static String decode(String value) { + if (value == null || value.length() == 0) { + return ""; + } + try { + return URLDecoder.decode(value, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((host == null) ? 0 : host.hashCode()); + result = prime * result + ((parameters == null) ? 0 : parameters.hashCode()); + result = prime * result + ((password == null) ? 0 : password.hashCode()); + result = prime * result + ((path == null) ? 0 : path.hashCode()); + result = prime * result + port; + result = prime * result + ((protocol == null) ? 0 : protocol.hashCode()); + result = prime * result + ((username == null) ? 0 : username.hashCode()); + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + URI uri = (URI) o; + + if (port != uri.port) return false; + if (protocol != null ? !protocol.equals(uri.protocol) : uri.protocol != null) return false; + if (username != null ? !username.equals(uri.username) : uri.username != null) return false; + if (password != null ? !password.equals(uri.password) : uri.password != null) return false; + if (host != null ? !host.equals(uri.host) : uri.host != null) return false; + if (path != null ? !path.equals(uri.path) : uri.path != null) return false; + if (parameters != null ? !parameters.equals(uri.parameters) : uri.parameters != null) return false; + return true; + } +} diff --git a/mpush-tools/src/main/java/com/mpush/tools/config/CC.java b/mpush-tools/src/main/java/com/mpush/tools/config/CC.java index 2f597c11..c91e3117 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/config/CC.java +++ b/mpush-tools/src/main/java/com/mpush/tools/config/CC.java @@ -20,25 +20,18 @@ package com.mpush.tools.config; import com.mpush.api.spi.net.DnsMapping; -import com.mpush.tools.config.data.RedisGroup; -import com.mpush.tools.config.data.RedisServer; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import com.typesafe.config.ConfigList; -import com.typesafe.config.ConfigObject; +import com.mpush.tools.config.data.RedisNode; +import com.typesafe.config.*; import java.io.File; import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; -import static com.typesafe.config.ConfigBeanFactory.create; import static java.util.stream.Collectors.toCollection; /** + * mpush 配置中心 * Created by yxx on 2016/5/20. * * @author ohun@live.cn @@ -47,8 +40,8 @@ public interface CC { Config cfg = load(); static Config load() { - Config config = ConfigFactory.load(); - String custom_conf = "mp.conf"; + Config config = ConfigFactory.load();//扫描加载所有可用的配置文件 + String custom_conf = "mp.conf";//加载自定义配置, 值来自jvm启动参数指定-Dmp.conf if (config.hasPath(custom_conf)) { File file = new File(config.getString(custom_conf)); if (file.exists()) { @@ -61,8 +54,9 @@ static Config load() { interface mp { Config cfg = CC.cfg.getObject("mp").toConfig(); - String log_dir = cfg.getString("log.dir"); - String log_level = cfg.getString("log.level"); + String log_dir = cfg.getString("log-dir"); + String log_level = cfg.getString("log-level"); + String log_conf_path = cfg.getString("log-conf-path"); interface core { Config cfg = mp.cfg.getObject("core").toConfig(); @@ -80,23 +74,90 @@ interface core { int max_hb_timeout_times = cfg.getInt("max-hb-timeout-times"); String epoll_provider = cfg.getString("epoll-provider"); + + static boolean useNettyEpoll() { + if (!"netty".equals(CC.mp.core.epoll_provider)) return false; + String name = CC.cfg.getString("os.name").toLowerCase(Locale.UK).trim(); + return name.startsWith("linux");//只在linux下使用netty提供的epoll库 + } } interface net { Config cfg = mp.cfg.getObject("net").toConfig(); + String local_ip = cfg.getString("local-ip"); + String public_ip = cfg.getString("public-ip"); + int connect_server_port = cfg.getInt("connect-server-port"); - int gateway_server_port = cfg.getInt("gateway-server-port"); + String connect_server_bind_ip = cfg.getString("connect-server-bind-ip"); + String connect_server_register_ip = cfg.getString("connect-server-register-ip"); + Map connect_server_register_attr = cfg.getObject("connect-server-register-attr").unwrapped(); + + int admin_server_port = cfg.getInt("admin-server-port"); + int gateway_server_port = cfg.getInt("gateway-server-port"); + String gateway_server_bind_ip = cfg.getString("gateway-server-bind-ip"); + String gateway_server_register_ip = cfg.getString("gateway-server-register-ip"); + String gateway_server_net = cfg.getString("gateway-server-net"); + String gateway_server_multicast = cfg.getString("gateway-server-multicast"); + String gateway_client_multicast = cfg.getString("gateway-client-multicast"); + int gateway_client_port = cfg.getInt("gateway-client-port"); + + int ws_server_port = cfg.getInt("ws-server-port"); + String ws_path = cfg.getString("ws-path"); + int gateway_client_num = cfg.getInt("gateway-client-num"); + + static boolean tcpGateway() { + return "tcp".equals(gateway_server_net); + } + + static boolean udpGateway() { + return "udp".equals(gateway_server_net); + } + + static boolean wsEnabled() { + return ws_server_port > 0; + } + + static boolean udtGateway() { + return "udt".equals(gateway_server_net); + } + + static boolean sctpGateway() { + return "sctp".equals(gateway_server_net); + } + + interface public_ip_mapping { Map mappings = net.cfg.getObject("public-host-mapping").unwrapped(); static String getString(String localIp) { - return (String) mappings.getOrDefault(localIp, localIp); + return (String) mappings.get(localIp); } + } + + interface snd_buf { + Config cfg = net.cfg.getObject("snd_buf").toConfig(); + int connect_server = (int) cfg.getMemorySize("connect-server").toBytes(); + int gateway_server = (int) cfg.getMemorySize("gateway-server").toBytes(); + int gateway_client = (int) cfg.getMemorySize("gateway-client").toBytes(); + } + + interface rcv_buf { + Config cfg = net.cfg.getObject("rcv_buf").toConfig(); + int connect_server = (int) cfg.getMemorySize("connect-server").toBytes(); + int gateway_server = (int) cfg.getMemorySize("gateway-server").toBytes(); + int gateway_client = (int) cfg.getMemorySize("gateway-client").toBytes(); + } + interface write_buffer_water_mark { + Config cfg = net.cfg.getObject("write-buffer-water-mark").toConfig(); + int connect_server_low = (int) cfg.getMemorySize("connect-server-low").toBytes(); + int connect_server_high = (int) cfg.getMemorySize("connect-server-high").toBytes(); + int gateway_server_low = (int) cfg.getMemorySize("gateway-server-low").toBytes(); + int gateway_server_high = (int) cfg.getMemorySize("gateway-server-high").toBytes(); } interface traffic_shaping { @@ -144,8 +205,6 @@ interface security { String private_key = cfg.getString("private-key"); - int ras_key_length = cfg.getInt("ras-key-length"); - } interface thread { @@ -156,21 +215,13 @@ interface pool { Config cfg = thread.cfg.getObject("pool").toConfig(); - interface boss { - Config cfg = pool.cfg.getObject("boss").toConfig(); - int min = cfg.getInt("min"); - int max = cfg.getInt("max"); - int queue_size = cfg.getInt("queue-size"); - - } - - interface work { - Config cfg = pool.cfg.getObject("work").toConfig(); - int min = cfg.getInt("min"); - int max = cfg.getInt("max"); - int queue_size = cfg.getInt("queue-size"); - - } + int conn_work = cfg.getInt("conn-work"); + int http_work = cfg.getInt("http-work"); + int push_task = cfg.getInt("push-task"); + int push_client = cfg.getInt("push-client"); + int ack_timer = cfg.getInt("ack-timer"); + int gateway_server_work = cfg.getInt("gateway-server-work"); + int gateway_client_work = cfg.getInt("gateway-client-work"); interface event_bus { Config cfg = pool.cfg.getObject("event-bus").toConfig(); @@ -180,38 +231,13 @@ interface event_bus { } - interface http_proxy { - Config cfg = pool.cfg.getObject("http-proxy").toConfig(); - int min = cfg.getInt("min"); - int max = cfg.getInt("max"); - int queue_size = cfg.getInt("queue-size"); - - } - - interface biz { - Config cfg = pool.cfg.getObject("biz").toConfig(); - int min = cfg.getInt("min"); - int max = cfg.getInt("max"); - int queue_size = cfg.getInt("queue-size"); - - } - interface mq { Config cfg = pool.cfg.getObject("mq").toConfig(); int min = cfg.getInt("min"); int max = cfg.getInt("max"); int queue_size = cfg.getInt("queue-size"); - - } - - interface push_callback { - Config cfg = pool.cfg.getObject("push-callback").toConfig(); - int min = cfg.getInt("min"); - int max = cfg.getInt("max"); - int queue_size = cfg.getInt("queue-size"); } } - } interface zk { @@ -220,7 +246,7 @@ interface zk { int sessionTimeoutMs = (int) cfg.getDuration("sessionTimeoutMs", TimeUnit.MILLISECONDS); - String local_cache_path = cfg.getString("local-cache-path"); + String watch_path = cfg.getString("watch-path"); int connectionTimeoutMs = (int) cfg.getDuration("connectionTimeoutMs", TimeUnit.MILLISECONDS); @@ -240,66 +266,31 @@ interface retry { int maxSleepMs = (int) cfg.getDuration("maxSleepMs", TimeUnit.MILLISECONDS); } - } interface redis { Config cfg = mp.cfg.getObject("redis").toConfig(); - boolean write_to_zk = cfg.getBoolean("write-to-zk"); + String password = cfg.getString("password"); + String clusterModel = cfg.getString("cluster-model"); + String sentinelMaster = cfg.getString("sentinel-master"); - List cluster_group = cfg.getList("cluster-group") - .stream() - .map(v -> new RedisGroup(ConfigList.class.cast(v) - .stream() - .map(cv -> create(ConfigObject.class.cast(cv).toConfig(), RedisServer.class)) - .collect(toCollection(ArrayList::new)) - ) - ) + List nodes = cfg.getList("nodes") + .stream()//第一纬度数组 + .map(v -> RedisNode.from(v.unwrapped().toString())) .collect(toCollection(ArrayList::new)); - interface config { - - Config cfg = redis.cfg.getObject("config").toConfig(); - - boolean jmxEnabled = cfg.getBoolean("jmxEnabled"); - - int minIdle = cfg.getInt("minIdle"); - - boolean testOnReturn = cfg.getBoolean("testOnReturn"); - - long softMinEvictableIdleTimeMillis = cfg.getDuration("softMinEvictableIdleTimeMillis", TimeUnit.MILLISECONDS); - - boolean testOnBorrow = cfg.getBoolean("testOnBorrow"); - - boolean testWhileIdle = cfg.getBoolean("testWhileIdle"); - - long maxWaitMillis = cfg.getDuration("maxWaitMillis", TimeUnit.MILLISECONDS); - - String jmxNameBase = cfg.getString("jmxNameBase"); - - int numTestsPerEvictionRun = (int) cfg.getDuration("numTestsPerEvictionRun", TimeUnit.MILLISECONDS); - - String jmxNamePrefix = cfg.getString("jmxNamePrefix"); - - long minEvictableIdleTimeMillis = cfg.getDuration("minEvictableIdleTimeMillis", TimeUnit.MILLISECONDS); - - boolean blockWhenExhausted = cfg.getBoolean("blockWhenExhausted"); - - boolean fairness = cfg.getBoolean("fairness"); - - long timeBetweenEvictionRunsMillis = cfg.getDuration("timeBetweenEvictionRunsMillis", TimeUnit.MILLISECONDS); - - boolean testOnCreate = cfg.getBoolean("testOnCreate"); - - int maxIdle = cfg.getInt("maxIdle"); - - boolean lifo = cfg.getBoolean("lifo"); - - int maxTotal = cfg.getInt("maxTotal"); + static boolean isCluster() { + return "cluster".equals(clusterModel); + } + static boolean isSentinel() { + return "sentinel".equals(clusterModel); } + static T getPoolConfig(Class clazz) { + return ConfigBeanImpl.createInternal(cfg.getObject("config").toConfig(), clazz); + } } interface http { @@ -325,7 +316,30 @@ static Map> loadMapping() { ); return map; } + } + + interface push { + Config cfg = mp.cfg.getObject("push").toConfig(); + + interface flow_control { + + Config cfg = push.cfg.getObject("flow-control").toConfig(); + + interface global { + Config cfg = flow_control.cfg.getObject("global").toConfig(); + int limit = cfg.getNumber("limit").intValue(); + int max = cfg.getInt("max"); + int duration = (int) cfg.getDuration("duration").toMillis(); + } + + interface broadcast { + Config cfg = flow_control.cfg.getObject("broadcast").toConfig(); + int limit = cfg.getInt("limit"); + int max = cfg.getInt("max"); + int duration = (int) cfg.getDuration("duration").toMillis(); + } + } } interface monitor { @@ -334,12 +348,8 @@ interface monitor { boolean dump_stack = cfg.getBoolean("dump-stack"); boolean print_log = cfg.getBoolean("print-log"); Duration dump_period = cfg.getDuration("dump-period"); - } - - interface spi { - Config cfg = mp.cfg.getObject("spi").toConfig(); - String thread_pool_factory = cfg.getString("thread-pool-factory"); - String dns_mapping_manager = cfg.getString("dns-mapping-manager"); + boolean profile_enabled = cfg.getBoolean("profile-enabled"); + Duration profile_slowly_duration = cfg.getDuration("profile-slowly-duration"); } } } \ No newline at end of file diff --git a/mpush-tools/src/main/java/com/mpush/tools/config/ConfigBeanImpl.java b/mpush-tools/src/main/java/com/mpush/tools/config/ConfigBeanImpl.java new file mode 100644 index 00000000..c35ca70c --- /dev/null +++ b/mpush-tools/src/main/java/com/mpush/tools/config/ConfigBeanImpl.java @@ -0,0 +1,238 @@ +package com.mpush.tools.config; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.time.Duration; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigObject; +import com.typesafe.config.ConfigList; +import com.typesafe.config.ConfigException; +import com.typesafe.config.ConfigMemorySize; +import com.typesafe.config.ConfigValue; +import com.typesafe.config.ConfigValueType; +import com.typesafe.config.impl.ConfigImplUtil; + +/** + * Internal implementation detail, not ABI stable, do not touch. + * For use only by the {@link com.typesafe.config} package. + */ +public final class ConfigBeanImpl { + + /** + * This is public ONLY for use by the "config" package, DO NOT USE this ABI + * may change. + * + * @param type of the bean + * @param config config to use + * @param clazz class of the bean + * @return the bean instance + */ + public static T createInternal(Config config, Class clazz) { + + Map configProps = new HashMap(); + Map originalNames = new HashMap(); + for (Map.Entry configProp : config.root().entrySet()) { + String originalName = configProp.getKey(); + String camelName = toCamelCase(originalName); + // if a setting is in there both as some hyphen name and the camel name, + // the camel one wins + if (originalNames.containsKey(camelName) && !originalName.equals(camelName)) { + // if we aren't a camel name to start with, we lose. + // if we are or we are the first matching key, we win. + } else { + configProps.put(camelName, (ConfigValue) configProp.getValue()); + originalNames.put(camelName, originalName); + } + } + + BeanInfo beanInfo = null; + try { + beanInfo = Introspector.getBeanInfo(clazz); + } catch (IntrospectionException e) { + throw new ConfigException.BadBean("Could not get bean information for class " + clazz.getName(), e); + } + + try { + List beanProps = new ArrayList(); + for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { + if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) { + continue; + } + beanProps.add(beanProp); + } + + // Fill in the bean instance + T bean = clazz.newInstance(); + for (PropertyDescriptor beanProp : beanProps) { + Method setter = beanProp.getWriteMethod(); + Type parameterType = setter.getGenericParameterTypes()[0]; + Class parameterClass = setter.getParameterTypes()[0]; + String configKey = originalNames.get(beanProp.getName()); + if (configKey != null) { + Object unwrapped = getValue(clazz, parameterType, parameterClass, config, configKey); + setter.invoke(bean, unwrapped); + } + } + return bean; + } catch (InstantiationException e) { + throw new ConfigException.BadBean(clazz.getName() + " needs a public no-args constructor to be used as a bean", e); + } catch (IllegalAccessException e) { + throw new ConfigException.BadBean(clazz.getName() + " getters and setters are not accessible, they must be for use as a bean", e); + } catch (InvocationTargetException e) { + throw new ConfigException.BadBean("Calling bean method on " + clazz.getName() + " caused an exception", e); + } + } + + // we could magically make this work in many cases by doing + // getAnyRef() (or getValue().unwrapped()), but anytime we + // rely on that, we aren't doing the type conversions Config + // usually does, and we will throw ClassCastException instead + // of a nicer error message giving the name of the bad + // setting. So, instead, we only support a limited number of + // types plus you can always use Object, ConfigValue, Config, + // ConfigObject, etc. as an escape hatch. + private static Object getValue(Class beanClass, Type parameterType, Class parameterClass, Config config, + String configPropName) { + if (parameterClass == Boolean.class || parameterClass == boolean.class) { + return config.getBoolean(configPropName); + } else if (parameterClass == Integer.class || parameterClass == int.class) { + return config.getInt(configPropName); + } else if (parameterClass == Double.class || parameterClass == double.class) { + return config.getDouble(configPropName); + } else if (parameterClass == Long.class || parameterClass == long.class) { + return config.getLong(configPropName); + } else if (parameterClass == String.class) { + return config.getString(configPropName); + } else if (parameterClass == Duration.class) { + return config.getDuration(configPropName); + } else if (parameterClass == ConfigMemorySize.class) { + return config.getMemorySize(configPropName); + } else if (parameterClass == Object.class) { + return config.getAnyRef(configPropName); + } else if (parameterClass == List.class) { + return getListValue(beanClass, parameterType, parameterClass, config, configPropName); + } else if (parameterClass == Map.class) { + // we could do better here, but right now we don't. + Type[] typeArgs = ((ParameterizedType) parameterType).getActualTypeArguments(); + if (typeArgs[0] != String.class || typeArgs[1] != Object.class) { + throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported Map<" + typeArgs[0] + "," + typeArgs[1] + ">, only Map is supported right now"); + } + return config.getObject(configPropName).unwrapped(); + } else if (parameterClass == Config.class) { + return config.getConfig(configPropName); + } else if (parameterClass == ConfigObject.class) { + return config.getObject(configPropName); + } else if (parameterClass == ConfigValue.class) { + return config.getValue(configPropName); + } else if (parameterClass == ConfigList.class) { + return config.getList(configPropName); + } else if (hasAtLeastOneBeanProperty(parameterClass)) { + return createInternal(config.getConfig(configPropName), parameterClass); + } else { + throw new ConfigException.BadBean("Bean property " + configPropName + " of class " + beanClass.getName() + " has unsupported type " + parameterType); + } + } + + private static Object getListValue(Class beanClass, Type parameterType, Class parameterClass, Config config, String configPropName) { + Type elementType = ((ParameterizedType) parameterType).getActualTypeArguments()[0]; + + if (elementType == Boolean.class) { + return config.getBooleanList(configPropName); + } else if (elementType == Integer.class) { + return config.getIntList(configPropName); + } else if (elementType == Double.class) { + return config.getDoubleList(configPropName); + } else if (elementType == Long.class) { + return config.getLongList(configPropName); + } else if (elementType == String.class) { + return config.getStringList(configPropName); + } else if (elementType == Duration.class) { + return config.getDurationList(configPropName); + } else if (elementType == ConfigMemorySize.class) { + return config.getMemorySizeList(configPropName); + } else if (elementType == Object.class) { + return config.getAnyRefList(configPropName); + } else if (elementType == Config.class) { + return config.getConfigList(configPropName); + } else if (elementType == ConfigObject.class) { + return config.getObjectList(configPropName); + } else if (elementType == ConfigValue.class) { + return config.getList(configPropName); + } else { + throw new ConfigException.BadBean("Bean property '" + configPropName + "' of class " + beanClass.getName() + " has unsupported list element type " + elementType); + } + } + + // null if we can't easily say; this is heuristic/best-effort + private static ConfigValueType getValueTypeOrNull(Class parameterClass) { + if (parameterClass == Boolean.class || parameterClass == boolean.class) { + return ConfigValueType.BOOLEAN; + } else if (parameterClass == Integer.class || parameterClass == int.class) { + return ConfigValueType.NUMBER; + } else if (parameterClass == Double.class || parameterClass == double.class) { + return ConfigValueType.NUMBER; + } else if (parameterClass == Long.class || parameterClass == long.class) { + return ConfigValueType.NUMBER; + } else if (parameterClass == String.class) { + return ConfigValueType.STRING; + } else if (parameterClass == Duration.class) { + return null; + } else if (parameterClass == ConfigMemorySize.class) { + return null; + } else if (parameterClass == List.class) { + return ConfigValueType.LIST; + } else if (parameterClass == Map.class) { + return ConfigValueType.OBJECT; + } else if (parameterClass == Config.class) { + return ConfigValueType.OBJECT; + } else if (parameterClass == ConfigObject.class) { + return ConfigValueType.OBJECT; + } else if (parameterClass == ConfigList.class) { + return ConfigValueType.LIST; + } else { + return null; + } + } + + private static boolean hasAtLeastOneBeanProperty(Class clazz) { + BeanInfo beanInfo = null; + try { + beanInfo = Introspector.getBeanInfo(clazz); + } catch (IntrospectionException e) { + return false; + } + + for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) { + if (beanProp.getReadMethod() != null && beanProp.getWriteMethod() != null) { + return true; + } + } + + return false; + } + + private static String toCamelCase(String originalName) { + String[] words = originalName.split("-+"); + StringBuilder nameBuilder = new StringBuilder(originalName.length()); + for (String word : words) { + if (nameBuilder.length() == 0) { + nameBuilder.append(word); + } else { + nameBuilder.append(word.substring(0, 1).toUpperCase()); + nameBuilder.append(word.substring(1)); + } + } + return nameBuilder.toString(); + } +} diff --git a/mpush-tools/src/main/java/com/mpush/tools/config/ConfigManager.java b/mpush-tools/src/main/java/com/mpush/tools/config/ConfigManager.java deleted file mode 100644 index 7f305bf3..00000000 --- a/mpush-tools/src/main/java/com/mpush/tools/config/ConfigManager.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.tools.config; - -import com.mpush.tools.Utils; - -import static com.mpush.tools.Utils.getInetAddress; - -/** - * Created by yxx on 2016/5/18. - * - * @author ohun@live.cn - */ -public class ConfigManager { - public static final ConfigManager I = new ConfigManager(); - - private ConfigManager() { - } - - public int getHeartbeat(int min, int max) { - return Math.max( - CC.mp.core.min_heartbeat, - Math.min(max, CC.mp.core.max_heartbeat) - ); - } - - public String getLocalIp() { - return Utils.getLocalIp(); - } - - public String getPublicIp() { - String localIp = getInetAddress(); - - String remoteIp = CC.mp.net.public_ip_mapping.getString(localIp); - - return remoteIp; - } -} diff --git a/mpush-tools/src/main/java/com/mpush/tools/config/ConfigTools.java b/mpush-tools/src/main/java/com/mpush/tools/config/ConfigTools.java new file mode 100644 index 00000000..560f02cf --- /dev/null +++ b/mpush-tools/src/main/java/com/mpush/tools/config/ConfigTools.java @@ -0,0 +1,90 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.tools.config; + +import com.mpush.tools.Utils; +import com.mpush.tools.config.CC.mp.net.public_ip_mapping; + +/** + * Created by yxx on 2016/5/18. + * + * @author ohun@live.cn + */ +public final class ConfigTools { + + private ConfigTools() { + } + + public static int getHeartbeat(int min, int max) { + return Math.max( + CC.mp.core.min_heartbeat, + Math.min(max, CC.mp.core.max_heartbeat) + ); + } + + /** + * 获取内网IP地址 + * + * @return 内网IP地址 + */ + public static String getLocalIp() { + if (CC.mp.net.local_ip.length() > 0) { + return CC.mp.net.local_ip; + } + return Utils.lookupLocalIp(); + } + + /** + * 获取外网IP地址 + * + * @return 外网IP地址 + */ + public static String getPublicIp() { + + if (CC.mp.net.public_ip.length() > 0) { + return CC.mp.net.public_ip; + } + + String localIp = getLocalIp(); + + String remoteIp = public_ip_mapping.getString(localIp); + + if (remoteIp == null) { + remoteIp = Utils.lookupExtranetIp(); + } + + return remoteIp == null ? localIp : remoteIp; + } + + + public static String getConnectServerRegisterIp() { + if (CC.mp.net.connect_server_register_ip.length() > 0) { + return CC.mp.net.connect_server_register_ip; + } + return getPublicIp(); + } + + public static String getGatewayServerRegisterIp() { + if (CC.mp.net.gateway_server_register_ip.length() > 0) { + return CC.mp.net.gateway_server_register_ip; + } + return getLocalIp(); + } +} diff --git a/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisGroup.java b/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisGroup.java index 30369c96..afb3ca04 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisGroup.java +++ b/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisGroup.java @@ -27,12 +27,12 @@ * redis 组 */ public class RedisGroup { - public List redisNodeList = Collections.emptyList(); + public List redisNodeList = Collections.emptyList(); public RedisGroup() { } - public RedisGroup(List redisNodeList) { + public RedisGroup(List redisNodeList) { this.redisNodeList = redisNodeList; } diff --git a/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisServer.java b/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisNode.java similarity index 79% rename from mpush-tools/src/main/java/com/mpush/tools/config/data/RedisServer.java rename to mpush-tools/src/main/java/com/mpush/tools/config/data/RedisNode.java index 451efdf6..6819dad7 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisServer.java +++ b/mpush-tools/src/main/java/com/mpush/tools/config/data/RedisNode.java @@ -22,18 +22,16 @@ /** * redis 相关的配置信息 */ -public class RedisServer { +public class RedisNode { public String host; public int port; - public String password; - public RedisServer() { + public RedisNode() { } - public RedisServer(String host, int port, String password) { + public RedisNode(String host, int port) { this.host = host; this.port = port; - this.password = password; } public String getHost() { @@ -52,12 +50,14 @@ public void setPort(int port) { this.port = port; } - public String getPassword() { - return password; - } - public void setPassword(String password) { - this.password = password; + public static RedisNode from(String config) { + String[] array = config.split(":"); + if (array.length == 2) { + return new RedisNode(array[0], Integer.parseInt(array[1])); + } else { + return new RedisNode(array[0], Integer.parseInt(array[1])); + } } @Override @@ -65,7 +65,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - RedisServer server = (RedisServer) o; + RedisNode server = (RedisNode) o; if (port != server.port) return false; return host.equals(server.host); @@ -84,7 +84,6 @@ public String toString() { return "RedisServer{" + "host='" + host + '\'' + ", port=" + port + - ", password='" + password + '\'' + '}'; } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/crypto/AESUtils.java b/mpush-tools/src/main/java/com/mpush/tools/crypto/AESUtils.java index 80050e8f..ad0b1705 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/crypto/AESUtils.java +++ b/mpush-tools/src/main/java/com/mpush/tools/crypto/AESUtils.java @@ -19,6 +19,7 @@ package com.mpush.tools.crypto; +import com.mpush.tools.common.Profiler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,15 +41,7 @@ public final class AESUtils { public static final String KEY_ALGORITHM = "AES"; public static final String KEY_ALGORITHM_PADDING = "AES/CBC/PKCS5Padding"; - /** - *

- * 生成密钥 - *

- * - * @param seed 密钥种子 - * @return - * @throws Exception - */ + public static SecretKey getSecretKey(byte[] seed) throws Exception { SecureRandom secureRandom = new SecureRandom(seed); KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); @@ -70,6 +63,7 @@ public static byte[] decrypt(byte[] data, byte[] decryptKey, byte[] iv) { public static byte[] encrypt(byte[] data, IvParameterSpec zeroIv, SecretKeySpec keySpec) { try { + Profiler.enter("time cost on [aes encrypt]: data length=" + data.length); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING); cipher.init(Cipher.ENCRYPT_MODE, keySpec, zeroIv); return cipher.doFinal(data); @@ -78,11 +72,14 @@ public static byte[] encrypt(byte[] data, IvParameterSpec zeroIv, SecretKeySpec Arrays.toString(zeroIv.getIV()), Arrays.toString(keySpec.getEncoded()), e); throw new CryptoException("AES encrypt ex", e); + } finally { + Profiler.release(); } } public static byte[] decrypt(byte[] data, IvParameterSpec zeroIv, SecretKeySpec keySpec) { try { + Profiler.enter("time cost on [aes decrypt]: data length=" + data.length); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_PADDING); cipher.init(Cipher.DECRYPT_MODE, keySpec, zeroIv); return cipher.doFinal(data); @@ -91,6 +88,8 @@ public static byte[] decrypt(byte[] data, IvParameterSpec zeroIv, SecretKeySpec Arrays.toString(zeroIv.getIV()), Arrays.toString(keySpec.getEncoded()), e); throw new CryptoException("AES decrypt ex", e); + } finally { + Profiler.release(); } } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/crypto/Base64.java b/mpush-tools/src/main/java/com/mpush/tools/crypto/Base64.java deleted file mode 100644 index 370c72f4..00000000 --- a/mpush-tools/src/main/java/com/mpush/tools/crypto/Base64.java +++ /dev/null @@ -1,970 +0,0 @@ -/* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. - * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ -package com.mpush.tools.crypto; - -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Objects; - -/** - * This class consists exclusively of static methods for obtaining - * encoders and decoders for the Base64 encoding scheme. The - * implementation of this class supports the following types of Base64 - * as specified in - * RFC 4648 and - * RFC 2045. - *

- *

    - *
  • Basic - *

    Uses "The Base64 Alphabet" as specified in Table 1 of - * RFC 4648 and RFC 2045 for encoding and decoding operation. - * The encoder does not add any line feed (line separator) - * character. The decoder rejects data that contains characters - * outside the base64 alphabet.

  • - *

    - *

  • URL and Filename safe - *

    Uses the "URL and Filename safe Base64 Alphabet" as specified - * in Table 2 of RFC 4648 for encoding and decoding. The - * encoder does not add any line feed (line separator) character. - * The decoder rejects data that contains characters outside the - * base64 alphabet.

  • - *

    - *

  • MIME - *

    Uses the "The Base64 Alphabet" as specified in Table 1 of - * RFC 2045 for encoding and decoding operation. The encoded output - * must be represented in lines of no more than 76 characters each - * and uses a carriage return {@code '\r'} followed immediately by - * a linefeed {@code '\n'} as the line separator. No line separator - * is added to the end of the encoded output. All line separators - * or other characters not found in the base64 alphabet table are - * ignored in decoding operation.

  • - *
- *

- *

Unless otherwise noted, passing a {@code null} argument to a - * method of this class will cause a {@link java.lang.NullPointerException - * NullPointerException} to be thrown. - * - * @author Xueming Shen - * @since 1.8 - */ - -public class Base64 { - - private Base64() { - } - - /** - * Returns a {@link Encoder} that encodes using the - * Basic type base64 encoding scheme. - * - * @return A Base64 encoder. - */ - public static Encoder getEncoder() { - return Encoder.RFC4648; - } - - /** - * Returns a {@link Encoder} that encodes using the - * URL and Filename safe type base64 - * encoding scheme. - * - * @return A Base64 encoder. - */ - public static Encoder getUrlEncoder() { - return Encoder.RFC4648_URLSAFE; - } - - /** - * Returns a {@link Encoder} that encodes using the - * MIME type base64 encoding scheme. - * - * @return A Base64 encoder. - */ - public static Encoder getMimeEncoder() { - return Encoder.RFC2045; - } - - /** - * Returns a {@link Encoder} that encodes using the - * MIME type base64 encoding scheme - * with specified line length and line separators. - * - * @param lineLength the length of each output line (rounded down to nearest multiple - * of 4). If {@code lineLength <= 0} the output will not be separated - * in lines - * @param lineSeparator the line separator for each output line - * @return A Base64 encoder. - * @throws IllegalArgumentException if {@code lineSeparator} includes any - * character of "The Base64 Alphabet" as specified in Table 1 of - * RFC 2045. - */ - public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) { - Objects.requireNonNull(lineSeparator); - int[] base64 = Decoder.fromBase64; - for (byte b : lineSeparator) { - if (base64[b & 0xff] != -1) - throw new IllegalArgumentException( - "Illegal base64 line separator character 0x" + Integer.toString(b, 16)); - } - if (lineLength <= 0) { - return Encoder.RFC4648; - } - return new Encoder(false, lineSeparator, lineLength >> 2 << 2, true); - } - - /** - * Returns a {@link Decoder} that decodes using the - * Basic type base64 encoding scheme. - * - * @return A Base64 decoder. - */ - public static Decoder getDecoder() { - return Decoder.RFC4648; - } - - /** - * Returns a {@link Decoder} that decodes using the - * URL and Filename safe type base64 - * encoding scheme. - * - * @return A Base64 decoder. - */ - public static Decoder getUrlDecoder() { - return Decoder.RFC4648_URLSAFE; - } - - /** - * Returns a {@link Decoder} that decodes using the - * MIME type base64 decoding scheme. - * - * @return A Base64 decoder. - */ - public static Decoder getMimeDecoder() { - return Decoder.RFC2045; - } - - /** - * This class implements an encoder for encoding byte data using - * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. - *

- *

Instances of {@link Encoder} class are safe for use by - * multiple concurrent threads. - *

- *

Unless otherwise noted, passing a {@code null} argument to - * a method of this class will cause a - * {@link java.lang.NullPointerException NullPointerException} to - * be thrown. - * - * @see Decoder - * @since 1.8 - */ - public static class Encoder { - - private final byte[] newline; - private final int linemax; - private final boolean isURL; - private final boolean doPadding; - - private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) { - this.isURL = isURL; - this.newline = newline; - this.linemax = linemax; - this.doPadding = doPadding; - } - - /** - * This array is a lookup table that translates 6-bit positive integer - * index values into their "Base64 Alphabet" equivalents as specified - * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). - */ - private static final char[] toBase64 = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' - }; - - /** - * It's the lookup table for "URL and Filename safe Base64" as specified - * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and - * '_'. This table is used when BASE64_URL is specified. - */ - private static final char[] toBase64URL = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' - }; - - private static final int MIMELINEMAX = 76; - private static final byte[] CRLF = new byte[]{'\r', '\n'}; - - static final Encoder RFC4648 = new Encoder(false, null, -1, true); - static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true); - static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true); - - private final int outLength(int srclen) { - int len = 0; - if (doPadding) { - len = 4 * ((srclen + 2) / 3); - } else { - int n = srclen % 3; - len = 4 * (srclen / 3) + (n == 0 ? 0 : n + 1); - } - if (linemax > 0) // line separators - len += (len - 1) / linemax * newline.length; - return len; - } - - /** - * Encodes all bytes from the specified byte array into a newly-allocated - * byte array using the {@link Base64} encoding scheme. The returned byte - * array is of the length of the resulting bytes. - * - * @param src the byte array to encode - * @return A newly-allocated byte array containing the resulting - * encoded bytes. - */ - public byte[] encode(byte[] src) { - int len = outLength(src.length); // dst array size - byte[] dst = new byte[len]; - int ret = encode0(src, 0, src.length, dst); - if (ret != dst.length) - return Arrays.copyOf(dst, ret); - return dst; - } - - /** - * Encodes all bytes from the specified byte array using the - * {@link Base64} encoding scheme, writing the resulting bytes to the - * given output byte array, starting at offset 0. - *

- *

It is the responsibility of the invoker of this method to make - * sure the output byte array {@code dst} has enough space for encoding - * all bytes from the input byte array. No bytes will be written to the - * output byte array if the output byte array is not big enough. - * - * @param src the byte array to encode - * @param dst the output byte array - * @return The number of bytes written to the output byte array - * @throws IllegalArgumentException if {@code dst} does not have enough - * space for encoding all input bytes. - */ - public int encode(byte[] src, byte[] dst) { - int len = outLength(src.length); // dst array size - if (dst.length < len) - throw new IllegalArgumentException( - "Output byte array is too small for encoding all input bytes"); - return encode0(src, 0, src.length, dst); - } - - /** - * Encodes the specified byte array into a String using the {@link Base64} - * encoding scheme. - *

- *

This method first encodes all input bytes into a base64 encoded - * byte array and then constructs a new String by using the encoded byte - * array and the {@link java.nio.charset.StandardCharsets#ISO_8859_1 - * ISO-8859-1} charset. - *

- *

In other words, an invocation of this method has exactly the same - * effect as invoking - * {@code new String(encode(src), StandardCharsets.ISO_8859_1)}. - * - * @param src the byte array to encode - * @return A String containing the resulting Base64 encoded characters - */ - @SuppressWarnings("deprecation") - public String encodeToString(byte[] src) { - byte[] encoded = encode(src); - return new String(encoded, 0, 0, encoded.length); - } - - /** - * Encodes all remaining bytes from the specified byte buffer into - * a newly-allocated ByteBuffer using the {@link Base64} encoding - * scheme. - *

- * Upon return, the source buffer's position will be updated to - * its limit; its limit will not have been changed. The returned - * output buffer's position will be zero and its limit will be the - * number of resulting encoded bytes. - * - * @param buffer the source ByteBuffer to encode - * @return A newly-allocated byte buffer containing the encoded bytes. - */ - public ByteBuffer encode(ByteBuffer buffer) { - int len = outLength(buffer.remaining()); - byte[] dst = new byte[len]; - int ret = 0; - if (buffer.hasArray()) { - ret = encode0(buffer.array(), - buffer.arrayOffset() + buffer.position(), - buffer.arrayOffset() + buffer.limit(), - dst); - buffer.position(buffer.limit()); - } else { - byte[] src = new byte[buffer.remaining()]; - buffer.get(src); - ret = encode0(src, 0, src.length, dst); - } - if (ret != dst.length) - dst = Arrays.copyOf(dst, ret); - return ByteBuffer.wrap(dst); - } - - /** - * Wraps an output stream for encoding byte data using the {@link Base64} - * encoding scheme. - *

- *

It is recommended to promptly close the returned output stream after - * use, during which it will flush all possible leftover bytes to the underlying - * output stream. Closing the returned output stream will close the underlying - * output stream. - * - * @param os the output stream. - * @return the output stream for encoding the byte data into the - * specified Base64 encoded format - */ - public OutputStream wrap(OutputStream os) { - Objects.requireNonNull(os); - return new EncOutputStream(os, isURL ? toBase64URL : toBase64, - newline, linemax, doPadding); - } - - /** - * Returns an encoder instance that encodes equivalently to this one, - * but without adding any padding character at the end of the encoded - * byte data. - *

- *

The encoding scheme of this encoder instance is unaffected by - * this invocation. The returned encoder instance should be used for - * non-padding encoding operation. - * - * @return an equivalent encoder that encodes without adding any - * padding character at the end - */ - public Encoder withoutPadding() { - if (!doPadding) - return this; - return new Encoder(isURL, newline, linemax, false); - } - - private int encode0(byte[] src, int off, int end, byte[] dst) { - char[] base64 = isURL ? toBase64URL : toBase64; - int sp = off; - int slen = (end - off) / 3 * 3; - int sl = off + slen; - if (linemax > 0 && slen > linemax / 4 * 3) - slen = linemax / 4 * 3; - int dp = 0; - while (sp < sl) { - int sl0 = Math.min(sp + slen, sl); - for (int sp0 = sp, dp0 = dp; sp0 < sl0; ) { - int bits = (src[sp0++] & 0xff) << 16 | - (src[sp0++] & 0xff) << 8 | - (src[sp0++] & 0xff); - dst[dp0++] = (byte) base64[(bits >>> 18) & 0x3f]; - dst[dp0++] = (byte) base64[(bits >>> 12) & 0x3f]; - dst[dp0++] = (byte) base64[(bits >>> 6) & 0x3f]; - dst[dp0++] = (byte) base64[bits & 0x3f]; - } - int dlen = (sl0 - sp) / 3 * 4; - dp += dlen; - sp = sl0; - if (dlen == linemax && sp < end) { - for (byte b : newline) { - dst[dp++] = b; - } - } - } - if (sp < end) { // 1 or 2 leftover bytes - int b0 = src[sp++] & 0xff; - dst[dp++] = (byte) base64[b0 >> 2]; - if (sp == end) { - dst[dp++] = (byte) base64[(b0 << 4) & 0x3f]; - if (doPadding) { - dst[dp++] = '='; - dst[dp++] = '='; - } - } else { - int b1 = src[sp++] & 0xff; - dst[dp++] = (byte) base64[(b0 << 4) & 0x3f | (b1 >> 4)]; - dst[dp++] = (byte) base64[(b1 << 2) & 0x3f]; - if (doPadding) { - dst[dp++] = '='; - } - } - } - return dp; - } - } - - /** - * This class implements a decoder for decoding byte data using the - * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. - *

- *

The Base64 padding character {@code '='} is accepted and - * interpreted as the end of the encoded byte data, but is not - * required. So if the final unit of the encoded byte data only has - * two or three Base64 characters (without the corresponding padding - * character(s) padded), they are decoded as if followed by padding - * character(s). If there is a padding character present in the - * final unit, the correct number of padding character(s) must be - * present, otherwise {@code IllegalArgumentException} ( - * {@code IOException} when reading from a Base64 stream) is thrown - * during decoding. - *

- *

Instances of {@link Decoder} class are safe for use by - * multiple concurrent threads. - *

- *

Unless otherwise noted, passing a {@code null} argument to - * a method of this class will cause a - * {@link java.lang.NullPointerException NullPointerException} to - * be thrown. - * - * @see Encoder - * @since 1.8 - */ - public static class Decoder { - - private final boolean isURL; - private final boolean isMIME; - - private Decoder(boolean isURL, boolean isMIME) { - this.isURL = isURL; - this.isMIME = isMIME; - } - - /** - * Lookup table for decoding unicode characters drawn from the - * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into - * their 6-bit positive integer equivalents. Characters that - * are not in the Base64 alphabet but fall within the bounds of - * the array are encoded to -1. - */ - private static final int[] fromBase64 = new int[256]; - - static { - Arrays.fill(fromBase64, -1); - for (int i = 0; i < Encoder.toBase64.length; i++) - fromBase64[Encoder.toBase64[i]] = i; - fromBase64['='] = -2; - } - - /** - * Lookup table for decoding "URL and Filename safe Base64 Alphabet" - * as specified in Table2 of the RFC 4648. - */ - private static final int[] fromBase64URL = new int[256]; - - static { - Arrays.fill(fromBase64URL, -1); - for (int i = 0; i < Encoder.toBase64URL.length; i++) - fromBase64URL[Encoder.toBase64URL[i]] = i; - fromBase64URL['='] = -2; - } - - static final Decoder RFC4648 = new Decoder(false, false); - static final Decoder RFC4648_URLSAFE = new Decoder(true, false); - static final Decoder RFC2045 = new Decoder(false, true); - - /** - * Decodes all bytes from the input byte array using the {@link Base64} - * encoding scheme, writing the results into a newly-allocated output - * byte array. The returned byte array is of the length of the resulting - * bytes. - * - * @param src the byte array to decode - * @return A newly-allocated byte array containing the decoded bytes. - * @throws IllegalArgumentException if {@code src} is not in valid Base64 scheme - */ - public byte[] decode(byte[] src) { - byte[] dst = new byte[outLength(src, 0, src.length)]; - int ret = decode0(src, 0, src.length, dst); - if (ret != dst.length) { - dst = Arrays.copyOf(dst, ret); - } - return dst; - } - - /** - * Decodes a Base64 encoded String into a newly-allocated byte array - * using the {@link Base64} encoding scheme. - *

- *

An invocation of this method has exactly the same effect as invoking - * {@code decode(src.getBytes(StandardCharsets.ISO_8859_1))} - * - * @param src the string to decode - * @return A newly-allocated byte array containing the decoded bytes. - * @throws IllegalArgumentException if {@code src} is not in valid Base64 scheme - */ - public byte[] decode(String src) { - return decode(src.getBytes(StandardCharsets.ISO_8859_1)); - } - - /** - * Decodes all bytes from the input byte array using the {@link Base64} - * encoding scheme, writing the results into the given output byte array, - * starting at offset 0. - *

- *

It is the responsibility of the invoker of this method to make - * sure the output byte array {@code dst} has enough space for decoding - * all bytes from the input byte array. No bytes will be be written to - * the output byte array if the output byte array is not big enough. - *

- *

If the input byte array is not in valid Base64 encoding scheme - * then some bytes may have been written to the output byte array before - * IllegalargumentException is thrown. - * - * @param src the byte array to decode - * @param dst the output byte array - * @return The number of bytes written to the output byte array - * @throws IllegalArgumentException if {@code src} is not in valid Base64 scheme, or {@code dst} - * does not have enough space for decoding all input bytes. - */ - public int decode(byte[] src, byte[] dst) { - int len = outLength(src, 0, src.length); - if (dst.length < len) - throw new IllegalArgumentException( - "Output byte array is too small for decoding all input bytes"); - return decode0(src, 0, src.length, dst); - } - - /** - * Decodes all bytes from the input byte buffer using the {@link Base64} - * encoding scheme, writing the results into a newly-allocated ByteBuffer. - *

- *

Upon return, the source buffer's position will be updated to - * its limit; its limit will not have been changed. The returned - * output buffer's position will be zero and its limit will be the - * number of resulting decoded bytes - *

- *

{@code IllegalArgumentException} is thrown if the input buffer - * is not in valid Base64 encoding scheme. The position of the input - * buffer will not be advanced in this case. - * - * @param buffer the ByteBuffer to decode - * @return A newly-allocated byte buffer containing the decoded bytes - * @throws IllegalArgumentException if {@code src} is not in valid Base64 scheme. - */ - public ByteBuffer decode(ByteBuffer buffer) { - int pos0 = buffer.position(); - try { - byte[] src; - int sp, sl; - if (buffer.hasArray()) { - src = buffer.array(); - sp = buffer.arrayOffset() + buffer.position(); - sl = buffer.arrayOffset() + buffer.limit(); - buffer.position(buffer.limit()); - } else { - src = new byte[buffer.remaining()]; - buffer.get(src); - sp = 0; - sl = src.length; - } - byte[] dst = new byte[outLength(src, sp, sl)]; - return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst)); - } catch (IllegalArgumentException iae) { - buffer.position(pos0); - throw iae; - } - } - - /** - * Returns an input stream for decoding {@link Base64} encoded byte stream. - *

- *

The {@code read} methods of the returned {@code InputStream} will - * throw {@code IOException} when reading bytes that cannot be decoded. - *

- *

Closing the returned input stream will close the underlying - * input stream. - * - * @param is the input stream - * @return the input stream for decoding the specified Base64 encoded - * byte stream - */ - public InputStream wrap(InputStream is) { - Objects.requireNonNull(is); - return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME); - } - - private int outLength(byte[] src, int sp, int sl) { - int[] base64 = isURL ? fromBase64URL : fromBase64; - int paddings = 0; - int len = sl - sp; - if (len == 0) - return 0; - if (len < 2) { - if (isMIME && base64[0] == -1) - return 0; - throw new IllegalArgumentException( - "Input byte[] should at least have 2 bytes for base64 bytes"); - } - if (isMIME) { - // scan all bytes to fill out all non-alphabet. a performance - // trade-off of pre-scan or Arrays.copyOf - int n = 0; - while (sp < sl) { - int b = src[sp++] & 0xff; - if (b == '=') { - len -= (sl - sp + 1); - break; - } - if ((b = base64[b]) == -1) - n++; - } - len -= n; - } else { - if (src[sl - 1] == '=') { - paddings++; - if (src[sl - 2] == '=') - paddings++; - } - } - if (paddings == 0 && (len & 0x3) != 0) - paddings = 4 - (len & 0x3); - return 3 * ((len + 3) / 4) - paddings; - } - - private int decode0(byte[] src, int sp, int sl, byte[] dst) { - int[] base64 = isURL ? fromBase64URL : fromBase64; - int dp = 0; - int bits = 0; - int shiftto = 18; // pos of first byte of 4-byte atom - while (sp < sl) { - int b = src[sp++] & 0xff; - if ((b = base64[b]) < 0) { - if (b == -2) { // padding byte '=' - // = shiftto==18 unnecessary padding - // x= shiftto==12 a dangling single x - // x to be handled together with non-padding case - // xx= shiftto==6&&sp==sl missing last = - // xx=y shiftto==6 last is not = - if (shiftto == 6 && (sp == sl || src[sp++] != '=') || - shiftto == 18) { - throw new IllegalArgumentException( - "Input byte array has wrong 4-byte ending unit"); - } - break; - } - if (isMIME) // skip if for rfc2045 - continue; - else - throw new IllegalArgumentException( - "Illegal base64 character " + - Integer.toString(src[sp - 1], 16)); - } - bits |= (b << shiftto); - shiftto -= 6; - if (shiftto < 0) { - dst[dp++] = (byte) (bits >> 16); - dst[dp++] = (byte) (bits >> 8); - dst[dp++] = (byte) (bits); - shiftto = 18; - bits = 0; - } - } - // reached end of byte array or hit padding '=' characters. - if (shiftto == 6) { - dst[dp++] = (byte) (bits >> 16); - } else if (shiftto == 0) { - dst[dp++] = (byte) (bits >> 16); - dst[dp++] = (byte) (bits >> 8); - } else if (shiftto == 12) { - // dangling single "x", incorrectly encoded. - throw new IllegalArgumentException( - "Last unit does not have enough valid bits"); - } - // anything left is invalid, if is not MIME. - // if MIME, ignore all non-base64 character - while (sp < sl) { - if (isMIME && base64[src[sp++]] < 0) - continue; - throw new IllegalArgumentException( - "Input byte array has incorrect ending byte at " + sp); - } - return dp; - } - } - - /* - * An output stream for encoding bytes into the Base64. - */ - private static class EncOutputStream extends FilterOutputStream { - - private int leftover = 0; - private int b0, b1, b2; - private boolean closed = false; - - private final char[] base64; // byte->base64 mapping - private final byte[] newline; // line separator, if needed - private final int linemax; - private final boolean doPadding;// whether or not to pad - private int linepos = 0; - - EncOutputStream(OutputStream os, char[] base64, - byte[] newline, int linemax, boolean doPadding) { - super(os); - this.base64 = base64; - this.newline = newline; - this.linemax = linemax; - this.doPadding = doPadding; - } - - @Override - public void write(int b) throws IOException { - byte[] buf = new byte[1]; - buf[0] = (byte) (b & 0xff); - write(buf, 0, 1); - } - - private void checkNewline() throws IOException { - if (linepos == linemax) { - out.write(newline); - linepos = 0; - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - if (closed) - throw new IOException("Stream is closed"); - if (off < 0 || len < 0 || off + len > b.length) - throw new ArrayIndexOutOfBoundsException(); - if (len == 0) - return; - if (leftover != 0) { - if (leftover == 1) { - b1 = b[off++] & 0xff; - len--; - if (len == 0) { - leftover++; - return; - } - } - b2 = b[off++] & 0xff; - len--; - checkNewline(); - out.write(base64[b0 >> 2]); - out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); - out.write(base64[(b1 << 2) & 0x3f | (b2 >> 6)]); - out.write(base64[b2 & 0x3f]); - linepos += 4; - } - int nBits24 = len / 3; - leftover = len - (nBits24 * 3); - while (nBits24-- > 0) { - checkNewline(); - int bits = (b[off++] & 0xff) << 16 | - (b[off++] & 0xff) << 8 | - (b[off++] & 0xff); - out.write(base64[(bits >>> 18) & 0x3f]); - out.write(base64[(bits >>> 12) & 0x3f]); - out.write(base64[(bits >>> 6) & 0x3f]); - out.write(base64[bits & 0x3f]); - linepos += 4; - } - if (leftover == 1) { - b0 = b[off++] & 0xff; - } else if (leftover == 2) { - b0 = b[off++] & 0xff; - b1 = b[off++] & 0xff; - } - } - - @Override - public void close() throws IOException { - if (!closed) { - closed = true; - if (leftover == 1) { - checkNewline(); - out.write(base64[b0 >> 2]); - out.write(base64[(b0 << 4) & 0x3f]); - if (doPadding) { - out.write('='); - out.write('='); - } - } else if (leftover == 2) { - checkNewline(); - out.write(base64[b0 >> 2]); - out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]); - out.write(base64[(b1 << 2) & 0x3f]); - if (doPadding) { - out.write('='); - } - } - leftover = 0; - out.close(); - } - } - } - - /* - * An input stream for decoding Base64 bytes - */ - private static class DecInputStream extends InputStream { - - private final InputStream is; - private final boolean isMIME; - private final int[] base64; // base64 -> byte mapping - private int bits = 0; // 24-bit buffer for decoding - private int nextin = 18; // next available "off" in "bits" for input; - // -> 18, 12, 6, 0 - private int nextout = -8; // next available "off" in "bits" for output; - // -> 8, 0, -8 (no byte for output) - private boolean eof = false; - private boolean closed = false; - - DecInputStream(InputStream is, int[] base64, boolean isMIME) { - this.is = is; - this.base64 = base64; - this.isMIME = isMIME; - } - - private byte[] sbBuf = new byte[1]; - - @Override - public int read() throws IOException { - return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (closed) - throw new IOException("Stream is closed"); - if (eof && nextout < 0) // eof and no leftover - return -1; - if (off < 0 || len < 0 || len > b.length - off) - throw new IndexOutOfBoundsException(); - int oldOff = off; - if (nextout >= 0) { // leftover output byte(s) in bits buf - do { - if (len == 0) - return off - oldOff; - b[off++] = (byte) (bits >> nextout); - len--; - nextout -= 8; - } while (nextout >= 0); - bits = 0; - } - while (len > 0) { - int v = is.read(); - if (v == -1) { - eof = true; - if (nextin != 18) { - if (nextin == 12) - throw new IOException("Base64 stream has one un-decoded dangling byte."); - // treat ending xx/xxx without padding character legal. - // same logic as v == '=' below - b[off++] = (byte) (bits >> (16)); - len--; - if (nextin == 0) { // only one padding byte - if (len == 0) { // no enough output space - bits >>= 8; // shift to lowest byte - nextout = 0; - } else { - b[off++] = (byte) (bits >> 8); - } - } - } - if (off == oldOff) - return -1; - else - return off - oldOff; - } - if (v == '=') { // padding byte(s) - // = shiftto==18 unnecessary padding - // x= shiftto==12 dangling x, invalid unit - // xx= shiftto==6 && missing last '=' - // xx=y or last is not '=' - if (nextin == 18 || nextin == 12 || - nextin == 6 && is.read() != '=') { - throw new IOException("Illegal base64 ending sequence:" + nextin); - } - b[off++] = (byte) (bits >> (16)); - len--; - if (nextin == 0) { // only one padding byte - if (len == 0) { // no enough output space - bits >>= 8; // shift to lowest byte - nextout = 0; - } else { - b[off++] = (byte) (bits >> 8); - } - } - eof = true; - break; - } - if ((v = base64[v]) == -1) { - if (isMIME) // skip if for rfc2045 - continue; - else - throw new IOException("Illegal base64 character " + - Integer.toString(v, 16)); - } - bits |= (v << nextin); - if (nextin == 0) { - nextin = 18; // clear for next - nextout = 16; - while (nextout >= 0) { - b[off++] = (byte) (bits >> nextout); - len--; - nextout -= 8; - if (len == 0 && nextout >= 0) { // don't clean "bits" - return off - oldOff; - } - } - bits = 0; - } else { - nextin -= 6; - } - } - return off - oldOff; - } - - @Override - public int available() throws IOException { - if (closed) - throw new IOException("Stream is closed"); - return is.available(); // TBD: - } - - @Override - public void close() throws IOException { - if (!closed) { - closed = true; - is.close(); - } - } - } -} diff --git a/mpush-tools/src/main/java/com/mpush/tools/crypto/Base64Utils.java b/mpush-tools/src/main/java/com/mpush/tools/crypto/Base64Utils.java index 3c2f1393..13116c9f 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/crypto/Base64Utils.java +++ b/mpush-tools/src/main/java/com/mpush/tools/crypto/Base64Utils.java @@ -22,6 +22,8 @@ import com.mpush.api.Constants; +import java.util.Base64; + public class Base64Utils { /** @@ -29,11 +31,10 @@ public class Base64Utils { * BASE64字符串解码为二进制数据 *

* - * @param base64 - * @return - * @throws Exception + * @param base64 base64 + * @return 源二进制数据 */ - public static byte[] decode(String base64) throws Exception { + public static byte[] decode(String base64) { return Base64.getDecoder().decode(base64.getBytes(Constants.UTF_8)); } @@ -42,11 +43,10 @@ public static byte[] decode(String base64) throws Exception { * 二进制数据编码为BASE64字符串 *

* - * @param bytes - * @return - * @throws Exception + * @param bytes base64 + * @return BASE64后的二进制数据 */ - public static String encode(byte[] bytes) throws Exception { + public static String encode(byte[] bytes) { return new String(Base64.getEncoder().encode(bytes), Constants.UTF_8); } diff --git a/mpush-tools/src/main/java/com/mpush/tools/crypto/MD5Utils.java b/mpush-tools/src/main/java/com/mpush/tools/crypto/MD5Utils.java index 536f9ba8..08932041 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/crypto/MD5Utils.java +++ b/mpush-tools/src/main/java/com/mpush/tools/crypto/MD5Utils.java @@ -86,14 +86,6 @@ private static String toHex(byte[] bytes) { return buffer.toString(); } - - /** - * HmacSHA1 加密 - * - * @param data - * @param encryptKey - * @return - */ public static String hmacSha1(String data, String encryptKey) { final String HMAC_SHA1 = "HmacSHA1"; SecretKeySpec signingKey = new SecretKeySpec(encryptKey.getBytes(Constants.UTF_8), HMAC_SHA1); @@ -107,12 +99,7 @@ public static String hmacSha1(String data, String encryptKey) { } } - /** - * HmacSHA1 加密 - * - * @param data - * @return - */ + public static String sha1(String data) { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); diff --git a/mpush-tools/src/main/java/com/mpush/tools/crypto/RSAUtils.java b/mpush-tools/src/main/java/com/mpush/tools/crypto/RSAUtils.java index b1edd6db..5be3db38 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/crypto/RSAUtils.java +++ b/mpush-tools/src/main/java/com/mpush/tools/crypto/RSAUtils.java @@ -39,9 +39,8 @@ /** * RSA公钥/私钥/签名工具包 - *

- * 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式
- * 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密,
+ * 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式 + * 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密 * 非对称加密算法可以用来对对称加密的密钥加密,这样保证密钥的安全也就保证了数据的安全 */ public final class RSAUtils { @@ -50,7 +49,7 @@ public final class RSAUtils { /** * 密钥位数 */ - private static final int RAS_KEY_SIZE = 1024; + public static final int RAS_KEY_SIZE = 1024; /** * 加密算法RSA @@ -81,12 +80,14 @@ public final class RSAUtils { /** * 生成公钥和私钥 * - * @throws NoSuchAlgorithmException + * @param rsaKeySize key size + * + * @return 公钥和私钥 */ - public static Pair genKeyPair() { + public static Pair genKeyPair(int rsaKeySize) { try { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM); - keyPairGen.initialize(RAS_KEY_SIZE); + keyPairGen.initialize(rsaKeySize); KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); @@ -100,9 +101,9 @@ public static Pair genKeyPair() { /** * 编码密钥,便于存储 * - * @param key - * @return - * @throws Exception + * @param key 密钥 + * @return base64后的字符串 + * @throws Exception Exception */ public static String encodeBase64(Key key) throws Exception { return Base64Utils.encode(key.getEncoded()); @@ -111,9 +112,9 @@ public static String encodeBase64(Key key) throws Exception { /** * 从字符串解码私钥 * - * @param key - * @return - * @throws Exception + * @param key 密钥 + * @return base64后的字符串 + * @throws Exception Exception */ public static PrivateKey decodePrivateKey(String key) throws Exception { byte[] keyBytes = Base64Utils.decode(key); @@ -125,9 +126,9 @@ public static PrivateKey decodePrivateKey(String key) throws Exception { /** * 从字符串解码公钥 * - * @param publicKey - * @return - * @throws Exception + * @param publicKey 公钥 + * @return 公钥 + * @throws Exception Exception */ public static PublicKey decodePublicKey(String publicKey) throws Exception { byte[] keyBytes = Base64Utils.decode(publicKey); @@ -142,8 +143,8 @@ public static PublicKey decodePublicKey(String publicKey) throws Exception { * * @param data 已加密数据 * @param privateKey 私钥(BASE64编码) - * @return - * @throws Exception + * @return 私钥 + * @throws Exception Exception */ public static String sign(byte[] data, String privateKey) throws Exception { Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); @@ -158,8 +159,8 @@ public static String sign(byte[] data, String privateKey) throws Exception { * @param data 已加密数据 * @param publicKey 公钥(BASE64编码) * @param sign 数字签名 - * @return - * @throws Exception + * @return 是否通过校验 + * @throws Exception Exception */ public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); @@ -177,7 +178,7 @@ public static boolean verify(byte[] data, String publicKey, String sign) throws * * @param modulus 模 * @param exponent 指数 - * @return + * @return 公钥 */ public static RSAPublicKey getPublicKey(String modulus, String exponent) { try { @@ -200,7 +201,7 @@ public static RSAPublicKey getPublicKey(String modulus, String exponent) { * * @param modulus 模 * @param exponent 指数 - * @return + * @return 私钥 */ public static RSAPrivateKey getPrivateKey(String modulus, String exponent) { try { @@ -218,10 +219,9 @@ public static RSAPrivateKey getPrivateKey(String modulus, String exponent) { /** * 公钥加密 * - * @param data - * @param publicKey - * @return - * @throws Exception + * @param data 待加密数据 + * @param publicKey 公钥 + * @return 加密后的值 */ public static byte[] encryptByPublicKey(byte[] data, RSAPublicKey publicKey) { try { @@ -241,10 +241,9 @@ public static byte[] encryptByPublicKey(byte[] data, RSAPublicKey publicKey) { /** * 私钥解密 * - * @param data - * @param privateKey - * @return - * @throws Exception + * @param data 待加密数据 + * @param privateKey 私钥 + * @return 解密后的值 */ public static byte[] decryptByPrivateKey(byte[] data, RSAPrivateKey privateKey) { try { @@ -265,20 +264,20 @@ public static byte[] decryptByPrivateKey(byte[] data, RSAPrivateKey privateKey) * 解密要求密文最大长度为128字节, * 所以在加密和解密的过程中需要分块进行。 * - * @param cipher - * @param data - * @return + * @param cipher 密钥 + * @param data 待处理的数据 + * @return 处理后的值 * @throws BadPaddingException * @throws IllegalBlockSizeException */ private static byte[] doFinal(Cipher cipher, byte[] data, int key_len) throws BadPaddingException, IllegalBlockSizeException { - int inputLen = data.length, offSet = 0; + int inputLen = data.length, offset = 0; byte[] tmp; ByteArrayOutputStream out = new ByteArrayOutputStream(getTmpArrayLength(inputLen)); while (inputLen > 0) { - tmp = cipher.doFinal(data, offSet, Math.min(key_len, inputLen)); + tmp = cipher.doFinal(data, offset, Math.min(key_len, inputLen)); out.write(tmp, 0, tmp.length); - offSet += key_len; + offset += key_len; inputLen -= key_len; } return out.toByteArray(); @@ -295,8 +294,8 @@ private static int getTmpArrayLength(int L) { * * @param data 已加密数据 * @param privateKey 私钥(BASE64编码) - * @return - * @throws Exception + * @return 解密后的值 + * @throws Exception Exception */ public static byte[] decryptByPrivateKey(byte[] data, String privateKey) throws Exception { PrivateKey key = decodePrivateKey(privateKey); @@ -310,8 +309,8 @@ public static byte[] decryptByPrivateKey(byte[] data, String privateKey) throws * * @param data 已加密数据 * @param publicKey 公钥(BASE64编码) - * @return - * @throws Exception + * @return 解密后的值 + * @throws Exception Exception */ public static byte[] decryptByPublicKey(byte[] data, String publicKey) throws Exception { PublicKey key = decodePublicKey(publicKey); @@ -325,8 +324,8 @@ public static byte[] decryptByPublicKey(byte[] data, String publicKey) throws Ex * * @param data 源数据 * @param publicKey 公钥(BASE64编码) - * @return - * @throws Exception + * @return 加密后的值 + * @throws Exception Exception */ public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception { PublicKey key = decodePublicKey(publicKey); @@ -341,8 +340,8 @@ public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Ex * * @param data 源数据 * @param privateKey 私钥(BASE64编码) - * @return - * @throws Exception + * @return 加密后的值 + * @throws Exception Exception */ public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception { PrivateKey key = decodePrivateKey(privateKey); @@ -351,8 +350,8 @@ public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws return doFinal(cipher, data, MAX_ENCRYPT_BLOCK); } - public static void main(String[] args) throws Exception { - Pair pair = RSAUtils.genKeyPair(); + private static void test() { + Pair pair = RSAUtils.genKeyPair(RAS_KEY_SIZE); //生成公钥和私钥 RSAPublicKey publicKey = pair.key; RSAPrivateKey privateKey = pair.value; @@ -370,8 +369,6 @@ public static void main(String[] args) throws Exception { RSAPublicKey pubKey = RSAUtils.getPublicKey(modulus, public_exponent); System.out.println("privateKey=" + priKey); System.out.println("publicKey=" + pubKey); - System.out.println("privateKey=" + priKey); - System.out.println("publicKey=" + pubKey); //加密后的密文 byte[] mi = RSAUtils.encryptByPublicKey(ming, pubKey); System.out.println("密文:" + new String(mi, Constants.UTF_8)); @@ -379,4 +376,30 @@ public static void main(String[] args) throws Exception { ming = RSAUtils.decryptByPrivateKey(mi, priKey); System.out.println("解密:" + new String(ming, Constants.UTF_8)); } + + public static void main(String[] args) throws Exception { + int keySize = RAS_KEY_SIZE; + if (args.length > 0) keySize = Integer.parseInt(args[0]); + if (keySize < RAS_KEY_SIZE) keySize = RAS_KEY_SIZE; + Pair pair = RSAUtils.genKeyPair(keySize); + //生成公钥和私钥 + RSAPublicKey publicKey = pair.key; + RSAPrivateKey privateKey = pair.value; + + System.out.println("key generate success!"); + + System.out.println("privateKey=" + RSAUtils.encodeBase64(privateKey)); + System.out.println("publicKey=" + RSAUtils.encodeBase64(publicKey)); + + //明文 + byte[] ming = "这是一段测试文字。。。。".getBytes(Constants.UTF_8); + System.out.println("明文:" + new String(ming, Constants.UTF_8)); + + //加密后的密文 + byte[] mi = RSAUtils.encryptByPublicKey(ming, publicKey); + System.out.println("密文:" + new String(mi, Constants.UTF_8)); + //解密后的明文 + ming = RSAUtils.decryptByPrivateKey(mi, privateKey); + System.out.println("解密:" + new String(ming, Constants.UTF_8)); + } } \ No newline at end of file diff --git a/mpush-tools/src/main/java/com/mpush/tools/event/EventBus.java b/mpush-tools/src/main/java/com/mpush/tools/event/EventBus.java index f547073d..16557732 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/event/EventBus.java +++ b/mpush-tools/src/main/java/com/mpush/tools/event/EventBus.java @@ -20,10 +20,7 @@ package com.mpush.tools.event; import com.google.common.eventbus.AsyncEventBus; -import com.google.common.eventbus.SubscriberExceptionContext; -import com.google.common.eventbus.SubscriberExceptionHandler; import com.mpush.api.event.Event; -import com.mpush.tools.thread.pool.ThreadPoolManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,29 +32,23 @@ * @author ohun@live.cn */ public class EventBus { - private final Logger LOGGER = LoggerFactory.getLogger(EventBus.class); - public static final EventBus I = new EventBus(); - private final com.google.common.eventbus.EventBus eventBus; + private static final Logger LOGGER = LoggerFactory.getLogger(EventBus.class); + private static com.google.common.eventbus.EventBus eventBus; - public EventBus() { - Executor executor = ThreadPoolManager.I.getEventBusExecutor(); - eventBus = new AsyncEventBus(executor, new SubscriberExceptionHandler() { - @Override - public void handleException(Throwable exception, SubscriberExceptionContext context) { - LOGGER.error("event bus subscriber ex", exception); - } - }); + public static void create(Executor executor) { + eventBus = new AsyncEventBus(executor, (exception, context) + -> LOGGER.error("event bus subscriber ex", exception)); } - public void post(Event event) { + public static void post(Event event) { eventBus.post(event); } - public void register(Object bean) { + public static void register(Object bean) { eventBus.register(bean); } - public void unregister(Object bean) { + public static void unregister(Object bean) { eventBus.unregister(bean); } diff --git a/mpush-tools/src/main/java/com/mpush/tools/event/EventConsumer.java b/mpush-tools/src/main/java/com/mpush/tools/event/EventConsumer.java index c7e7ccab..2776a8a7 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/event/EventConsumer.java +++ b/mpush-tools/src/main/java/com/mpush/tools/event/EventConsumer.java @@ -22,7 +22,7 @@ public abstract class EventConsumer { public EventConsumer() { - EventBus.I.register(this); + EventBus.register(this); } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/log/Logs.java b/mpush-tools/src/main/java/com/mpush/tools/log/Logs.java index 44fb5e5e..043fc9a5 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/log/Logs.java +++ b/mpush-tools/src/main/java/com/mpush/tools/log/Logs.java @@ -24,6 +24,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static ch.qos.logback.classic.util.ContextInitializer.CONFIG_FILE_PROPERTY; + /** * Created by ohun on 2016/5/16. * @@ -36,6 +38,7 @@ static boolean init() { if (logInit) return true; System.setProperty("log.home", CC.mp.log_dir); System.setProperty("log.root.level", CC.mp.log_level); + System.setProperty(CONFIG_FILE_PROPERTY, CC.mp.log_conf_path); LoggerFactory .getLogger("console") .info(CC.mp.cfg.root().render(ConfigRenderOptions.concise().setFormatted(true))); @@ -44,17 +47,19 @@ static boolean init() { Logger Console = LoggerFactory.getLogger("console"), - Conn = LoggerFactory.getLogger("mpush.conn.log"), + CONN = LoggerFactory.getLogger("mpush.conn.log"), - Monitor = LoggerFactory.getLogger("mpush.monitor.log"), + MONITOR = LoggerFactory.getLogger("mpush.monitor.log"), PUSH = LoggerFactory.getLogger("mpush.push.log"), HB = LoggerFactory.getLogger("mpush.heartbeat.log"), - REDIS = LoggerFactory.getLogger("mpush.redis.log"), + CACHE = LoggerFactory.getLogger("mpush.cache.log"), + + RSD = LoggerFactory.getLogger("mpush.srd.log"), - ZK = LoggerFactory.getLogger("mpush.zk.log"), + HTTP = LoggerFactory.getLogger("mpush.http.log"), - HTTP = LoggerFactory.getLogger("mpush.http.log"); + PROFILE = LoggerFactory.getLogger("mpush.profile.log"); } diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/PoolThreadFactory.java b/mpush-tools/src/main/java/com/mpush/tools/thread/NamedPoolThreadFactory.java similarity index 81% rename from mpush-tools/src/main/java/com/mpush/tools/thread/PoolThreadFactory.java rename to mpush-tools/src/main/java/com/mpush/tools/thread/NamedPoolThreadFactory.java index adb945ba..ef0c92a0 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/PoolThreadFactory.java +++ b/mpush-tools/src/main/java/com/mpush/tools/thread/NamedPoolThreadFactory.java @@ -22,7 +22,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; -public class PoolThreadFactory implements ThreadFactory { +public final class NamedPoolThreadFactory implements ThreadFactory { private static final AtomicInteger poolNum = new AtomicInteger(1); private final AtomicInteger threadNum = new AtomicInteger(1); @@ -31,11 +31,11 @@ public class PoolThreadFactory implements ThreadFactory { private final String namePre; private final boolean isDaemon; - public PoolThreadFactory(String prefix) { + public NamedPoolThreadFactory(String prefix) { this(prefix, true); } - public PoolThreadFactory(String prefix, boolean daemon) { + public NamedPoolThreadFactory(String prefix, boolean daemon) { SecurityManager manager = System.getSecurityManager(); if (manager != null) { group = manager.getThreadGroup(); @@ -43,7 +43,7 @@ public PoolThreadFactory(String prefix, boolean daemon) { group = Thread.currentThread().getThreadGroup(); } isDaemon = daemon; - namePre = prefix + "p-" + poolNum.getAndIncrement() + "-t-"; + namePre = prefix + "-p-" + poolNum.getAndIncrement() + "-t-"; } /** @@ -52,8 +52,8 @@ public PoolThreadFactory(String prefix, boolean daemon) { @Override public Thread newThread(Runnable runnable) { Thread t = new Thread(group, runnable, namePre + threadNum.getAndIncrement(), 0); - t.setContextClassLoader(PoolThreadFactory.class.getClassLoader()); - t.setPriority(Thread.MAX_PRIORITY); + t.setContextClassLoader(NamedPoolThreadFactory.class.getClassLoader()); + t.setPriority(Thread.NORM_PRIORITY); t.setDaemon(isDaemon); return t; } diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/NamedThreadFactory.java b/mpush-tools/src/main/java/com/mpush/tools/thread/NamedThreadFactory.java index 4e4f407e..866d7731 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/NamedThreadFactory.java +++ b/mpush-tools/src/main/java/com/mpush/tools/thread/NamedThreadFactory.java @@ -26,11 +26,13 @@ /** * Created by xiaoxu.yxx on 2015/7/19. + * + * @author ohun@live.cn (夜色) */ public final class NamedThreadFactory implements ThreadFactory { - protected final AtomicInteger threadNumber = new AtomicInteger(1); - protected final String namePrefix; - protected final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + private final ThreadGroup group; public NamedThreadFactory() { @@ -42,15 +44,38 @@ public NamedThreadFactory(final String namePrefix) { this.group = Thread.currentThread().getThreadGroup(); } + /** + * Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的, + * 唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。 + * 守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ; + *

+ * 但是有几点需要注意: + * 1)、thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 + *

+ * 2)、 在Daemon线程中产生的新线程也是Daemon的。 + *

+ * 3)、不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。 + * + * @param name name + * @param r runnable + * @return new Thread + */ public Thread newThread(String name, Runnable r) { - return new Thread(group, r, name); + Thread thread = new Thread(group, r, namePrefix + "-" + threadNumber.getAndIncrement() + "-" + name); + thread.setDaemon(true); //设置为非守护线程,否则jvm会立即退出 + return thread; } @Override public Thread newThread(Runnable r) { - Thread t = newThread(namePrefix + threadNumber.getAndIncrement(), r); - if (t.isDaemon()) - t.setDaemon(false); - return t; + return newThread("none", r); + } + + public static NamedThreadFactory build() { + return new NamedThreadFactory(); + } + + public static NamedThreadFactory build(String namePrefix) { + return new NamedThreadFactory(namePrefix); } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/ThreadNames.java b/mpush-tools/src/main/java/com/mpush/tools/thread/ThreadNames.java index a1d49ad6..ced3f490 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/ThreadNames.java +++ b/mpush-tools/src/main/java/com/mpush/tools/thread/ThreadNames.java @@ -21,33 +21,26 @@ public final class ThreadNames { public static final String NS = "mp"; - public static final String THREAD_NAME_PREFIX = NS + "-t-"; - - /** - * netty boss 线程 - */ - public static final String T_SERVER_BOSS = NS + "-boss-"; - - /** - * netty worker 线程 - */ - public static final String T_SERVER_WORKER = NS + "-worker-"; - - public static final String T_HTTP_CLIENT = NS + "-http-"; - - public static final String T_EVENT_BUS = NS + "-event-"; - - public static final String T_MQ = NS + "-mq-"; - - public static final String T_ZK = NS + "-zk-"; - - public static final String T_BIZ = NS + "-biz-"; - public static final String T_PUSH_CALLBACK = NS + "-push-cb-"; - public static final String T_PUSH_REQ_TIMER = NS + "-push-timer-"; - - /** - * connection 定期检测线程 - */ - public static final String T_NETTY_TIMER = NS + "-timer-"; + public static final String THREAD_NAME_PREFIX = NS + "-t"; + public static final String T_BOSS = NS + "-boss"; + public static final String T_WORKER = NS + "-work"; + public static final String T_CONN_BOSS = NS + "-conn-boss"; + public static final String T_GATEWAY_BOSS = NS + "-gateway-boss"; + public static final String T_ADMIN_BOSS = NS + "-admin-boss"; + public static final String T_CONN_WORKER = NS + "-conn-work"; + public static final String T_ADMIN_WORKER = NS + "-admin-work"; + public static final String T_GATEWAY_WORKER = NS + "-gateway-work"; + public static final String T_TRAFFIC_SHAPING = NS + "-traffic-shaping"; + public static final String T_TCP_CLIENT = NS + "-tcp-client"; + public static final String T_HTTP_CLIENT = NS + "-http-client-work"; + public static final String T_EVENT_BUS = NS + "-event"; + public static final String T_MQ = NS + "-mq"; + public static final String T_ARK_REQ_TIMER = NS + "-ack-timer"; + public static final String T_PUSH_CLIENT_TIMER = NS + "-push-client-timer"; + public static final String T_PUSH_CENTER_TIMER = NS + "-push-center-timer"; + public static final String T_CONN_TIMER = NS + "-conn-check-timer"; + public static final String T_HTTP_TIMER = NS + "-http-client-timer"; + public static final String T_HTTP_DNS_TIMER = NS + "-http-dns-timer"; + public static final String T_MONITOR = "monitor"; } diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DefaultExecutor.java b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DefaultExecutor.java index aad039ea..33570c03 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DefaultExecutor.java +++ b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DefaultExecutor.java @@ -25,9 +25,13 @@ * * @author ohun@live.cn (夜色) */ -public class DefaultExecutor extends ThreadPoolExecutor { +public final class DefaultExecutor extends ThreadPoolExecutor { - public DefaultExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { + public DefaultExecutor(int corePoolSize, int maximumPoolSize, + long keepAliveTime, TimeUnit unit, + BlockingQueue workQueue, + ThreadFactory threadFactory, + RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); } diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DefaultThreadPoolFactory.java b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DefaultThreadPoolFactory.java deleted file mode 100644 index ef83ac45..00000000 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DefaultThreadPoolFactory.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.tools.thread.pool; - -import com.mpush.api.spi.common.ThreadPoolFactory; -import com.mpush.tools.config.CC; -import com.mpush.tools.thread.PoolThreadFactory; - -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -import static com.mpush.tools.thread.ThreadNames.*; - -/** - * 此线程池可伸缩,线程空闲一定时间后回收,新请求重新创建线程 - */ -public class DefaultThreadPoolFactory implements ThreadPoolFactory { - - private Executor get(ThreadPoolConfig config) { - String name = config.getName(); - int corePoolSize = config.getCorePoolSize(); - int maxPoolSize = config.getMaxPoolSize(); - int keepAliveSeconds = config.getKeepAliveSeconds(); - - BlockingQueue queue = config.getQueue(); - ThreadFactory threadFactory = new PoolThreadFactory(name); - - return new DefaultExecutor(corePoolSize - , maxPoolSize - , keepAliveSeconds - , TimeUnit.SECONDS - , queue - , threadFactory - , new DumpThreadRejectedHandler(config)); - } - - @Override - public Executor get(String name) { - final ThreadPoolConfig config; - switch (name) { - case SERVER_BOSS: - config = ThreadPoolConfig - .build(T_SERVER_BOSS) - .setCorePoolSize(CC.mp.thread.pool.boss.min) - .setMaxPoolSize(CC.mp.thread.pool.boss.max) - .setKeepAliveSeconds(TimeUnit.MINUTES.toSeconds(5)) - .setQueueCapacity(CC.mp.thread.pool.boss.queue_size); - break; - case SERVER_WORK: - config = ThreadPoolConfig - .build(T_SERVER_WORKER) - .setCorePoolSize(CC.mp.thread.pool.work.min) - .setMaxPoolSize(CC.mp.thread.pool.work.max) - .setKeepAliveSeconds(TimeUnit.MINUTES.toSeconds(5)) - .setQueueCapacity(CC.mp.thread.pool.work.queue_size); - break; - case HTTP_CLIENT_WORK: - config = ThreadPoolConfig - .buildFixed(T_HTTP_CLIENT, - CC.mp.thread.pool.http_proxy.min, - CC.mp.thread.pool.http_proxy.queue_size - ) - .setRejectedPolicy(ThreadPoolConfig.REJECTED_POLICY_DISCARD); - break; - case EVENT_BUS: - config = ThreadPoolConfig - .buildFixed(T_EVENT_BUS, - CC.mp.thread.pool.event_bus.min, - CC.mp.thread.pool.event_bus.queue_size - ); - break; - case MQ: - config = ThreadPoolConfig - .buildFixed(T_MQ, - CC.mp.thread.pool.mq.min, - CC.mp.thread.pool.mq.queue_size - ); - break; - case PUSH_CALLBACK: - config = ThreadPoolConfig - .build(T_PUSH_CALLBACK) - .setCorePoolSize(CC.mp.thread.pool.push_callback.min) - .setMaxPoolSize(CC.mp.thread.pool.push_callback.max) - .setKeepAliveSeconds(TimeUnit.SECONDS.toSeconds(10)) - .setQueueCapacity(CC.mp.thread.pool.push_callback.queue_size) - .setRejectedPolicy(ThreadPoolConfig.REJECTED_POLICY_CALLER_RUNS); - break; - default: - case BIZ: - config = ThreadPoolConfig - .build(T_BIZ) - .setCorePoolSize(CC.mp.thread.pool.biz.min) - .setMaxPoolSize(CC.mp.thread.pool.biz.max) - .setKeepAliveSeconds(TimeUnit.MINUTES.toSeconds(5)) - .setQueueCapacity(CC.mp.thread.pool.biz.queue_size); - break; - } - - return get(config); - } -} diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DumpThreadRejectedHandler.java b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DumpThreadRejectedHandler.java index f5d5165d..e2fbdd1a 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DumpThreadRejectedHandler.java +++ b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/DumpThreadRejectedHandler.java @@ -19,6 +19,7 @@ package com.mpush.tools.thread.pool; +import com.mpush.tools.Utils; import com.mpush.tools.common.JVMUtil; import com.mpush.tools.config.CC; import org.slf4j.Logger; @@ -31,7 +32,7 @@ import static com.mpush.tools.thread.pool.ThreadPoolConfig.REJECTED_POLICY_ABORT; import static com.mpush.tools.thread.pool.ThreadPoolConfig.REJECTED_POLICY_CALLER_RUNS; -public class DumpThreadRejectedHandler implements RejectedExecutionHandler { +public final class DumpThreadRejectedHandler implements RejectedExecutionHandler { private final static Logger LOGGER = LoggerFactory.getLogger(DumpThreadRejectedHandler.class); @@ -50,7 +51,7 @@ public DumpThreadRejectedHandler(ThreadPoolConfig poolConfig) { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { - LOGGER.warn("one task rejected, poolConfig={}, poolInfo={}", poolConfig, ThreadPoolManager.getPoolInfo(e)); + LOGGER.warn("one task rejected, poolConfig={}, poolInfo={}", poolConfig, Utils.getPoolInfo(e)); if (!dumping) { dumping = true; dumpJVMInfo(); @@ -66,9 +67,9 @@ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } private void dumpJVMInfo() { - LOGGER.error("start dump jvm info"); + LOGGER.info("start dump jvm info"); JVMUtil.dumpJstack(DUMP_DIR + "/" + poolConfig.getName()); - LOGGER.error("end dump jvm info"); + LOGGER.info("end dump jvm info"); } } diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/ThreadPoolConfig.java b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/ThreadPoolConfig.java index d82cf1a0..1f2e1236 100644 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/ThreadPoolConfig.java +++ b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/ThreadPoolConfig.java @@ -23,7 +23,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.SynchronousQueue; -public class ThreadPoolConfig { +public final class ThreadPoolConfig { public static final int REJECTED_POLICY_ABORT = 0; public static final int REJECTED_POLICY_DISCARD = 1; public static final int REJECTED_POLICY_CALLER_RUNS = 2; @@ -110,7 +110,7 @@ public static ThreadPoolConfig build(String name) { } - public BlockingQueue getQueue() { + public BlockingQueue getQueue() { BlockingQueue blockingQueue; if (queueCapacity == 0) { blockingQueue = new SynchronousQueue<>(); diff --git a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/ThreadPoolManager.java b/mpush-tools/src/main/java/com/mpush/tools/thread/pool/ThreadPoolManager.java deleted file mode 100644 index 45b28813..00000000 --- a/mpush-tools/src/main/java/com/mpush/tools/thread/pool/ThreadPoolManager.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.tools.thread.pool; - -import com.mpush.api.spi.SpiLoader; -import com.mpush.api.spi.common.ThreadPoolFactory; -import com.mpush.tools.thread.NamedThreadFactory; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadPoolExecutor; - -import static com.mpush.tools.config.CC.mp.spi.thread_pool_factory; - -public class ThreadPoolManager { - public static final ThreadPoolManager I = new ThreadPoolManager(); - - private final ThreadPoolFactory threadPoolFactory = SpiLoader.load(ThreadPoolFactory.class, thread_pool_factory); - private final NamedThreadFactory threadFactory = new NamedThreadFactory(); - - private Executor bossExecutor; - private Executor workExecutor; - private Executor bizExecutor; - private Executor eventBusExecutor; - private Executor redisExecutor; - private Executor httpExecutor; - private Executor pushCallbackExecutor; - - public final Thread newThread(String name, Runnable target) { - return threadFactory.newThread(name, target); - } - - public Executor getHttpExecutor() { - if (httpExecutor == null) { - synchronized (this) { - httpExecutor = threadPoolFactory.get(ThreadPoolFactory.HTTP_CLIENT_WORK); - } - } - return httpExecutor; - } - - public Executor getRedisExecutor() { - if (redisExecutor == null) { - synchronized (this) { - redisExecutor = threadPoolFactory.get(ThreadPoolFactory.MQ); - } - } - return redisExecutor; - } - - public Executor getEventBusExecutor() { - if (eventBusExecutor == null) { - synchronized (this) { - eventBusExecutor = threadPoolFactory.get(ThreadPoolFactory.EVENT_BUS); - } - } - return eventBusExecutor; - } - - public Executor getBizExecutor() { - if (bizExecutor == null) { - synchronized (this) { - bizExecutor = threadPoolFactory.get(ThreadPoolFactory.BIZ); - } - } - return bizExecutor; - } - - public Executor getWorkExecutor() { - if (workExecutor == null) { - synchronized (this) { - workExecutor = threadPoolFactory.get(ThreadPoolFactory.SERVER_WORK); - } - } - return workExecutor; - } - - public Executor getBossExecutor() { - if (bossExecutor == null) { - synchronized (this) { - bossExecutor = threadPoolFactory.get(ThreadPoolFactory.SERVER_BOSS); - } - } - return bossExecutor; - } - - public Executor getPushCallbackExecutor() { - if (pushCallbackExecutor == null) { - synchronized (this) { - pushCallbackExecutor = threadPoolFactory.get(ThreadPoolFactory.PUSH_CALLBACK); - } - } - return pushCallbackExecutor; - } - - public Map getActivePools() { - Map map = new HashMap<>(); - if (bossExecutor != null) map.put("bossExecutor", bossExecutor); - if (workExecutor != null) map.put("workExecutor", workExecutor); - if (bizExecutor != null) map.put("bizExecutor", bizExecutor); - if (eventBusExecutor != null) map.put("eventBusExecutor", eventBusExecutor); - if (redisExecutor != null) map.put("redisExecutor", redisExecutor); - if (httpExecutor != null) map.put("httpExecutor", httpExecutor); - if (pushCallbackExecutor != null) map.put("pushCallbackExecutor", pushCallbackExecutor); - return map; - } - - public static Map getPoolInfo(ThreadPoolExecutor executor) { - Map info = new HashMap<>(); - info.put("corePoolSize", executor.getCorePoolSize()); - info.put("maxPoolSize", executor.getMaximumPoolSize()); - info.put("activeCount(workingThread)", executor.getActiveCount()); - info.put("poolSize(workThread)", executor.getPoolSize()); - info.put("queueSize(blockedTask)", executor.getQueue().size()); - return info; - } -} diff --git a/mpush-tools/src/main/resources/META-INF/services/com.mpush.api.spi.common.JsonFactory b/mpush-tools/src/main/resources/META-INF/services/com.mpush.api.spi.common.JsonFactory new file mode 100644 index 00000000..07ae4798 --- /dev/null +++ b/mpush-tools/src/main/resources/META-INF/services/com.mpush.api.spi.common.JsonFactory @@ -0,0 +1 @@ +com.mpush.tools.common.DefaultJsonFactory \ No newline at end of file diff --git a/mpush-tools/src/main/resources/META-INF/services/com.mpush.api.spi.common.ThreadPoolFactory b/mpush-tools/src/main/resources/META-INF/services/com.mpush.api.spi.common.ThreadPoolFactory deleted file mode 100644 index be69a4a6..00000000 --- a/mpush-tools/src/main/resources/META-INF/services/com.mpush.api.spi.common.ThreadPoolFactory +++ /dev/null @@ -1 +0,0 @@ -com.mpush.tools.thread.pool.DefaultThreadPoolFactory \ No newline at end of file diff --git a/mpush-tools/src/test/java/com/mpush/tools/crypto/RSAUtilsTest.java b/mpush-tools/src/test/java/com/mpush/tools/crypto/RSAUtilsTest.java index d723f7c6..6aa9a241 100644 --- a/mpush-tools/src/test/java/com/mpush/tools/crypto/RSAUtilsTest.java +++ b/mpush-tools/src/test/java/com/mpush/tools/crypto/RSAUtilsTest.java @@ -36,21 +36,14 @@ public class RSAUtilsTest { String publicKey; String privateKey; - String publicKey2; - String privateKey2; - @Before public void setUp() throws Exception { try { - Pair pair = RSAUtils.genKeyPair(); + Pair pair = RSAUtils.genKeyPair(RSAUtils.RAS_KEY_SIZE); publicKey = RSAUtils.encodeBase64(pair.key); privateKey = RSAUtils.encodeBase64(pair.value); System.out.println("公钥: \n\r" + publicKey); System.out.println("私钥: \n\r" + privateKey); - - pair = RSAUtils.genKeyPair(); - publicKey2 = RSAUtils.encodeBase64(pair.key); - privateKey2 = RSAUtils.encodeBase64(pair.value); } catch (Exception e) { e.printStackTrace(); } @@ -65,11 +58,6 @@ public void testGetKeys() throws Exception { byte[] decodedData = RSAUtils.decryptByPrivateKey(encodedData, privateKey); String target = new String(decodedData); System.out.println("解密后:\n" + target); - - decodedData = RSAUtils.decryptByPrivateKey(encodedData, privateKey2); - target = new String(decodedData); - System.out.println("解密后2:\n" + target); - } @Test diff --git a/mpush-tools/src/test/resources/META-INF/services/com.mpush.tools.spi.test.TestService b/mpush-tools/src/test/resources/META-INF/services/com.mpush.tools.spi.test.TestService deleted file mode 100644 index d914afd4..00000000 --- a/mpush-tools/src/test/resources/META-INF/services/com.mpush.tools.spi.test.TestService +++ /dev/null @@ -1,2 +0,0 @@ -test1=com.mpush.tools.spi.test.TestServiceImpl -test2=com.mpush.tools.spi.test.TestServiceImpl2 \ No newline at end of file diff --git a/mpush-tools/src/test/resources/logback.xml b/mpush-tools/src/test/resources/logback.xml deleted file mode 100644 index c1d001d8..00000000 --- a/mpush-tools/src/test/resources/logback.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - System.out - UTF-8 - - INFO - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - System.err - UTF-8 - - WARN - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - [%thread] %-5level - %logger{35} - %msg%n - - - - - - - - - diff --git a/mpush-tools/src/test/resources/serverconfig.properties b/mpush-tools/src/test/resources/serverconfig.properties deleted file mode 100644 index 50ba973a..00000000 --- a/mpush-tools/src/test/resources/serverconfig.properties +++ /dev/null @@ -1,10 +0,0 @@ -zk_ip=10.1.20.74:2181 -zk_namespace=mpush -zk_digest=shinemoIpo -max_packet_size=10240 -compress_limit=10240 -min_heartbeat=10000 -max_heartbeat=180000 -max_hb_timeout_times=2 -private_key=MIIBNgIBADANBgkqhkiG9w0BAQEFAASCASAwggEcAgEAAoGBAKCE8JYKhsbydMPbiO7BJVq1pbuJWJHFxOR7L8Hv3ZVkSG4eNC8DdwAmDHYu/wadfw0ihKFm2gKDcLHp5yz5UQ8PZ8FyDYvgkrvGV0ak4nc40QDJWws621dm01e/INlGKOIStAAsxOityCLv0zm5Vf3+My/YaBvZcB5mGUsPbx8fAgEAAoGAAy0+WanRqwRHXUzt89OsupPXuNNqBlCEqgTqGAt4Nimq6Ur9u2R1KXKXUotxjp71Ubw6JbuUWvJg+5Rmd9RjT0HOUEQF3rvzEepKtaraPhV5ejEIrB+nJWNfGye4yzLdfEXJBGUQzrG+wNe13izfRNXI4dN/6Q5npzqaqv0E1CkCAQACAQACAQACAQACAQA= -public_key=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCghPCWCobG8nTD24juwSVataW7iViRxcTkey/B792VZEhuHjQvA3cAJgx2Lv8GnX8NIoShZtoCg3Cx6ecs+VEPD2fBcg2L4JK7xldGpOJ3ONEAyVsLOttXZtNXvyDZRijiErQALMTorcgi79M5uVX9/jMv2Ggb2XAeZhlLD28fHwIDAQAB \ No newline at end of file diff --git a/mpush-zk/pom.xml b/mpush-zk/pom.xml index aa427ea6..1695489e 100644 --- a/mpush-zk/pom.xml +++ b/mpush-zk/pom.xml @@ -2,21 +2,26 @@ + + 4.0.0 + mpush - com.mpush - 1.0 + com.github.mpusher + 0.8.0 + ../pom.xml - 4.0.0 - ${mpush.groupId} mpush-zk - ${mpush-zk-version} + jar + mpush-zookeeper + MPUSH消息推送系统zookeeper集群控制模块 + https://github.com/mpusher/mpush - ${mpush.groupId} - mpush-tools + ${project.groupId} + mpush-monitor org.apache.curator diff --git a/mpush-zk/src/main/java/com/mpush/zk/listener/ZKNodeCacheWatcher.java b/mpush-zk/src/main/java/com/mpush/zk/ZKCacheListener.java similarity index 50% rename from mpush-zk/src/main/java/com/mpush/zk/listener/ZKNodeCacheWatcher.java rename to mpush-zk/src/main/java/com/mpush/zk/ZKCacheListener.java index 4cd661cd..7eb9883c 100644 --- a/mpush-zk/src/main/java/com/mpush/zk/listener/ZKNodeCacheWatcher.java +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKCacheListener.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,63 +14,56 @@ * limitations under the License. * * Contributors: - * ohun@live.cn (夜色) + * ohun@live.cn (夜色) */ -package com.mpush.zk.listener; +package com.mpush.zk; import com.google.common.base.Strings; +import com.mpush.api.srd.CommonServiceNode; +import com.mpush.api.srd.ServiceListener; +import com.mpush.tools.Jsons; import com.mpush.tools.log.Logs; -import com.mpush.zk.ZKClient; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.TreeCacheEvent; import org.apache.curator.framework.recipes.cache.TreeCacheListener; -public abstract class ZKNodeCacheWatcher implements TreeCacheListener { +/** + * Created by ohun on 2016/12/28. + * + * @author ohun@live.cn (夜色) + */ +public final class ZKCacheListener implements TreeCacheListener { + + private final String watchPath; + + private final ServiceListener listener; + + public ZKCacheListener(String watchPath, ServiceListener listener) { + this.watchPath = watchPath; + this.listener = listener; + } @Override - public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception { + public void childEvent(CuratorFramework curator, TreeCacheEvent event) throws Exception { ChildData data = event.getData(); if (data == null) return; - String path = data.getPath(); - if (Strings.isNullOrEmpty(path)) return; - if (path.startsWith(watchPath())) { + String dataPath = data.getPath(); + if (Strings.isNullOrEmpty(dataPath)) return; + if (dataPath.startsWith(watchPath)) { switch (event.getType()) { case NODE_ADDED: - onNodeAdded(path, data.getData()); + listener.onServiceAdded(dataPath, Jsons.fromJson(data.getData(), CommonServiceNode.class)); break; case NODE_REMOVED: - onNodeRemoved(path, data.getData()); + listener.onServiceRemoved(dataPath, Jsons.fromJson(data.getData(), CommonServiceNode.class)); break; case NODE_UPDATED: - onNodeUpdated(path, data.getData()); + listener.onServiceUpdated(dataPath, Jsons.fromJson(data.getData(), CommonServiceNode.class)); break; } + Logs.RSD.info("ZK node data change={}, nodePath={}, watchPath={}, ns={}"); } - Logs.ZK.info("ZK node data change={}, name={}, listener={}, ns={}", event.getType(), path, watchPath(), client.getNamespace()); - } - - public final void beginWatch() { - beforeWatch(); - ZKClient.I.registerListener(this); - } - - public abstract String watchPath(); - - protected void beforeWatch() { - - } - - protected void onNodeAdded(String path, byte[] data) { - - } - - protected void onNodeRemoved(String path, byte[] data) { - - } - - protected void onNodeUpdated(String path, byte[] data) { - } } diff --git a/mpush-zk/src/main/java/com/mpush/zk/ZKClient.java b/mpush-zk/src/main/java/com/mpush/zk/ZKClient.java index 81b578f6..f947ce55 100644 --- a/mpush-zk/src/main/java/com/mpush/zk/ZKClient.java +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKClient.java @@ -23,23 +23,20 @@ import com.mpush.api.service.BaseService; import com.mpush.api.service.Listener; import com.mpush.tools.log.Logs; -import com.mpush.zk.listener.ZKNodeCacheWatcher; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.CuratorFrameworkFactory.Builder; import org.apache.curator.framework.api.ACLProvider; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.TreeCache; +import org.apache.curator.framework.recipes.cache.TreeCacheListener; import org.apache.curator.framework.state.ConnectionState; -import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.data.ACL; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; public class ZKClient extends BaseService { @@ -47,27 +44,45 @@ public class ZKClient extends BaseService { private ZKConfig zkConfig; private CuratorFramework client; private TreeCache cache; + private Map ephemeralNodes = new LinkedHashMap<>(4); + private Map ephemeralSequentialNodes = new LinkedHashMap<>(1); private synchronized static ZKClient I() { - if (I == null) return new ZKClient(); - else return I; + return I == null ? new ZKClient() : I; } private ZKClient() { } + @Override + public void start(Listener listener) { + if (isRunning()) { + listener.onSuccess(); + } else { + super.start(listener); + } + } + + @Override + public void stop(Listener listener) { + if (isRunning()) { + super.stop(listener); + } else { + listener.onSuccess(); + } + } + @Override protected void doStart(Listener listener) throws Throwable { client.start(); - Logs.Console.error("init zk client waiting for connected..."); + Logs.RSD.info("init zk client waiting for connected..."); if (!client.blockUntilConnected(1, TimeUnit.MINUTES)) { throw new ZKException("init zk error, config=" + zkConfig); } - initLocalCache(zkConfig.getLocalCachePath()); + initLocalCache(zkConfig.getWatchPath()); addConnectionStateListener(); + Logs.RSD.info("zk client start success, server lists is:{}", zkConfig.getHosts()); listener.onSuccess(zkConfig.getHosts()); - Logs.ZK.info("zk client start success, server lists is:{}", zkConfig.getHosts()); - Logs.Console.error("init zk client success..."); } @Override @@ -75,6 +90,8 @@ protected void doStop(Listener listener) throws Throwable { if (cache != null) cache.close(); TimeUnit.MILLISECONDS.sleep(600); client.close(); + Logs.RSD.info("zk client closed..."); + listener.onSuccess(); } /** @@ -82,8 +99,10 @@ protected void doStop(Listener listener) throws Throwable { */ @Override public void init() { - if (zkConfig != null) return; - zkConfig = ZKConfig.build(); + if (client != null) return; + if (zkConfig == null) { + zkConfig = ZKConfig.build(); + } Builder builder = CuratorFrameworkFactory .builder() .connectString(zkConfig.getHosts()) @@ -98,38 +117,47 @@ public void init() { } if (zkConfig.getDigest() != null) { - builder.authorization("digest", zkConfig.getDigest().getBytes(Constants.UTF_8)) - .aclProvider(new ACLProvider() { - - @Override - public List getDefaultAcl() { - return ZooDefs.Ids.CREATOR_ALL_ACL; - } - - @Override - public List getAclForPath(final String path) { - return ZooDefs.Ids.CREATOR_ALL_ACL; - } - }); + /* + * scheme对应于采用哪种方案来进行权限管理,zookeeper实现了一个pluggable的ACL方案,可以通过扩展scheme,来扩展ACL的机制。 + * zookeeper缺省支持下面几种scheme: + * + * world: 默认方式,相当于全世界都能访问; 它下面只有一个id, 叫anyone, world:anyone代表任何人,zookeeper中对所有人有权限的结点就是属于world:anyone的 + * auth: 代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户); 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication) + * digest: 即用户名:密码这种方式认证,这也是业务系统中最常用的;它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication + * ip: 使用Ip地址认证;它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段 + * super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa) + */ + builder.authorization("digest", zkConfig.getDigest().getBytes(Constants.UTF_8)); + builder.aclProvider(new ACLProvider() { + @Override + public List getDefaultAcl() { + return ZooDefs.Ids.CREATOR_ALL_ACL; + } + + @Override + public List getAclForPath(final String path) { + return ZooDefs.Ids.CREATOR_ALL_ACL; + } + }); } client = builder.build(); - Logs.Console.error("init zk client, config=" + zkConfig); + Logs.RSD.info("init zk client, config={}", zkConfig.toString()); } // 注册连接状态监听器 private void addConnectionStateListener() { - client.getConnectionStateListenable().addListener(new ConnectionStateListener() { - //TODO need close jvm? - @Override - public void stateChanged(final CuratorFramework client, final ConnectionState newState) { - Logs.ZK.warn("zk connection state changed new state={}, isConnected={}", newState, newState.isConnected()); + client.getConnectionStateListenable().addListener((cli, newState) -> { + if (newState == ConnectionState.RECONNECTED) { + ephemeralNodes.forEach(this::reRegisterEphemeral); + ephemeralSequentialNodes.forEach(this::reRegisterEphemeralSequential); } + Logs.RSD.warn("zk connection state changed new state={}, isConnected={}", newState, newState.isConnected()); }); } // 本地缓存 - private void initLocalCache(String cachePath) throws Exception { - cache = new TreeCache(client, cachePath); + private void initLocalCache(String watchRootPath) throws Exception { + cache = new TreeCache(client, watchRootPath); cache.start(); } @@ -157,12 +185,14 @@ public String get(final String key) { * @return */ public String getFromRemote(final String key) { - try { - return new String(client.getData().forPath(key), Constants.UTF_8); - } catch (final Exception ex) { - Logs.ZK.error("getFromRemote:{}", key, ex); - return null; + if (isExisted(key)) { + try { + return new String(client.getData().forPath(key), Constants.UTF_8); + } catch (Exception ex) { + Logs.RSD.error("getFromRemote:{}", key, ex); + } } + return null; } /** @@ -173,17 +203,12 @@ public String getFromRemote(final String key) { */ public List getChildrenKeys(final String key) { try { + if (!isExisted(key)) return Collections.emptyList(); List result = client.getChildren().forPath(key); - Collections.sort(result, new Comparator() { - - @Override - public int compare(final String o1, final String o2) { - return o2.compareTo(o1); - } - }); + result.sort(Comparator.reverseOrder()); return result; - } catch (final Exception ex) { - Logs.ZK.error("getChildrenKeys:{}", key, ex); + } catch (Exception ex) { + Logs.RSD.error("getChildrenKeys:{}", key, ex); return Collections.emptyList(); } } @@ -197,8 +222,8 @@ public int compare(final String o1, final String o2) { public boolean isExisted(final String key) { try { return null != client.checkExists().forPath(key); - } catch (final Exception ex) { - Logs.ZK.error("isExisted:{}", key, ex); + } catch (Exception ex) { + Logs.RSD.error("isExisted:{}", key, ex); return false; } } @@ -211,13 +236,13 @@ public boolean isExisted(final String key) { */ public void registerPersist(final String key, final String value) { try { - if (!isExisted(key)) { - client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(key, value.getBytes()); - } else { + if (isExisted(key)) { update(key, value); + } else { + client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(key, value.getBytes()); } - } catch (final Exception ex) { - Logs.ZK.error("persist:{},{}", key, value, ex); + } catch (Exception ex) { + Logs.RSD.error("persist:{},{}", key, value, ex); throw new ZKException(ex); } } @@ -230,9 +255,14 @@ public void registerPersist(final String key, final String value) { */ public void update(final String key, final String value) { try { + /*TransactionOp op = client.transactionOp(); + client.transaction().forOperations( + op.check().forPath(key), + op.setData().forPath(key, value.getBytes(Constants.UTF_8)) + );*/ client.inTransaction().check().forPath(key).and().setData().forPath(key, value.getBytes(Constants.UTF_8)).and().commit(); - } catch (final Exception ex) { - Logs.ZK.error("update:{},{}", key, value, ex); + } catch (Exception ex) { + Logs.RSD.error("update:{},{}", key, value, ex); throw new ZKException(ex); } } @@ -243,32 +273,65 @@ public void update(final String key, final String value) { * @param key * @param value */ - public void registerEphemeral(final String key, final String value) { + public void registerEphemeral(final String key, final String value, boolean cacheNode) { try { if (isExisted(key)) { client.delete().deletingChildrenIfNeeded().forPath(key); } client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(key, value.getBytes(Constants.UTF_8)); - } catch (final Exception ex) { - Logs.ZK.error("persistEphemeral:{},{}", key, value, ex); + if (cacheNode) ephemeralNodes.put(key, value); + } catch (Exception ex) { + Logs.RSD.error("persistEphemeral:{},{}", key, value, ex); throw new ZKException(ex); } } + /** + * 注册临时数据 + * + * @param key + * @param value + */ + public void reRegisterEphemeral(final String key, final String value) { + registerEphemeral(key, value, false); + } + + /** + * 注册临时数据 + * + * @param key + * @param value + */ + public void registerEphemeral(final String key, final String value) { + registerEphemeral(key, value, true); + } + /** * 注册临时顺序数据 * * @param key + * @param value + * @param cacheNode 第一次注册时设置为true, 连接断开重新注册时设置为false */ - public void registerEphemeralSequential(final String key, final String value) { + private void registerEphemeralSequential(final String key, final String value, boolean cacheNode) { try { client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(key, value.getBytes()); - } catch (final Exception ex) { - Logs.ZK.error("persistEphemeralSequential:{},{}", key, value, ex); + if (cacheNode) ephemeralSequentialNodes.put(key, value); + } catch (Exception ex) { + Logs.RSD.error("persistEphemeralSequential:{},{}", key, value, ex); throw new ZKException(ex); } } + private void reRegisterEphemeralSequential(final String key, final String value) { + registerEphemeralSequential(key, value, false); + } + + public void registerEphemeralSequential(final String key, final String value) { + registerEphemeralSequential(key, value, true); + } + + /** * 注册临时顺序数据 * @@ -277,8 +340,8 @@ public void registerEphemeralSequential(final String key, final String value) { public void registerEphemeralSequential(final String key) { try { client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(key); - } catch (final Exception ex) { - Logs.ZK.error("persistEphemeralSequential:{}", key, ex); + } catch (Exception ex) { + Logs.RSD.error("persistEphemeralSequential:{}", key, ex); throw new ZKException(ex); } } @@ -291,17 +354,26 @@ public void registerEphemeralSequential(final String key) { public void remove(final String key) { try { client.delete().deletingChildrenIfNeeded().forPath(key); - } catch (final Exception ex) { - Logs.ZK.error("removeAndClose:{}", key, ex); + } catch (Exception ex) { + Logs.RSD.error("removeAndClose:{}", key, ex); throw new ZKException(ex); } } - public void registerListener(ZKNodeCacheWatcher listener) { + public void registerListener(TreeCacheListener listener) { cache.getListenable().addListener(listener); } public ZKConfig getZKConfig() { return zkConfig; } + + public ZKClient setZKConfig(ZKConfig zkConfig) { + this.zkConfig = zkConfig; + return this; + } + + public CuratorFramework getClient() { + return client; + } } diff --git a/mpush-zk/src/main/java/com/mpush/zk/ZKConfig.java b/mpush-zk/src/main/java/com/mpush/zk/ZKConfig.java index 8843d450..c1814958 100644 --- a/mpush-zk/src/main/java/com/mpush/zk/ZKConfig.java +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKConfig.java @@ -45,7 +45,7 @@ public class ZKConfig { private int connectionTimeout = ZK_CONNECTION_TIMEOUT; - private String localCachePath = ZK_DEFAULT_CACHE_PATH; + private String watchPath = ZK_DEFAULT_CACHE_PATH; public ZKConfig(String hosts) { this.hosts = hosts; @@ -55,7 +55,7 @@ public static ZKConfig build() { return new ZKConfig(zk.server_address) .setConnectionTimeout(zk.connectionTimeoutMs) .setDigest(zk.digest) - .setLocalCachePath(zk.local_cache_path) + .setWatchPath(zk.watch_path) .setMaxRetries(zk.retry.maxRetries) .setMaxSleepMs(zk.retry.maxSleepMs) .setBaseSleepTimeMs(zk.retry.baseSleepTimeMs) @@ -136,12 +136,12 @@ public ZKConfig setDigest(String digest) { return this; } - public String getLocalCachePath() { - return localCachePath; + public String getWatchPath() { + return watchPath; } - public ZKConfig setLocalCachePath(String localCachePath) { - this.localCachePath = localCachePath; + public ZKConfig setWatchPath(String watchPath) { + this.watchPath = watchPath; return this; } @@ -156,7 +156,7 @@ public String toString() { ", maxSleepMs=" + maxSleepMs + ", sessionTimeout=" + sessionTimeout + ", connectionTimeout=" + connectionTimeout + - ", localCachePath='" + localCachePath + '\'' + + ", watchPath='" + watchPath + '\'' + '}'; } } diff --git a/mpush-zk/src/main/java/com/mpush/zk/ZKDiscoveryFactory.java b/mpush-zk/src/main/java/com/mpush/zk/ZKDiscoveryFactory.java new file mode 100644 index 00000000..99da6071 --- /dev/null +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKDiscoveryFactory.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.zk; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.ServiceDiscoveryFactory; +import com.mpush.api.srd.ServiceDiscovery; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 1) +public final class ZKDiscoveryFactory implements ServiceDiscoveryFactory { + @Override + public ServiceDiscovery get() { + return ZKServiceRegistryAndDiscovery.I; + } +} diff --git a/mpush-zk/src/main/java/com/mpush/zk/ZKPath.java b/mpush-zk/src/main/java/com/mpush/zk/ZKPath.java index ed1c7aa0..fd7613ad 100644 --- a/mpush-zk/src/main/java/com/mpush/zk/ZKPath.java +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKPath.java @@ -22,10 +22,14 @@ import org.apache.curator.utils.ZKPaths; +import static org.apache.curator.utils.ZKPaths.PATH_SEPARATOR; + public enum ZKPath { REDIS_SERVER("/redis", "machine", "redis注册的地方"), CONNECT_SERVER("/cs/hosts", "machine", "connection server服务器应用注册的路径"), - GATEWAY_SERVER("/gs/hosts", "machine", "gateway server服务器应用注册的路径"); + GATEWAY_SERVER("/gs/hosts", "machine", "gateway server服务器应用注册的路径"), + WS_SERVER("/ws/hosts", "machine", "websocket server服务器应用注册的路径"), + DNS_MAPPING("/dns/mapping", "machine", "dns mapping服务器应用注册的路径"); ZKPath(String root, String name, String desc) { this.root = root; @@ -40,12 +44,24 @@ public String getRootPath() { } public String getNodePath() { - return root + ZKPaths.PATH_SEPARATOR + name; + return root + PATH_SEPARATOR + name; + } + + public String getNodePath(String... tails) { + String path = getNodePath(); + for (String tail : tails) { + path += (PATH_SEPARATOR + tail); + } + return path; } //根据从zk中获取的app的值,拼装全路径 - public String getFullPath(String tail) { - return root + ZKPaths.PATH_SEPARATOR + tail; + public String getFullPath(String childPaths) { + return root + PATH_SEPARATOR + childPaths; + } + + public String getTail(String childPaths) { + return ZKPaths.getNodeFromPath(childPaths); } } diff --git a/mpush-zk/src/main/java/com/mpush/zk/ZKRegistryFactory.java b/mpush-zk/src/main/java/com/mpush/zk/ZKRegistryFactory.java new file mode 100644 index 00000000..f3ccb466 --- /dev/null +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKRegistryFactory.java @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.zk; + +import com.mpush.api.spi.Spi; +import com.mpush.api.spi.common.ServiceRegistryFactory; +import com.mpush.api.srd.ServiceRegistry; + +/** + * Created by ohun on 2016/12/27. + * + * @author ohun@live.cn (夜色) + */ +@Spi(order = 1) +public final class ZKRegistryFactory implements ServiceRegistryFactory { + @Override + public ServiceRegistry get() { + return ZKServiceRegistryAndDiscovery.I; + } +} diff --git a/mpush-zk/src/main/java/com/mpush/zk/ZKServiceRegistryAndDiscovery.java b/mpush-zk/src/main/java/com/mpush/zk/ZKServiceRegistryAndDiscovery.java new file mode 100644 index 00000000..e5cbf2c7 --- /dev/null +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKServiceRegistryAndDiscovery.java @@ -0,0 +1,118 @@ +/* + * (C) Copyright 2015-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Contributors: + * ohun@live.cn (夜色) + */ + +package com.mpush.zk; + +import com.mpush.api.service.BaseService; +import com.mpush.api.service.Listener; +import com.mpush.api.srd.*; +import com.mpush.tools.Jsons; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.apache.curator.utils.ZKPaths.PATH_SEPARATOR; + +/** + * Created by ohun on 16/9/22. + * + * @author ohun@live.cn (夜色) + */ +public final class ZKServiceRegistryAndDiscovery extends BaseService implements ServiceRegistry, ServiceDiscovery { + + public static final ZKServiceRegistryAndDiscovery I = new ZKServiceRegistryAndDiscovery(); + + private final ZKClient client; + + public ZKServiceRegistryAndDiscovery() { + this.client = ZKClient.I; + } + + @Override + public void start(Listener listener) { + if (isRunning()) { + listener.onSuccess(); + } else { + super.start(listener); + } + } + + @Override + public void stop(Listener listener) { + if (isRunning()) { + super.stop(listener); + } else { + listener.onSuccess(); + } + } + + @Override + protected void doStart(Listener listener) throws Throwable { + client.start(listener); + } + + @Override + protected void doStop(Listener listener) throws Throwable { + client.stop(listener); + } + + @Override + public void register(ServiceNode node) { + if (node.isPersistent()) { + client.registerPersist(node.nodePath(), Jsons.toJson(node)); + } else { + client.registerEphemeral(node.nodePath(), Jsons.toJson(node)); + } + } + + @Override + public void deregister(ServiceNode node) { + if (client.isRunning()) { + client.remove(node.nodePath()); + } + } + + @Override + public List lookup(String serviceName) { + List childrenKeys = client.getChildrenKeys(serviceName); + if (childrenKeys == null || childrenKeys.isEmpty()) { + return Collections.emptyList(); + } + + return childrenKeys.stream() + .map(key -> serviceName + PATH_SEPARATOR + key) + .map(client::get) + .filter(Objects::nonNull) + .map(childData -> Jsons.fromJson(childData, CommonServiceNode.class)) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public void subscribe(String watchPath, ServiceListener listener) { + client.registerListener(new ZKCacheListener(watchPath, listener)); + } + + @Override + public void unsubscribe(String path, ServiceListener listener) { + + } +} diff --git a/mpush-zk/src/main/java/com/mpush/zk/ZKSyncMap.java b/mpush-zk/src/main/java/com/mpush/zk/ZKSyncMap.java new file mode 100644 index 00000000..7bcd8a8a --- /dev/null +++ b/mpush-zk/src/main/java/com/mpush/zk/ZKSyncMap.java @@ -0,0 +1,224 @@ +package com.mpush.zk; + +import com.google.common.collect.Maps; +import org.apache.curator.framework.CuratorFramework; +import org.apache.zookeeper.KeeperException; + +import java.io.*; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Created by Stream.Liu + */ +public class ZKSyncMap implements Map { + static final String ZK_PATH_SYNC_MAP = "syncMap"; + + private final CuratorFramework curator; + private final String mapPath; + private final String mapName; + + public ZKSyncMap(CuratorFramework curator, String mapName) { + this.curator = curator; + this.mapName = mapName; + this.mapPath = "/" + ZK_PATH_SYNC_MAP + "/" + mapName; + } + + @Override + public int size() { + try { + return curator.getChildren().forPath(mapPath).size(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean isEmpty() { + try { + return curator.getChildren().forPath(mapPath).isEmpty(); + } catch (Exception e) { + throw new ZKException(e); + } + } + + @Override + public boolean containsKey(Object key) { + try { + return curator.checkExists().forPath(keyPath(key)) != null; + } catch (Exception e) { + throw new ZKException(e); + } + } + + @Override + public boolean containsValue(Object value) { + try { + return curator.getChildren().forPath(mapPath).stream().anyMatch(k -> { + try { + byte[] bytes = curator.getData().forPath(keyPath(k)); + KeyValue keyValue = asObject(bytes); + return keyValue.getValue().equals(value); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }); + } catch (Exception e) { + throw new ZKException(e); + } + } + + @Override + public V get(Object key) { + try { + String keyPath = keyPath(key); + if (null == curator.checkExists().forPath(keyPath)) { + return null; + } else { + KeyValue keyValue = asObject(curator.getData().forPath(keyPath)); + return keyValue.getValue(); + } + } catch (Exception e) { + if (!(e instanceof KeeperException.NodeExistsException)) { + throw new ZKException(e); + } + } + return null; + } + + @Override + public V put(K key, V value) { + try { + String keyPath = keyPath(key); + KeyValue keyValue = new KeyValue<>(key, value); + byte[] valueBytes = asByte(keyValue); + if (get(key) != null) { + curator.setData().forPath(keyPath, valueBytes); + } else { + curator.create().creatingParentsIfNeeded().forPath(keyPath, valueBytes); + } + return value; + } catch (Exception e) { + throw new ZKException(e); + } + } + + @Override + public V remove(Object key) { + try { + V result = get(key); + if (result != null) curator.delete().deletingChildrenIfNeeded().forPath(keyPath(key)); + return result; + } catch (Exception e) { + throw new ZKException(e); + } + } + + @Override + public void putAll(Map m) { + m.entrySet().forEach(entry -> put(entry.getKey(), entry.getValue())); + } + + @Override + public void clear() { + try { + curator.delete().deletingChildrenIfNeeded().forPath(mapPath); + curator.create().creatingParentsIfNeeded().forPath(mapPath); + } catch (Exception e) { + throw new ZKException(e); + } + } + + @Override + public Set keySet() { + try { + return curator.getChildren().forPath(mapPath).stream().map(k -> { + try { + KeyValue keyValue = asObject(curator.getData().forPath(keyPath(k))); + return keyValue.getKey(); + } catch (Exception ex) { + throw new ZKException(ex); + } + }).collect(Collectors.toSet()); + } catch (Exception ex) { + throw new ZKException(ex); + } + } + + @Override + public Collection values() { + try { + return curator.getChildren().forPath(mapPath).stream() + .map(k -> { + try { + KeyValue keyValue = asObject(curator.getData().forPath(keyPath(k))); + return keyValue.getValue(); + } catch (Exception ex) { + throw new ZKException(ex); + } + } + ).collect(Collectors.toSet()); + } catch (Exception ex) { + throw new ZKException(ex); + } + } + + private String keyPath(Object k) { + return mapPath + "/" + k.toString(); + } + + private String valuePath(Object k, Object v) { + return keyPath(k) + "/" + v.toString(); + } + + private byte[] asByte(Object object) throws IOException { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + DataOutput dataOutput = new DataOutputStream(byteOut); + dataOutput.writeBoolean(false); + ByteArrayOutputStream javaByteOut = new ByteArrayOutputStream(); + ObjectOutput objectOutput = new ObjectOutputStream(javaByteOut); + objectOutput.writeObject(object); + dataOutput.write(javaByteOut.toByteArray()); + return byteOut.toByteArray(); + } + + @SuppressWarnings("unchecked") + private T asObject(byte[] bytes) throws Exception { + ByteArrayInputStream byteIn = new ByteArrayInputStream(bytes); + DataInputStream in = new DataInputStream(byteIn); + + byte[] body = new byte[in.available()]; + in.readFully(body); + ObjectInputStream objectIn = new ObjectInputStream(new ByteArrayInputStream(body)); + return (T) objectIn.readObject(); + } + + @Override + public Set> entrySet() { + return keySet().stream().map(k -> { + V v = get(k); + return Maps.immutableEntry(k, v); + }).collect(Collectors.toSet()); + } + + private static class KeyValue implements Serializable { + private K key; + private V value; + + private KeyValue(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + } + +} \ No newline at end of file diff --git a/mpush-zk/src/main/java/com/mpush/zk/cache/ZKRedisNodeCache.java b/mpush-zk/src/main/java/com/mpush/zk/cache/ZKRedisNodeCache.java deleted file mode 100644 index 338704ab..00000000 --- a/mpush-zk/src/main/java/com/mpush/zk/cache/ZKRedisNodeCache.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.zk.cache; - -import com.mpush.zk.node.ZKRedisNode; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Created by yxx on 2016/5/18. - * - * @author ohun@live.cn - */ -public class ZKRedisNodeCache implements ZKNodeCache { - - private List nodes = Collections.emptyList(); - - @Override - public void addAll(List list) { - nodes = list; - } - - @Override - public void put(String fullPath, ZKRedisNode node) { - throw new UnsupportedOperationException("can not put one redis node, name=" + fullPath); - } - - @Override - public ZKRedisNode remove(String fullPath) { - nodes = Collections.emptyList(); - return null; - } - - @Override - public Collection values() { - return nodes; - } - - @Override - public void clear() { - nodes = Collections.emptyList(); - } -} diff --git a/mpush-zk/src/main/java/com/mpush/zk/cache/ZKServerNodeCache.java b/mpush-zk/src/main/java/com/mpush/zk/cache/ZKServerNodeCache.java deleted file mode 100644 index b41a271f..00000000 --- a/mpush-zk/src/main/java/com/mpush/zk/cache/ZKServerNodeCache.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.zk.cache; - -import com.google.common.collect.Maps; -import com.mpush.zk.node.ZKServerNode; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Created by yxx on 2016/5/17. - * - * @author ohun@live.cn - */ -public class ZKServerNodeCache implements ZKNodeCache { - - private final Logger logger = LoggerFactory.getLogger(ZKServerNodeCache.class); - - protected final Map cache = Maps.newConcurrentMap(); - - @Override - public void addAll(List list) { - - } - - @Override - public void put(String fullPath, ZKServerNode node) { - if (StringUtils.isNotBlank(fullPath) && node != null) { - cache.put(fullPath, node); - } else { - logger.error("fullPath is null or application is null"); - } - printList(); - } - - @Override - public ZKServerNode remove(String fullPath) { - ZKServerNode node = cache.remove(fullPath); - printList(); - return node; - } - - @Override - public Collection values() { - return Collections.unmodifiableCollection(cache.values()); - } - - @Override - public void clear() { - cache.clear(); - } - - private void printList() { - for (ZKServerNode app : cache.values()) { - logger.warn(app.toString()); - } - } -} diff --git a/mpush-zk/src/main/java/com/mpush/zk/listener/ZKRedisNodeWatcher.java b/mpush-zk/src/main/java/com/mpush/zk/listener/ZKRedisNodeWatcher.java deleted file mode 100644 index ef805ff0..00000000 --- a/mpush-zk/src/main/java/com/mpush/zk/listener/ZKRedisNodeWatcher.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.zk.listener; - -import com.google.common.base.Strings; -import com.mpush.tools.Jsons; -import com.mpush.zk.ZKClient; -import com.mpush.zk.ZKPath; -import com.mpush.zk.cache.ZKRedisNodeCache; -import com.mpush.zk.node.ZKRedisNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; - -/** - * redis 监控 - */ -public class ZKRedisNodeWatcher extends ZKNodeCacheWatcher { - - private final Logger logger = LoggerFactory.getLogger(ZKRedisNodeWatcher.class); - - private final ZKRedisNodeCache cache = new ZKRedisNodeCache(); - - private void refresh() { - String rawGroup = ZKClient.I.get(ZKPath.REDIS_SERVER.getRootPath()); - logger.warn("refresh zk redis node cache, data=" + rawGroup); - if (Strings.isNullOrEmpty(rawGroup)) return; - ZKRedisNode[] group = Jsons.fromJson(rawGroup, ZKRedisNode[].class); - if (group == null) return; - cache.addAll(Arrays.asList(group)); - } - - @Override - protected void beforeWatch() { - refresh(); - } - - @Override - protected void onNodeAdded(String path, byte[] data) { - refresh(); - } - - @Override - protected void onNodeRemoved(String path, byte[] data) { - refresh(); - } - - @Override - protected void onNodeUpdated(String path, byte[] data) { - refresh(); - } - - @Override - public String watchPath() { - return ZKPath.REDIS_SERVER.getRootPath(); - } - - public ZKRedisNodeCache getCache() { - return cache; - } -} diff --git a/mpush-zk/src/main/java/com/mpush/zk/listener/ZKServerNodeWatcher.java b/mpush-zk/src/main/java/com/mpush/zk/listener/ZKServerNodeWatcher.java deleted file mode 100644 index a88b6c14..00000000 --- a/mpush-zk/src/main/java/com/mpush/zk/listener/ZKServerNodeWatcher.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.zk.listener; - -import com.mpush.tools.Jsons; -import com.mpush.tools.log.Logs; -import com.mpush.zk.ZKClient; -import com.mpush.zk.ZKPath; -import com.mpush.zk.cache.ZKServerNodeCache; -import com.mpush.zk.node.ZKServerNode; - -import java.util.List; - -public final class ZKServerNodeWatcher extends ZKNodeCacheWatcher { - private final ZKPath path; - private final ZKServerNodeCache cache; - - public static ZKServerNodeWatcher buildConnect() { - return new ZKServerNodeWatcher(ZKPath.CONNECT_SERVER); - } - - public static ZKServerNodeWatcher buildGateway() { - return new ZKServerNodeWatcher(ZKPath.GATEWAY_SERVER); - } - - public static ZKServerNodeWatcher build(ZKPath path, ZKServerNodeCache cache) { - return new ZKServerNodeWatcher(path, cache); - } - - public ZKServerNodeWatcher(ZKPath path) { - this.path = path; - this.cache = new ZKServerNodeCache(); - } - - public ZKServerNodeWatcher(ZKPath path, ZKServerNodeCache cache) { - this.path = path; - this.cache = cache; - } - - @Override - protected void onNodeAdded(String path, byte[] data) { - ZKServerNode serverApp = Jsons.fromJson(data, ZKServerNode.class); - cache.put(path, serverApp); - } - - @Override - protected void onNodeRemoved(String path, byte[] data) { - cache.remove(path); - } - - @Override - protected void onNodeUpdated(String path, byte[] data) { - ZKServerNode serverApp = Jsons.fromJson(data, ZKServerNode.class); - cache.put(path, serverApp); - } - - @Override - public String watchPath() { - return path.getNodePath(); - } - - @Override - protected void beforeWatch() { - Logs.Console.error("start init zk server data"); - List rawData = ZKClient.I.getChildrenKeys(path.getRootPath()); - for (String raw : rawData) { - String fullPath = path.getFullPath(raw); - ZKServerNode app = getServerNode(fullPath); - cache.put(fullPath, app); - } - Logs.Console.error("end init zk server data"); - } - - private ZKServerNode getServerNode(String fullPath) { - String rawApp = ZKClient.I.get(fullPath); - ZKServerNode app = Jsons.fromJson(rawApp, ZKServerNode.class); - return app; - } - - public ZKServerNodeCache getCache() { - return cache; - } - -} diff --git a/mpush-zk/src/main/java/com/mpush/zk/node/ZKServerNode.java b/mpush-zk/src/main/java/com/mpush/zk/node/ZKServerNode.java deleted file mode 100644 index 73d24e84..00000000 --- a/mpush-zk/src/main/java/com/mpush/zk/node/ZKServerNode.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * (C) Copyright 2015-2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Contributors: - * ohun@live.cn (夜色) - */ - -package com.mpush.zk.node; - - -import com.mpush.tools.Jsons; -import com.mpush.tools.Utils; -import com.mpush.tools.config.CC; -import com.mpush.tools.config.ConfigManager; -import com.mpush.zk.ZKPath; - -/** - * 系统配置 - */ -public class ZKServerNode implements ZKNode { - - private String ip; - - private int port; - - private String extranetIp; - - private transient String zkPath; - - public ZKServerNode() { - } - - public ZKServerNode(String ip, int port, String extranetIp, String zkPath) { - this.ip = ip; - this.port = port; - this.extranetIp = extranetIp; - this.zkPath = zkPath; - } - - public static ZKServerNode csNode() { - return new ZKServerNode(Utils.getLocalIp(), - CC.mp.net.connect_server_port, - ConfigManager.I.getPublicIp(), - ZKPath.CONNECT_SERVER.getNodePath()); - } - - public static ZKServerNode gsNode() { - return new ZKServerNode(Utils.getLocalIp(), - CC.mp.net.gateway_server_port, - null, - ZKPath.GATEWAY_SERVER.getNodePath()); - } - - public String getIp() { - return ip; - } - - public ZKServerNode setIp(String ip) { - this.ip = ip; - return this; - } - - public int getPort() { - return port; - } - - public ZKServerNode setPort(int port) { - this.port = port; - return this; - } - - public String getExtranetIp() { - return extranetIp; - } - - public ZKServerNode setExtranetIp(String extranetIp) { - this.extranetIp = extranetIp; - return this; - } - - public String getZkPath() { - return zkPath; - } - - public ZKServerNode setZkPath(String zkPath) { - this.zkPath = zkPath; - return this; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ZKServerNode that = (ZKServerNode) o; - - if (port != that.port) return false; - return ip != null ? ip.equals(that.ip) : that.ip == null; - - } - - @Override - public int hashCode() { - int result = ip != null ? ip.hashCode() : 0; - result = 31 * result + port; - return result; - } - - @Override - public String toString() { - return "ZKServerNode{" + - "host='" + ip + '\'' + - ", port=" + port + - ", extranetIp='" + extranetIp + '\'' + - ", zkPath='" + zkPath + '\'' + - '}'; - } - - @Override - public String encode() { - return Jsons.toJson(this); - } -} diff --git a/mpush-zk/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceDiscoveryFactory b/mpush-zk/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceDiscoveryFactory new file mode 100644 index 00000000..f7fd190c --- /dev/null +++ b/mpush-zk/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceDiscoveryFactory @@ -0,0 +1 @@ +com.mpush.zk.ZKDiscoveryFactory \ No newline at end of file diff --git a/mpush-zk/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceRegistryFactory b/mpush-zk/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceRegistryFactory new file mode 100644 index 00000000..da53a49a --- /dev/null +++ b/mpush-zk/src/main/resources/META-INF/services/com.mpush.api.spi.common.ServiceRegistryFactory @@ -0,0 +1 @@ +com.mpush.zk.ZKRegistryFactory \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5c4d8401..dde964c5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,19 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.mpush + + org.sonatype.oss + oss-parent + 9 + + + com.github.mpusher mpush pom - 1.0 + 0.8.0 + mpush + MPUSH消息推送系统 + https://github.com/mpusher/mpush @@ -16,16 +25,28 @@ repo + - master - git@github.com:mpusher/mpush.git - scm:git@github.com:mpusher/mpush.git - scm:ggit@github.com:mpusher/mpush.git + v${project.version} + https://github.com/mpusher/mpush + scm:git:git://github.com/mpusher/mpush.git + scm:git:ssh://github.com/mpusher/mpush.git + + + GitHub Issues + https://github.com/mpusher/mpush/issues + + + + MPusher, Inc. + https://mpusher.io + + - ohun - ohun@live.cm + 夜色 + ohun@live.cn mpusher @@ -49,20 +70,8 @@ UTF-8 UTF-8 1.8 - com.mpush - 0.0.1 - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - ${mpush.version} - 5.0.0.Alpha2 + 4.1.8.Final + 1.7.25 linux-x86_64 @@ -96,96 +105,75 @@ io.netty netty-handler - 5.0.0.Alpha2 - - - - - - com.google.guava - guava - 19.0 - - - - junit - junit - 4.10 + ${netty.version} - org.apache.curator - curator-recipes - 2.9.1 - - - netty - io.netty - - + io.netty + netty-transport-udt + ${netty.version} - org.apache.curator - curator-x-discovery - 2.9.1 + io.netty + netty-transport-sctp + ${netty.version} - - + - ${mpush.groupId} + ${project.groupId} mpush-test - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-api - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-tools - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-common - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-netty - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-core - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-client - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-monitor - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-boot - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-zk - ${mpush.version} + ${project.version} - ${mpush.groupId} + ${project.groupId} mpush-cache - ${mpush.version} + ${project.version} @@ -193,17 +181,30 @@ org.slf4j slf4j-api - 1.7.5 + ${slf4j.version} + + + org.slf4j + log4j-over-slf4j + ${slf4j.version} + + + + org.slf4j + jul-to-slf4j + ${slf4j.version} + + org.slf4j jcl-over-slf4j - 1.7.5 + ${slf4j.version} ch.qos.logback logback-classic - 1.0.13 + 1.2.3 commons-logging @@ -217,33 +218,68 @@ 1.2.17 provided - - - + + + + - redis.clients - jedis - 2.8.0 + junit + junit + 4.10 + test + org.apache.commons commons-lang3 - 3.4 + 3.6 + + + + com.google.guava + guava + 20.0 + + + + com.alibaba + fastjson + 1.2.36 + - com.google.code.gson - gson - 2.5 + org.apache.curator + curator-recipes + 2.11.1 + + + netty + io.netty + + + + + org.apache.curator + curator-x-discovery + 2.11.1 + - org.aeonbits.owner - owner - 1.0.9 + redis.clients + jedis + 2.9.0 + com.typesafe config - 1.3.0 + 1.3.1 + + + + org.javassist + javassist + 3.21.0-GA @@ -254,18 +290,14 @@ junit test - - org.slf4j - slf4j-api - - maven-compiler-plugin + 3.6.2 ${java.version} ${java.version} @@ -274,13 +306,14 @@ maven-resources-plugin + 3.0.2 ${java.encoding} - org.apache.maven.plugins maven-surefire-plugin + 2.20 true