init project

This commit is contained in:
jaysunxiao
2021-05-20 14:17:21 +08:00
parent b7f28da485
commit bce060a28e
573 changed files with 84513 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
# Compiled class file
*.class
# Log file
*.log
*.log*
**/logs/
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# 忽略IntelliJ IDEA生成的项目描述文件
.idea
*.iml
**/target/
# 忽略lucene生成的索引文件
**/lucene-data/
# 忽略生成的协议文件
**/jsProtocol/
**/CsProtocol/
**/LuaProtocol/
**/zapp-user/protocol/
**/protobuf/
# 以下是web前端需要忽略的文件
# 忽略npm生成的package描述文件
package-lock.json
# 忽略web前端前端package.jsonbower.json所有依赖的包
**/node_modules/
**/bower_components/
# 忽略npm编译后的文件
**/dist/
# 忽略webapp中部署的文件
**/resources/static/
**/resources/templates/index.html
# test文件
tests/**/coverage/
tests/e2e/reports
+163
View File
@@ -0,0 +1,163 @@
### . zfoo简介
- **性能炸裂,天生异步,Actor设计思想,无锁化设计,基于Spring的MVC式用法的万能RPC框架**
- **极致序列化**,原生集成的目前二进制序列化和反序列化速度最快的 [zfoo protocol](protocol/README.md) 作为网络通讯协议
- **高可拓展性**,单台服务器部署,集群部署,注册中心加集群部署,网关加集群部署,随意搭配
周边生态较为完善的RPC框架
- **普通java项目,spring项目,spring boot项目,一行代码无差别热更新** [hotswap](hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java)
- **Excel配置自动映射和Excel热更新方案** [storage](storage/src/test/java/com/zfoo/storage/ApplicationTest.java)
- **轻量级cpu,内存,硬盘,网络监控,** 拒绝复杂的监控部署 [monitor](monitor/src/test/java/com/zfoo/monitor/ApplicationTest.java)
- mongodb的自动映射框架 [orm](orm/README.md)
- 事件总线 [event](event/src/test/java/com/zfoo/event/ApplicationTest.java)
- 时间任务调度 [scheduler](scheduler/README.md)
### Ⅱ. 适用项目
- 性能需求极高的项目,如直播,游戏等
- 节省研发成本的项目,如想节省,开发,部署,运维成本
- 喜欢 [KISS法则](https://baike.baidu.com/item/KISS原则/3242383) 的项目 ,简单的配置,优雅的代码。
### Ⅲ. 安装和使用
#### 1. 环境要求
**JDK 11+**,可以在 **OpenJDK****Oracle JDK** 无缝切换
```
如果你的机器没有安装JDK 11,最快速的安装方法是在Idea中的Project StructurePlatform SettingsSDKs中直接下载
```
#### 2. [protocol](protocol/README.md) 目前性能最好的Java序列化和反序列化库
```
// zfoo协议注册,只能初始化一次
ProtocolManager.initProtocol(Set.of(ComplexObject.class, ObjectA.class, ObjectB.class));
// 序列化
ProtocolManager.write(byteBuf, complexObject);
// 反序列化
var packet = ProtocolManager.read(byteBuf);
```
#### 3. [net](net/README.md) 目前速度最快的RPC框架
```
// 服务提供者,只需要在方法上加个注解,则自动注册接口
@PacketReceiver
public void atUserInfoAsk(Session session, UserInfoAsk ask) {
}
// 消费者,同步请求远程用户信息,会阻塞当前的线程,慎重考虑使用同步请求
var userInfoAsk = UserInfoAsk.valueOf(userId);
var answer = NetContext.getCosumer().syncAsk(userInfoAsk, UserInfoAnswer.class, userId).packet();
// 消费者,异步请求远程用户信息,不会柱塞当前的线程,异步请求成功过后依然会在userId指定的线程执行逻辑
NetContext.getCosumer()
.asyncAsk(userInfoAsk, UserInfoAnswer.class, userId)
.whenComplete(sm -> {
// do something
);
```
#### 4. [hotswap](hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java) 热更新代码,不需要停止服务器,不需要额外的任何配置,一行代码开启热更新
```
// 传入需要更新的class文件
ConfigUtils.startHotSwapConfig(bytes);
```
#### 5. [orm](orm/README.md) 基于mongodb的自动映射框架
```
// 无需自己写sql和任何配置,直接通过注解定义在数据库中定义一张表
@EntityCache(cacheStrategy = "tenThousand", persister = @Persister("time30s"))
public class UserEntity implements IEntity<Long> {
@Id
private long id;
private String name;
}
// 更新数据库的数据
entityCaches.update(userEntity);
```
#### 6. [event](event/src/test/java/com/zfoo/event/ApplicationTest.java) 事件总线解耦不同模块,提高代码的质量
```
// 接收一个事件,只需要在需要接收事件的方法上加一个注解就会自动监听这个事件
@EventReceiver
public void onNoticeEvent(NoticeEvent event) {
// do something
}
// 抛出一个事件
EventBus.syncSubmit(MyNoticeEvent.valueOf("同步事件"));
EventBus.asyncSubmit(MyNoticeEvent.valueOf("异步事件"));
```
#### 7. [scheduler](scheduler/README.md) 基于cron表达式的定时任务调度框架
````
@Scheduler(cron = "0/1 * * * * ?")
public void cronSchedulerPerSecond() {
// do something
}
````
#### 8. [storage](storage/src/test/java/com/zfoo/storage/ApplicationTest.java) excel和Java类自动映射框架,无需代码和任何工具只需要定义一个和excel对应的类,直接解析excel
```
@Resource
public class StudentResource {
@Id
private int id;
@Index
private String name;
private int age;
}
```
### Ⅳ. 为什么快
- 使用目前性能最好的 [zfoo protocol](protocol/README.md) 作为网关和RPC消息的序列化和反序列化协议
- 无锁化设计和优雅的线程池设计,用户的请求通过网关总能保证请求在同一台服务器的同一条线程去执行,所以就不需要用锁保证并发
- rpc调用天生异步支持,并且保证rpc异步调用结束过后在同一条线程去执行,类似于actor的设计思想,特别适合对性能有极高需求的场景,如直播聊天,游戏等等。
- 数据库使用高性能分布式数据库 [mongodb](https://github.com/mongodb/mongo) ,在其之上使用 [caffeine](https://github.com/ben-manes/caffeine)
设计了 [zfoo orm](protocol/README.md) 二级缓存,充分释放数据库压力
- 服务器热更新和监控无需代码和额外工具,直接内置在程序里,解放运维生产力
- 使用MVC设计模式,规范开发,保证代码质量,高效执行
### . 提交规范
- Java项目格式化代码的方式采用IntelliJ Idea默认的格式化
- 所有的接口的参数和方法返回的参数都默认是非空的,如果参数可空,需要加上org.springframework.lang.Nullable注解
- 代码提交的时候需要固定格式,如给net增加了一个功能,feat[net]: 功能描述,下面给出了一些常用的格式
```
feat[module]: 新增某一项功能
perf[module]: 优化了模块代码或者优化了什么功能
fix[module]: 修改了什么bug
test[module]: 测试了什么东西
doc[module]: 增加了什么文档
del[module]: 删除了某些功能或者无用代码
```
### Ⅵ. License
zfoo使用 [Apache License Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
```
Copyright (C) 2020 The zfoo 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.
```
+260
View File
@@ -0,0 +1,260 @@
#!/bin/bash
# 服务器部署脚本
# doc:
# 1.在Idea中下载安装阿里云Alibaba Cloud插件
# 2.使用Ecs的AccessKeyId和AccessSecurity登录,服务器信息里有具体密码
# 3.在upload选项中选择需要上传的jar包,location默认为根路径/
# command中输入脚本运行命令:sh /deploy.sh stopUpdateStart /usr/local/zapp/user/zapp-user-1.0.jar /usr/local/zapp/user notPrint
# 注:
# /deploy.sh脚本包括了,启动服务器,优雅停止java服务器,更新文件,重新启动服务器的脚本,对应四个命令:start|stop|update|stopUpdateStart。
# 其中stopUpdateStart会按照顺序执行,stopupdatestart命令。
# usage:
# sh deploy.sh start /usr/local/zapp/gateway/zapp-gateway-1.0.jar /usr/local/zapp/gateway
# sh deploy.sh stop gateway
# sh deploy.sh update /usr/local/zapp/gateway/zapp-gateway-1.0.jar
# sh deploy.sh stopUpdateStart /usr/local/zapp/gateway/zapp-gateway-1.0.jar /usr/local/zapp/gateway
# start启动服务器,/usr/local/zapp/gateway/zapp-gateway-1.0.jar是jar包的绝对路径,/usr/local/zapp/gateway是日志的输出路径
# stop关闭服务器,会使用jps | grep gateway,抓取需要关闭的java进程
# update更新jar包,默认会使用根路径/zapp-gateway-1.0.jar下的jar包去更新/usr/local/zapp/gateway/zapp-gateway-1.0.jar路径的jar包
# stopUpdateStart,按顺序执行命令stopupdatestart
# 相关参数命令
# java -XX:+PrintFlagsInitial,查看jvm全部参数的默认值
#
# @author jaysunxiao
# @version 1.0
*/
if [ $# -lt 1 ]; then
echo "deploy.sh脚本使用错误,命令参数不合法"
echo "usage: sh deploy.sh start|stop|update|stopUpdateStart"
exit 1
fi
command=${1}
function waitAllProcessesExit() {
while true; do
local runningProcesses
runningProcesses=$(jps -lvm | grep ${1})
if [ -n "${runningProcesses}" ]; then
echo "正在关闭以下Java进程${1}"
echo "${runningProcesses}"
sleep 3
else
echo "已正常关闭所有${1}进程"
return
fi
done
}
# 停止所有进程,不包括login
function stop() {
echo "-------------------------------------------------------------------------------------------------------------------------------------->stop"
local pids
pids=$(jps | grep ${1} | awk '{print $1}' | paste -d " " -s)
if [ -z "${pids}" ]; then
echo "没有找到任何包含${1}关键字的Java进程"
return
fi
echo "----------------------------------------------------------------------------------------->cpu"
uptime
echo "注:"
echo "uptime查看cpu的负载情况,load avarage分别是1分钟,5分钟,15分钟内系统的load值"
echo "一般load值不大于3,我们就认为它的负载是正常的,大于了3就要想办法降低系统的负载"
echo "----------------------------------------------------------------------------------------->内存"
free -m
echo "注:"
echo "free查看内存的使用情况,重点关注swap内存使用,swap大表示物理内存不够用,这时候容易导致OOM异常"
echo "----------------------------------------------------------------------------------------->磁盘"
df -h
echo "注:"
echo "df查看磁盘的使用情况,应该特别关注日志和数据库的挂载路径的使用情况"
echo "----------------------------------------------------------------------------------------->网络"
sar -n DEV 1 2
echo "注:"
echo "sar查看网络的使用情况,可以通过网络设备的吞吐量,判断网络设备是否已经饱和。"
echo "----------------------------------------------------------------------------------------->系统日志"
dmesg | tail -n 20
echo "注:"
echo "df查看系统日志的最后20行,主要看有没有严重的系统问题"
echo "----------------------------------------------------------------------------------------->jmap实例数量"
for pid in ${pids}; do
echo "${pid}->进程的class实例数量前20信息"
jmap -histo ${pid} | head -n 20
echo -e "\n"
done
echo "----------------------------------------------------------------------------------------->开始执行终止Java进程命令"
echo "kill -15 $pids"
kill -15 ${pids}
waitAllProcessesExit ${1}
}
# 启动一个java进程,必须指定jar路径和jar名称
# -n为notEmpty-z为empty
function start() {
echo "-------------------------------------------------------------------------------------------------------------------------------------->start"
local jarPath=${1}
local logPath=${2}
if [ -z "${jarPath}" ]; then
echo "启动的路径不能为空"
echo "usage: sh deploy.sh start jarPath logPath"
exit 1
fi
if [ -z "${logPath}" ]; then
echo "启动的jar包名称不能为空"
echo "usage: sh deploy.sh start jarPath logPath"
exit 1
fi
mkdir -p "${logPath}/log"
cd ${logPath}
# -XX:+AlwaysPreTouch,并置零内存页面,可能令得启动时慢上一点,但后面访问时会更流畅,比如页面会连续分配
nohup java -XX:+UseG1GC -XX:InitialHeapSize=4g -XX:MaxHeapSize=4g -XX:MaxMetaspaceSize=256m -XX:AutoBoxCacheMax=20000 -XX:+UseStringDeduplication -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -Djdk.attach.allowAttachSelf=true -Dspring.profiles.active=pro -Dfile.encoding=UTF-8 -jar ${jarPath} >/dev/null 2>&1 &
# 如果没有info的log,则等待一秒钟
local infoLog
infoLog=$(ls ./log | grep info)
if [ -z "${infoLog}" ]; then
sleep 1
fi
if [ -z "${3}" ]; then
tailf log/info.log
else
sleep 4
local jarName=${jarPath##*/}
local runningProcesses
runningProcesses=$(jps -lvm | grep ${jarName})
echo "----------------------------------------------------------------------------------------->jps信息"
echo "${runningProcesses}"
sleep 4
echo "----------------------------------------------------------------------------------------->jstat编译统计信息"
local pids
pids=$(jps | grep ${jarName} | awk '{print $1}' | paste -d " " -s)
for pid in ${pids}; do
echo "${pid}->进程的编译统计信息"
jstat -compiler ${pid}
echo -e "\n"
done
sleep 6
echo "----------------------------------------------------------------------------------------->启动信息"
tail -n 40 log/info.log
exit 0
fi
}
# 更新jar包
function update() {
echo "-------------------------------------------------------------------------------------------------------------------------------------->update"
local jarPath=${1}
if [ -z "${jarPath}" ]; then
echo "需要被更新的jar路径不能为空"
echo "usage: sh deploy.sh update jarPath"
exit 1
fi
local jarFilePath=${jarPath%/*}
local jarName=${jarPath##*/}
# echo ${jarFilePath}
# echo ${jarName}
echo "更新之前源文件和目标文件信息:"
local fileInfo=$(ls -lh "/${jarName}")
echo "${fileInfo}"
if [ -f "${jarPath}" ]; then
fileInfo=$(ls -lh ${jarPath})
echo "${fileInfo}"
fi
echo -e "\n\n"
rm -rf ${jarPath}
mkdir -p ${jarFilePath}
cp "/${jarName}" ${jarFilePath}
echo "更新之后源文件和目标文件信息:"
fileInfo=$(ls -lh "/${jarName}")
echo "${fileInfo}"
fileInfo=$(ls -lh ${jarPath})
echo "${fileInfo}"
}
function stopUpdateStart() {
echo "-------------------------------------------------------------------------------------------------------------------------------------->stopUpdateStart"
local jarPath=${1}
local logPath=${2}
if [ -z "${jarPath}" ]; then
echo "启动的路径不能为空"
echo "usage: sh deploy.sh start jarPath logPath"
exit 1
fi
if [ -z "${logPath}" ]; then
echo "启动的jar包名称不能为空"
echo "usage: sh deploy.sh start jarPath logPath"
exit 1
fi
echo "开始按顺序执行任务: stop -> update -> start"
echo -e "\n\n"
local jarFilePath=${jarPath%/*}
local jarName=${jarPath##*/}
# 先停止
stop ${jarName}
# 再更新
update ${jarPath}
# 最后启动
start ${jarPath} ${logPath} ${3}
}
# Linux性能调优
function optimizeLinux() {
echo "-------------------------------------------------------------------------------------------------------------------------------------->性能调优"
}
# 立即执行调优
optimizeLinux
case ${command} in
"start")
start ${2} ${3}
;;
"stop")
stop ${2}
;;
"update")
update ${2}
;;
"stopUpdateStart")
stopUpdateStart ${2} ${3} ${4}
;;
*)
echo "命令无法识别: ${1}"
echo "usage: sh deploy.sh start|stop|udpate|stopUpdateStart"
;;
esac
exit 0
+244
View File
@@ -0,0 +1,244 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zfoo</groupId>
<artifactId>event</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<properties>
<!-- 本项目的其它module版本号 -->
<zfoo.event.version>3.0</zfoo.event.version>
<zfoo.hotswap.version>3.0</zfoo.hotswap.version>
<zfoo.monitor.version>3.0</zfoo.monitor.version>
<zfoo.net.version>3.0</zfoo.net.version>
<zfoo.scheduler.version>3.0</zfoo.scheduler.version>
<zfoo.storage.version>3.0</zfoo.storage.version>
<zfoo.orm.version>3.0</zfoo.orm.version>
<zfoo.protocol.version>3.0</zfoo.protocol.version>
<zfoo.util.version>3.0</zfoo.util.version>
<!-- 核心spring框架 -->
<spring.version>5.3.4</spring.version>
<spring.boot.version>2.4.3</spring.boot.version>
<!-- 工具包 -->
<commons-codec.version>1.15</commons-codec.version>
<commons-io.version>2.8.0</commons-io.version>
<commons-collections.version>4.4</commons-collections.version>
<commons-lang.version>3.12.0</commons-lang.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<commons-logging.version>1.2</commons-logging.version>
<commons-log4j.version>2.14.0</commons-log4j.version>
<httpcomponents.version>4.5.13</httpcomponents.version>
<httpcore.version>4.4.14</httpcore.version>
<google.guava.version>30.1-jre</google.guava.version>
<google.protobuf.version>3.9.1</google.protobuf.version>
<google.gson.version>2.8.6</google.gson.version>
<kryo.version>5.0.3</kryo.version>
<caffeine.version>2.8.8</caffeine.version>
<jctools.version>3.2.0</jctools.version>
<hutool.version>5.5.9</hutool.version>
<oshi.version>5.7.0</oshi.version>
<snakeyaml.version>1.28</snakeyaml.version>
<!-- json和xml解析包 -->
<jackson.version>2.12.1</jackson.version>
<fastjson.version>1.2.51</fastjson.version>
<!-- office文档解析包 -->
<poi.version>4.1.2</poi.version>
<!-- 字节码增强 -->
<javassist.version>3.27.0-GA</javassist.version>
<bytebuddy.version>1.10.22</bytebuddy.version>
<!-- 网络通讯框架 -->
<netty.version>4.1.63.Final</netty.version>
<!-- 分布式zookeeper核心依赖包 -->
<zookeeper.version>3.6.1</zookeeper.version>
<curator.version>5.1.0</curator.version>
<!-- 数据库和缓存 -->
<mongodb-driver-sync.version>4.2.1</mongodb-driver-sync.version>
<jedis.version>3.3.0</jedis.version>
<!-- 消息队列中间件 -->
<rocketmq.version>4.5.2</rocketmq.version>
<!-- elastic search 中间件 -->
<elastic.search.version>7.9.3</elastic.search.version>
<elastic.search.spring.version>4.1.5</elastic.search.spring.version>
<lucene.version>8.6.2</lucene.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.13.1</junit.version>
<!-- java版本和文件编码 -->
<java.version>11</java.version>
<file.encoding>UTF-8</file.encoding>
<jakarta.version>1.3.5</jakarta.version>
<!-- maven核心插件 -->
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<versions-maven-plugin.version>2.8.1</versions-maven-plugin.version>
<project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
<maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
</properties>
<dependencies>
<!-- 依赖的utils类库 -->
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>util</artifactId>
<version>${zfoo.util.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
<!-- 动态生成二进制字节码的javassist类库 -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
<!-- 依赖的Spring模块类库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- logback核心包 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- logback的sl4j的实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 依赖的测试库 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<!-- 清理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${file.encoding}</encoding>
</configuration>
</plugin>
<!-- resource资源管理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>${file.encoding}</encoding>
<outputDirectory>${project.build.directory}/resource</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkMode>once</forkMode>
<threadCount>10</threadCount>
<argLine>-Dfile.encoding=${file.encoding}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event;
import com.zfoo.event.manager.EventBus;
import com.zfoo.event.schema.EventRegisterProcessor;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.util.ThreadUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import java.lang.reflect.Field;
import java.util.concurrent.ExecutorService;
/**
* @author jaysunxiao
* @version 3.0
*/
public class EventContext implements ApplicationListener<ApplicationContextEvent>, Ordered {
private static final Logger logger = LoggerFactory.getLogger(EventContext.class);
private static EventContext instance;
private ApplicationContext applicationContext;
public static EventContext getEventContext() {
return instance;
}
public static ApplicationContext getApplicationContext() {
return instance.applicationContext;
}
public synchronized static void shutdown() {
try {
Field field = EventBus.class.getDeclaredField("executors");
ReflectionUtils.makeAccessible(field);
var executors = (ExecutorService[]) ReflectionUtils.getField(field, null);
for (ExecutorService executor : executors) {
ThreadUtils.shutdown(executor);
}
} catch (Throwable e) {
logger.error("Event thread pool failed shutdown: " + ExceptionUtils.getMessage(e));
return;
}
logger.info("Event shutdown gracefully.");
}
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
if (instance != null) {
return;
}
// 初始化上下文
EventContext.instance = this;
instance.applicationContext = event.getApplicationContext();
var beanNames = applicationContext.getBeanDefinitionNames();
var processor = applicationContext.getBean(EventRegisterProcessor.class);
for (var beanName : beanNames) {
processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName);
}
} else if (event instanceof ContextClosedEvent) {
shutdown();
ThreadUtils.shutdownForkJoinPool();
}
}
@Override
public int getOrder() {
return 30;
}
}
@@ -0,0 +1,188 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.manager;
import com.zfoo.event.model.anno.EventReceiver;
import com.zfoo.event.model.event.IEvent;
import com.zfoo.event.model.vo.EnhanceUtils;
import com.zfoo.event.model.vo.EventReceiverDefinition;
import com.zfoo.event.model.vo.IEventReceiver;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.math.RandomUtils;
import io.netty.util.concurrent.FastThreadLocalThread;
import javassist.CannotCompileException;
import javassist.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class EventBus {
private static final Logger logger = LoggerFactory.getLogger(EventBus.class);
// 线程池的大小
private static final int EXECUTORS_SIZE = Runtime.getRuntime().availableProcessors() * 2;
private static final ExecutorService[] executors;
private static final Map<Class<? extends IEvent>, List<IEventReceiver>> receiverMap;
static {
receiverMap = new HashMap<>();
executors = new ExecutorService[EXECUTORS_SIZE];
for (int i = 0; i < executors.length; i++) {
var namedThreadFactory = new EventThreadFactory();
executors[i] = Executors.newSingleThreadExecutor(namedThreadFactory);
}
}
private static class EventThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
EventThreadFactory() {
var s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "event-p" + poolNumber.getAndIncrement() + "-t";
}
@Override
public Thread newThread(Runnable runnable) {
var t = new FastThreadLocalThread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
t.setUncaughtExceptionHandler((thread, e) -> logger.error(thread.toString(), e));
return t;
}
}
/**
* 同步抛出一个事件,会在当前线程中运行
*
* @param event 需要抛出的事件
*/
public static void syncSubmit(IEvent event) {
var list = receiverMap.get(event.getClass());
if (CollectionUtils.isEmpty(list)) {
return;
}
doSubmit(event, list);
}
/**
* 异步抛出一个事件,事件不在同一个线程中处理
*
* @param event 需要抛出的事件
*/
public static void asyncSubmit(IEvent event) {
var list = receiverMap.get(event.getClass());
if (CollectionUtils.isEmpty(list)) {
return;
}
executors[Math.abs(event.threadId() % EXECUTORS_SIZE)].execute(new Runnable() {
@Override
public void run() {
doSubmit(event, list);
}
});
}
/**
* 随机获取一个线程池
*/
public static Executor asyncExecute() {
return executors[RandomUtils.randomInt(EXECUTORS_SIZE)];
}
private static void doSubmit(IEvent event, List<IEventReceiver> listReceiver) {
for (var receiver : listReceiver) {
try {
receiver.invoke(event);
} catch (Exception e) {
logger.error("eventBus未知exception异常", e);
} catch (Throwable t) {
logger.error("eventBus未知error异常", t);
}
}
}
public static void registerEventReceiver(Object bean) {
try {
var clazz = bean.getClass();
var methods = ReflectionUtils.getMethodsByAnnoInPOJOClass(clazz, EventReceiver.class);
for (var method : methods) {
var paramClazzs = method.getParameterTypes();
if (paramClazzs.length != 1) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must have one parameter!", bean.getClass().getName(), method.getName()));
}
if (!IEvent.class.isAssignableFrom(paramClazzs[0])) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must have one [IEvent] type parameter!", bean.getClass().getName(), method.getName()));
}
var eventClazz = (Class<? extends IEvent>) paramClazzs[0];
var eventName = eventClazz.getCanonicalName();
var methodName = method.getName();
if (!Modifier.isPublic(method.getModifiers())) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] [event:{}] must use 'public' as modifier!", bean.getClass().getName(), methodName, eventName));
}
if (Modifier.isStatic(method.getModifiers())) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] [event:{}] can not use 'static' as modifier!", bean.getClass().getName(), methodName, eventName));
}
var expectedMethodName = StringUtils.format("on{}", eventClazz.getSimpleName());
if (!methodName.equals(expectedMethodName)) {
throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] [event:{}] expects '{}' as method name!"
, bean.getClass().getName(), methodName, eventName, expectedMethodName));
}
var receiverDefinition = new EventReceiverDefinition(bean, method, eventClazz);
if (!receiverMap.containsKey(eventClazz)) {
receiverMap.put(eventClazz, new LinkedList<>());
}
var enhanceReceiverDefinition = EnhanceUtils.createEventReceiver(receiverDefinition);
receiverMap.get(eventClazz).add(enhanceReceiverDefinition);
}
} catch (NotFoundException | CannotCompileException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.model.anno;
import java.lang.annotation.*;
/**
* 接受时间注解
*
* @author jaysunxiao
* @version 3.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface EventReceiver {
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.model.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
/**
* 应用启动事件,这个使用spring自带的事件机制,自研的event事件仅用在业务逻辑
*
* @author jaysunxiao
* @version 3.0
*/
public class AppStartEvent extends ApplicationContextEvent {
public AppStartEvent(ApplicationContext context) {
super(context);
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.model.event;
import com.zfoo.util.math.RandomUtils;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IEvent {
/**
* 这个返回的是一个用于确定事件在EventBus中的哪个线程池的执行的一个参数,只有异步事件才会有作用
* <p>
* 比如cpu是四核,那么EventBus中的executors线程池的数量为8个,通过取余可以确定异步事件在哪个线程池中执行。
* 如果参数是0,0 % 8 = 0,那么异步事件最终会在executors[0]表示的线程池中执行
* 如果参数是1,1 % 8 = 1,那么异步事件最终会在executors[1]表示的线程池中执行
* 如果参数是8,8 % 8 = 0,那么异步事件最终会在executors[0]表示的线程池中执行
* 如果参数是9,9 % 8 = 1,那么异步事件最终会在executors[1]表示的线程池中执行
* <p>
* 通过返回的参数,可以轻松控制异步事件在哪个线程池中去执行。
* 因为EventBus中的线程池都是单线程线程池,如果将一些异步事件放在同一个线程池中执行,可以不用加锁,提高程序运行的效率。
* <p>
* 默认返回一个随机数,这就导致如果不重写这个方法,那么异步事件有可能会在EventBus中的任何一条线程池中去执行。
*
* @return 线程池的执行的一个参数
*/
default int threadId() {
return RandomUtils.randomInt();
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.model.vo;
import com.zfoo.event.model.event.IEvent;
import com.zfoo.event.schema.NamespaceHandler;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.security.IdUtils;
import javassist.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class EnhanceUtils {
static {
// 适配Tomcat,因为Tomcat不是用的默认的类加载器,而Javaassist用的是默认的加载器
var classArray = new Class<?>[]{
IEventReceiver.class,
IEvent.class
};
var classPool = ClassPool.getDefault();
for (var clazz : classArray) {
if (classPool.find(clazz.getCanonicalName()) == null) {
ClassClassPath classPath = new ClassClassPath(clazz);
classPool.insertClassPath(classPath);
}
}
}
public static IEventReceiver createEventReceiver(EventReceiverDefinition definition) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
var classPool = ClassPool.getDefault();
Object bean = definition.getBean();
Method method = definition.getMethod();
Class<?> clazz = definition.getEventClazz();
// 定义类名称
CtClass enhanceClazz = classPool.makeClass(EnhanceUtils.class.getCanonicalName() + StringUtils.capitalize(NamespaceHandler.EVENT) + IdUtils.getLocalIntId());
enhanceClazz.addInterface(classPool.get(IEventReceiver.class.getCanonicalName()));
// 定义类中的一个成员
CtField field = new CtField(classPool.get(bean.getClass().getCanonicalName()), "bean", enhanceClazz);
field.setModifiers(Modifier.PRIVATE);
enhanceClazz.addField(field);
// 定义类的构造器
CtConstructor constructor = new CtConstructor(classPool.get(new String[]{bean.getClass().getCanonicalName()}), enhanceClazz);
constructor.setBody("{this.bean=$1;}");
constructor.setModifiers(Modifier.PUBLIC);
enhanceClazz.addConstructor(constructor);
// 定义类实现的接口方法
CtMethod invokeMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "invoke", classPool.get(new String[]{IEvent.class.getCanonicalName()}), enhanceClazz);
invokeMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL);
String invokeMethodBody = "{this.bean." + method.getName() + "((" + clazz.getCanonicalName() + ")$1);}";// 强制类型转换,转换为具体的Event类型的类型
invokeMethod.setBody(invokeMethodBody);
enhanceClazz.addMethod(invokeMethod);
// 释放缓存
enhanceClazz.detach();
Class<?> resultClazz = enhanceClazz.toClass(IEventReceiver.class);
Constructor<?> resultConstructor = resultClazz.getConstructor(bean.getClass());
IEventReceiver receiver = (IEventReceiver) resultConstructor.newInstance(bean);
return receiver;
}
}
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.model.vo;
import com.zfoo.event.model.event.IEvent;
import com.zfoo.protocol.util.ReflectionUtils;
import java.lang.reflect.Method;
/**
* @author jaysunxiao
* @version 3.0
*/
public class EventReceiverDefinition implements IEventReceiver {
private Object bean;
// 被ReceiveEvent注解的方法
private Method method;
// 接收的参数Class
private Class<? extends IEvent> eventClazz;
public EventReceiverDefinition(Object bean, Method method, Class<? extends IEvent> eventClazz) {
this.bean = bean;
this.method = method;
this.eventClazz = eventClazz;
ReflectionUtils.makeAccessible(this.method);
}
@Override
public void invoke(IEvent event) {
ReflectionUtils.invokeMethod(bean, method, event);
}
public Object getBean() {
return bean;
}
public void setBean(Object bean) {
this.bean = bean;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Class<? extends IEvent> getEventClazz() {
return eventClazz;
}
public void setEventClazz(Class<? extends IEvent> eventClazz) {
this.eventClazz = eventClazz;
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.model.vo;
import com.zfoo.event.model.event.IEvent;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IEventReceiver {
void invoke(IEvent event);
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.schema;
import com.zfoo.event.EventContext;
import com.zfoo.protocol.util.StringUtils;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* @author jaysunxiao
* @version 3.0
*/
public class EventDefinitionParser implements BeanDefinitionParser {
@Override
public AbstractBeanDefinition parse(Element element, ParserContext parserContext) {
Class<?> clazz;
String name;
BeanDefinitionBuilder builder;
// 注册EventSpringContext
clazz = EventContext.class;
name = StringUtils.uncapitalize(clazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition());
// 注册EventRegisterProcessorevent事件处理
clazz = EventRegisterProcessor.class;
name = StringUtils.uncapitalize(clazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition());
return builder.getBeanDefinition();
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.schema;
import com.zfoo.event.EventContext;
import com.zfoo.event.manager.EventBus;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author jaysunxiao
* @version 3.0
*/
public class EventRegisterProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (EventContext.getEventContext() == null) {
return bean;
}
EventBus.registerEventReceiver(bean);
return bean;
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event.schema;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NamespaceHandler extends NamespaceHandlerSupport {
public static final String EVENT = "event";
@Override
public void init() {
registerBeanDefinitionParser(EVENT, new EventDefinitionParser());
}
}
@@ -0,0 +1 @@
http\://www.zfoo.com/schema/event=com.zfoo.event.schema.NamespaceHandler
@@ -0,0 +1 @@
http\://www.zfoo.com/schema/event-1.0.xsd=event-1.0.xsd
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.zfoo.com/schema/event"
targetNamespace="http://www.zfoo.com/schema/event"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="event">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event;
import com.zfoo.event.manager.EventBus;
import com.zfoo.util.ThreadUtils;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author jaysunxiao
* @version 3.0
*/
@Ignore
public class ApplicationTest {
// 事件总线教程
@Test
public void startEventTest() {
// 加载配置文件,配置文件中必须引入event
var context = new ClassPathXmlApplicationContext("application.xml");
// 事件的接受需要在被Spring管理的bean的方法上加上@EventReceiver注解,即可自动注册事件的监听
// 参考MyController1中的标准注册方法
// 抛出同步事件,事件会被当前线程立刻执行,注意日志打印的线程号
EventBus.syncSubmit(MyNoticeEvent.valueOf("同步事件"));
// 抛出异步事件,事件会被不会立刻执行,注意日志打印的线程号
EventBus.asyncSubmit(MyNoticeEvent.valueOf("异步事件"));
// 睡眠3秒,等待异步事件执行完
ThreadUtils.sleep(3000);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event;
import com.zfoo.event.model.anno.EventReceiver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author jaysunxiao
* @version 3.0
*/
@Component
public class MyController1 {
private static final Logger logger = LoggerFactory.getLogger(MyController1.class);
@EventReceiver
public void onMyNoticeEvent(MyNoticeEvent event) {
logger.info("方法1收到事件:" + event.getMessage());
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event;
import com.zfoo.event.model.anno.EventReceiver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @author jaysunxiao
* @version 3.0
*/
@Component
public class MyController2 {
private static final Logger logger = LoggerFactory.getLogger(MyController2.class);
/**
* 同一个事件可以被重复注册和接受
*/
@EventReceiver
public void onMyNoticeEvent(MyNoticeEvent event) {
logger.info("方法2收到事件:" + event.getMessage());
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.event;
import com.zfoo.event.model.event.IEvent;
/**
* @author jaysunxiao
* @version 3.0
*/
public class MyNoticeEvent implements IEvent {
private String message;
public static MyNoticeEvent valueOf(String message) {
var event = new MyNoticeEvent();
event.setMessage(message);
return event;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:event="http://www.zfoo.com/schema/event"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.zfoo.com/schema/event
http://www.zfoo.com/schema/event-1.0.xsd">
<context:component-scan base-package="com.zfoo.event"/>
<event:event id="eventBus"/>
</beans>
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<contextName>com.zfoo.event</contextName>
<property name="LOG_HOME" value="log/event"/>
<property name="PATTERN_FILE"
value="%d{yyyy-MM-dd HH:mm:ss} [%5level] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<property name="PATTERN_CONSOLE"
value="%d{yyyy-MM-dd HH:mm:ss} [%highlight(%5level)] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<!-- 负责写日志,控制台日志,会打印所有的包的所有级别日志 -->
<appender name="zfoo_console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN_CONSOLE}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 根logger -->
<root level="info">
<appender-ref ref="zfoo_console"/>
</root>
<!--
- 1.name:包名或类名,用来指定受此logger约束的某一个包或者具体的某一个类
- 2.未设置打印级别,所以继承他的上级<root>的日志级别“DEBUG”
- 3.未设置additivity,默认为true,将此logger的打印信息向上级传递;
- 4.未设置appender,此logger本身不打印任何信息,级别为“DEBUG”及大于“DEBUG”的日志信息传递给root,
- root接到下级传递的信息,交给已经配置好的名为“STDOUT”的appender处理,“STDOUT”appender将信息打印到控制台;
-->
<logger name="ch.qos.logback" level="info"/>
<!--*******************************************Spring********************************************************-->
<!--logger中的name是指代码的包名或类名,路径要写全,可以配置不同包中的日志输出到不同的文件中。level是日志输出级别 -->
<!--过滤掉spring的一些无用的DEBUG信息-->
<logger name="org.springframework" level="info"/>
<!-- additivity="false"表示不继承父logger的配置和父类没有关系-->
<logger name="org.springframework.core" level="info"/>
<!--*******************************************Netty*********************************************************-->
<logger name="io.netty" level="info"/>
</configuration>
Binary file not shown.

After

Width:  |  Height:  |  Size: 992 KiB

+252
View File
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zfoo</groupId>
<artifactId>hotswap</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<properties>
<!-- 本项目的其它module版本号 -->
<zfoo.event.version>3.0</zfoo.event.version>
<zfoo.hotswap.version>3.0</zfoo.hotswap.version>
<zfoo.monitor.version>3.0</zfoo.monitor.version>
<zfoo.net.version>3.0</zfoo.net.version>
<zfoo.scheduler.version>3.0</zfoo.scheduler.version>
<zfoo.storage.version>3.0</zfoo.storage.version>
<zfoo.orm.version>3.0</zfoo.orm.version>
<zfoo.protocol.version>3.0</zfoo.protocol.version>
<zfoo.util.version>3.0</zfoo.util.version>
<!-- 核心spring框架 -->
<spring.version>5.3.4</spring.version>
<spring.boot.version>2.4.3</spring.boot.version>
<!-- 工具包 -->
<commons-codec.version>1.15</commons-codec.version>
<commons-io.version>2.8.0</commons-io.version>
<commons-collections.version>4.4</commons-collections.version>
<commons-lang.version>3.12.0</commons-lang.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<commons-logging.version>1.2</commons-logging.version>
<commons-log4j.version>2.14.0</commons-log4j.version>
<httpcomponents.version>4.5.13</httpcomponents.version>
<httpcore.version>4.4.14</httpcore.version>
<google.guava.version>30.1-jre</google.guava.version>
<google.protobuf.version>3.9.1</google.protobuf.version>
<google.gson.version>2.8.6</google.gson.version>
<kryo.version>5.0.3</kryo.version>
<caffeine.version>2.8.8</caffeine.version>
<jctools.version>3.2.0</jctools.version>
<hutool.version>5.5.9</hutool.version>
<oshi.version>5.7.0</oshi.version>
<snakeyaml.version>1.28</snakeyaml.version>
<!-- json和xml解析包 -->
<jackson.version>2.12.1</jackson.version>
<fastjson.version>1.2.51</fastjson.version>
<!-- office文档解析包 -->
<poi.version>4.1.2</poi.version>
<!-- 字节码增强 -->
<javassist.version>3.27.0-GA</javassist.version>
<bytebuddy.version>1.10.22</bytebuddy.version>
<!-- 网络通讯框架 -->
<netty.version>4.1.63.Final</netty.version>
<!-- 分布式zookeeper核心依赖包 -->
<zookeeper.version>3.6.1</zookeeper.version>
<curator.version>5.1.0</curator.version>
<!-- 数据库和缓存 -->
<mongodb-driver-sync.version>4.2.1</mongodb-driver-sync.version>
<jedis.version>3.3.0</jedis.version>
<!-- 消息队列中间件 -->
<rocketmq.version>4.5.2</rocketmq.version>
<!-- elastic search 中间件 -->
<elastic.search.version>7.9.3</elastic.search.version>
<elastic.search.spring.version>4.1.5</elastic.search.spring.version>
<lucene.version>8.6.2</lucene.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.13.1</junit.version>
<!-- java版本和文件编码 -->
<java.version>11</java.version>
<file.encoding>UTF-8</file.encoding>
<jakarta.version>1.3.5</jakarta.version>
<!-- maven核心插件 -->
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<versions-maven-plugin.version>2.8.1</versions-maven-plugin.version>
<project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
<maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
</properties>
<dependencies>
<!-- 依赖的util类库 -->
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>util</artifactId>
<version>${zfoo.util.version}</version>
</dependency>
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>scheduler</artifactId>
<version>${zfoo.scheduler.version}</version>
</dependency>
<!-- 动态生成二进制字节码的javassist类库 -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<!-- slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- logback核心包 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- logback的sl4j的实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<!-- 清理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${file.encoding}</encoding>
</configuration>
</plugin>
<!-- resource资源管理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>${file.encoding}</encoding>
<outputDirectory>${project.build.directory}/resource</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkMode>once</forkMode>
<threadCount>10</threadCount>
<argLine>-Dfile.encoding=${file.encoding}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,26 @@
package com.zfoo.hotswap;
import com.zfoo.hotswap.manager.HotSwapManager;
import com.zfoo.hotswap.service.HotSwapServiceMBean;
/**
* @author jaysunxiao
* @version 3.0
*/
public class HotSwapContext {
private static final HotSwapContext HOT_SWAP_CONTEXT = new HotSwapContext();
private HotSwapContext() {
}
public static HotSwapServiceMBean getHotSwapService() {
return HotSwapServiceMBean.getSingleInstance();
}
public static HotSwapManager getHotSwapManager() {
return HotSwapManager.getInstance();
}
}
@@ -0,0 +1,67 @@
package com.zfoo.hotswap.agent;
import com.zfoo.protocol.util.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
/**
* 使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,
* 甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,
* 这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
*
* @author jaysunxiao
*/
public class HotSwapAgent {
private static final String HOT_SWAP_MANAGER = "com.zfoo.hotswap.manager.HotSwapManager";
private static final String UPDATE_BYTES_FIELD = "updateBytes";
private static final String EXCEPTION = "exception";
/*
Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。 与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足:
在manifest中指定Agent-Class属性,值为代理类全路径
代理类需要提供public static void agentmain(String args, Instrumentation inst)或public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。
不过如此设计的再运行时进行代理有个问题——如何在应用程序启动之后再开启代理程序呢? JDK6中提供了Java Tools API,其中Attach API可以满足这个需求。
Attach API中的VirtualMachine代表一个运行中的VM。其提供了loadAgent()方法,可以在运行时动态加载一个代理jar。
*/
public static void agentmain(String args, Instrumentation inst) {
Class<?> clazz = null;
DataInputStream dis = null;
try {
clazz = Class.forName(HOT_SWAP_MANAGER);
byte[] byteArray = (byte[]) clazz.getField(UPDATE_BYTES_FIELD).get(null);
dis = new DataInputStream(new ByteArrayInputStream(byteArray));
int len = dis.readInt();
ClassDefinition[] classDefs = new ClassDefinition[len];
for (int i = 0; i < len; i++) {
//类名
String className = dis.readUTF();
//类字节码
byte[] classBytes = new byte[dis.readInt()];
dis.readFully(classBytes);
classDefs[i] = new ClassDefinition(Class.forName(className), classBytes);
}
//开始更新
inst.redefineClasses(classDefs);
//更新成功
} catch (Exception e) {
if (clazz != null) {
try {
clazz.getField(EXCEPTION).set(null, e);
} catch (Exception e1) {
throw new RuntimeException(e1);
}
}
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(dis);
}
}
}
@@ -0,0 +1,34 @@
package com.zfoo.hotswap.manager;
import com.zfoo.hotswap.model.ClassFileDef;
import java.util.HashMap;
import java.util.Map;
/**
* @author jaysunxiao
* @version 3.0
*/
public class HotSwapManager implements IHotSwapManager {
private static final HotSwapManager HOT_SWAP_MANAGER = new HotSwapManager();
private static Map<String, ClassFileDef> classFileDefMap = new HashMap<>();
public static volatile byte[] updateBytes;
public static volatile Exception exception;
private HotSwapManager() {
}
public static HotSwapManager getInstance() {
return HOT_SWAP_MANAGER;
}
@Override
public Map<String, ClassFileDef> getClassFileDefMap() {
return classFileDefMap;
}
}
@@ -0,0 +1,15 @@
package com.zfoo.hotswap.manager;
import com.zfoo.hotswap.model.ClassFileDef;
import java.util.Map;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IHotSwapManager {
Map<String, ClassFileDef> getClassFileDefMap();
}
@@ -0,0 +1,64 @@
package com.zfoo.hotswap.model;
import com.zfoo.util.security.MD5Utils;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ClassFileDef {
private String path;
private String className;
private byte[] data;
private long lastModifyTime;
private String md5;
public ClassFileDef(String className, String path, long lastModifyTime, byte[] data) {
this.className = className;
this.path = path;
this.lastModifyTime = lastModifyTime;
this.data = data;
this.md5 = MD5Utils.bytesToMD5(data);
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
public long getLastModifyTime() {
return lastModifyTime;
}
public void setLastModifyTime(long lastModifyTime) {
this.lastModifyTime = lastModifyTime;
}
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
}
@@ -0,0 +1,224 @@
package com.zfoo.hotswap.service;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.zfoo.hotswap.HotSwapContext;
import com.zfoo.hotswap.manager.HotSwapManager;
import com.zfoo.hotswap.model.ClassFileDef;
import com.zfoo.protocol.util.FileUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
import javassist.bytecode.ClassFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MBeanServer;
import javax.management.MXBean;
import javax.management.ObjectName;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//java11过后ClassFile对外不可见
//import com.sun.tools.classfile.ClassFile;
/**
* JMXJAVA Management Extensions)技术是java5的新特性,它提供一种简单,标准的方式去管理应用程序,设备,服务等资源。
* JMS定义了一些设计模式,api和一些服务来进行应用程序和网络的监控,这些都是基于java语言环境的。
* 使用JMS技术,资源被一种叫做MBeansManaged Beans)监控,这些MBean都在一个核心对象管理server上注册。
* JMS给java开发者提供了自由的方式去监控java代码,创建智能java agents,实现分布式管理的中间件和管理者,并且能够快速整合这些方案到的管理和监控系统。
* <p>
* 根据JMX描述,MBean接口包括一些可读或者可写的属性,还有一些定义好的方法,这些方法能够被MBean管理应用程序调用。
* 实现类,类名必须为接口sufixMBean的前缀。也就是Hello。如果不按这个命名注册MBean就会有问题。
*
* @author jaysunxiao
* @version 3.0
*/
@MXBean
public class HotSwapServiceMBean implements IHotSwapServiceMBean {
private static final Logger logger = LoggerFactory.getLogger(HotSwapServiceMBean.class);
// 热更新的代理和热更新的文件要放在同一个目录
private static final String HOT_SWAP_SCRIPT = "hotscript";
private static final String HOT_SWAP_AGENT = HOT_SWAP_SCRIPT + "/hotswap-2.0.jar";
public static final String CLASS_SUFFIX = ".class";
public static final String JAVA_SUFFIX = ".java";
private static final HotSwapServiceMBean HOT_SWAP_SERVICE = new HotSwapServiceMBean();
private HotSwapServiceMBean() {
registerMBean();
}
// 使用jconsole去连接,当然也可以使用RMI进行远程连接MBean server,来进行管理和执行操作。
private void registerMBean() {
//注册监控
try {
//获取MBeanServer 如果没有MBean server存在那么下面会自动调用ManagementFactory.createMBeanServer()
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// 包名加 类名 创建一个ObjectName
ObjectName objectName = new ObjectName(this.getClass().getPackage().getName() + ":type=" + this.getClass().getSimpleName());
// 在MBean server上注册MBean
mbs.registerMBean(this, objectName);
} catch (Exception e) {
logger.error("MBean error", e);
}
}
public static HotSwapServiceMBean getSingleInstance() {
return HOT_SWAP_SERVICE;
}
@Override
public void hotSwapByRelativePath(String relativePath) {
hotSwapByAbsolutePath(FileUtils.getProAbsPath() + File.separator + relativePath);
}
@Override
public synchronized void hotSwapByAbsolutePath(String absolutePath) {
// 热更新class文件,上一步没有完成的操作本次继续执行
try {
hotSwapClass(absolutePath);
} catch (Exception e) {
logger.error("热更新class文件异常:[exception:{}]", e);
}
}
@Override
public void logAllUpdateClassFileInfo() {
int count = 0;
for (Map.Entry<String, ClassFileDef> entry : HotSwapManager.getInstance().getClassFileDefMap().entrySet()) {
logger.info("[{}].更新的类名称:[{}],更改的时间:[{}]", ++count, entry.getKey(), entry.getValue().getLastModifyTime());
}
}
private void hotSwapClass(String absolutePath) throws Exception {
long start = System.currentTimeMillis();
ByteArrayOutputStream baos = null;
DataOutputStream dos = null;
try {
// 本次需要更新的Class
Map<String, ClassFileDef> updateClassMap = new HashMap<>();
List<File> fileList = FileUtils.getAllReadableFiles(new File(absolutePath));
for (File file : fileList) {
if (!file.getName().endsWith(CLASS_SUFFIX)) {
continue;
}
String path = file.getAbsolutePath();
long lastModifiedTime = file.lastModified();
byte[] data = FileUtils.readFileToByteArray(file);
String className = readClassName(data);
ClassFileDef classFileDef = new ClassFileDef(className, path, lastModifiedTime, data);
updateClassMap.put(classFileDef.getClassName(), classFileDef);
}
if (updateClassMap.isEmpty()) {
logger.debug("本次更新没有如何文件");
return;
}
// 验证本次更新的所有class文件是否合法
Map<String, ClassFileDef> classFileDefMap = HotSwapContext.getHotSwapManager().getClassFileDefMap();
for (Map.Entry<String, ClassFileDef> entry : updateClassMap.entrySet()) {// 读取所有本次需要热更新的字节码
ClassFileDef classFileDef = entry.getValue();
// 上一次热更新的文件
ClassFileDef lastClassFileDef = classFileDefMap.get(classFileDef.getClassName());
if (lastClassFileDef != null && !classFileDef.getClassName().equals(lastClassFileDef.getClassName())) {
logger.error("本次热更新失败,两次热更新文件的类名称不一致,转换失败:[{}]-->[{}]"
, classFileDef.getClassName(), lastClassFileDef.getClassName());
return;
}
}
// 创建临时更新流
baos = new ByteArrayOutputStream(1024);
dos = new DataOutputStream(baos);
// 先写需要热更新类的数量
dos.writeInt(updateClassMap.size());
for (ClassFileDef classFileDef : updateClassMap.values()) {
dos.writeUTF(classFileDef.getClassName());// 写类名称
dos.writeInt(classFileDef.getData().length);// 字节码的长度
dos.write(classFileDef.getData());// 字节码
}
dos.flush();
//设置状态,准备更新
HotSwapManager.updateBytes = baos.toByteArray();
HotSwapManager.exception = null;
//更新
loadHotSwapAgent();
if (HotSwapManager.exception != null) {
for (ClassFileDef classFileDef : updateClassMap.values()) {
logger.error("类[{}]热更新失败", classFileDef.getClassName());
}
logger.error("热更新失败原因:", HotSwapManager.exception);
return;
}
for (ClassFileDef classFileDef : updateClassMap.values()) {
logger.info("类[{}]热更新成功", classFileDef.getClassName());
}
long end = TimeUtils.currentTimeMillis();
logger.info("本次热更新总耗时:[{}]毫秒", end - start);
// 更新成功,保存更新记录
classFileDefMap.putAll(updateClassMap);
// 删除所有被更新过的java和class文件
for (ClassFileDef def : updateClassMap.values()) {
FileUtils.deleteFile(new File(def.getPath()));
}
} finally {
IOUtils.closeIO(dos, baos);
}
}
private void loadHotSwapAgent() throws Exception {
VirtualMachine vm = null;
try {
// 获取运行当前这个类的jvm的pid
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
// attach到这个pid建立通信管道,让jvm加载agent
vm = VirtualMachine.attach(pid);
vm.loadAgent(HOT_SWAP_AGENT);
} catch (AgentInitializationException | AgentLoadException | AttachNotSupportedException | IOException e) {
logger.error("热更新开启VirtualMachine失败", e);
throw e;
} finally {
try {
if (vm != null) {
vm.detach();
}
} catch (IOException e) {
logger.error("热更新关闭vm失败", e);
}
}
}
private String readClassName(byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
DataInputStream dataInputStream = null;
try {
dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes));
var classFile = new ClassFile(dataInputStream);
return classFile.getName().replaceAll(StringUtils.SLASH, StringUtils.PERIOD);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(dataInputStream, byteArrayInputStream);
}
}
}
@@ -0,0 +1,28 @@
package com.zfoo.hotswap.service;
import javax.management.MXBean;
/**
* @author jaysunxiao
* @version 3.0
*/
@MXBean
public interface IHotSwapServiceMBean {
/**
* 热更新相对于项目路径的文件
*
* @param relativePath 相对路径
*/
void hotSwapByRelativePath(String relativePath);
/**
* 热更新绝对路径的文件
*
* @param absolutePath 热更新文件的绝对路径
*/
void hotSwapByAbsolutePath(String absolutePath);
void logAllUpdateClassFileInfo();
}
@@ -0,0 +1,96 @@
package com.zfoo.hotswap.util;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.ClassFile;
import javassist.util.HotSwapAgent;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.instrument.ClassDefinition;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class HotSwapUtils {
private static final Logger logger = LoggerFactory.getLogger(HotSwapUtils.class);
private static String readClassName(byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
DataInputStream dataInputStream = null;
try {
dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes));
var classFile = new ClassFile(dataInputStream);
return classFile.getName().replaceAll(StringUtils.SLASH, StringUtils.PERIOD);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
IOUtils.closeIO(dataInputStream, byteArrayInputStream);
}
}
/**
* 热更新java文件
* jvm的启动参数,jdk11过后默认的全款更不允许连接自己,VM: -Djdk.attach.allowAttachSelf=true
* <p>
* 优先使用简单的Javassist做热更新,因为Byte Buddy使用了更为复杂的ASMspring boot中会使用Byte Buddy热更新
*
* @param bytes .class结尾的字节码文件
*/
public static synchronized void hotswapClass(byte[] bytes) {
if (bytes == null || bytes.length <= 0) {
return;
}
Class<?> clazz = null;
try {
clazz = Class.forName(readClassName(bytes));
} catch (ClassNotFoundException e) {
logger.error(ExceptionUtils.getMessage(e));
}
hotswapClassByJavassist(clazz, bytes);
}
private static void hotswapClassByJavassist(Class<?> clazz, byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
CtClass ctClass = null;
try {
clazz = Class.forName(readClassName(bytes));
byteArrayInputStream = new ByteArrayInputStream(bytes);
ctClass = ClassPool.getDefault().makeClass(byteArrayInputStream);
// Javassist热更新
HotSwapAgent.redefine(clazz, ctClass);
logger.info("Javassist热更新[{}]成功", clazz);
} catch (Throwable t) {
logger.info("无法使用Javassist热更新,开始使用替补方案Byte Buddy做热更新", t);
hotswapClassByByteBuddy(clazz, bytes);
} finally {
IOUtils.closeIO(byteArrayInputStream);
if (ctClass != null) {
ctClass.defrost();
}
}
}
private static void hotswapClassByByteBuddy(Class<?> clazz, byte[] bytes) {
try {
// Byte Buddy热更新
var instrumentation = ByteBuddyAgent.install();
instrumentation.redefineClasses(new ClassDefinition(clazz, bytes));
logger.info("Byte Buddy热更新[{}]成功", clazz);
} catch (Throwable t) {
logger.error("Byte Buddy热更新未知异常,热更新[{}]失败", clazz);
}
}
}
@@ -0,0 +1,5 @@
Manifest-Version: 1.0
Agent-Class: com.zfoo.hotswap.agent.HotSwapAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
@@ -0,0 +1,32 @@
package com.zfoo.hotswap;
import com.zfoo.hotswap.util.HotSwapUtils;
import com.zfoo.protocol.util.ClassUtils;
import com.zfoo.protocol.util.IOUtils;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
/**
* @author jaysunxiao
*/
@Ignore
public class ApplicationTest {
// 热更新教程,需要添加JVM参数,-Djdk.attach.allowAttachSelf=true,如果不加这个参数将使用Byte Buddy热更新替代Javassist热更新
// 使用Javassist热更新更加的轻量,如果Javassist热更新失败,则会自动使用Byte Buddy做热更新
@Test
public void startHotSwapTest() throws IOException {
// 热更新限制,不能为需要热更新的类添加或减少成员函数和成员变量,只能修改函数内部的逻辑
var test = new HotswapClass();
// 没有热更新的输出
test.print();
// 随便修改print方法,然后编译成为一个需要热更新的class文件
HotSwapUtils.hotswapClass(IOUtils.toByteArray(ClassUtils.getFileFromClassPath("HotswapClass.class")));
// 热更新之后的输出
test.print();
}
}
@@ -0,0 +1,17 @@
package com.zfoo.hotswap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
*/
public class HotswapClass {
private static final Logger logger = LoggerFactory.getLogger(HotswapClass.class);
public void print() {
logger.info("没有热更新的输出内容");
}
}
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="false" debug="false">
<contextName>com.zfoo.hotswap</contextName>
<property name="LOG_HOME" value="log/hotswap"/>
<property name="PATTERN_FILE"
value="%d{yyyy-MM-dd HH:mm:ss} [%5level] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<property name="PATTERN_CONSOLE"
value="%d{yyyy-MM-dd HH:mm:ss} [%highlight(%5level)] [%thread] %logger.%M\\(%F:%line\\) - %msg%n"/>
<!-- 负责写日志,控制台日志,会打印所有的包的所有级别日志 -->
<appender name="zfoo_console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN_CONSOLE}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 根logger -->
<root level="info">
<appender-ref ref="zfoo_console"/>
</root>
<!--
- 1.name:包名或类名,用来指定受此logger约束的某一个包或者具体的某一个类
- 2.未设置打印级别,所以继承他的上级<root>的日志级别“DEBUG”
- 3.未设置additivity,默认为true,将此logger的打印信息向上级传递;
- 4.未设置appender,此logger本身不打印任何信息,级别为“DEBUG”及大于“DEBUG”的日志信息传递给root,
- root接到下级传递的信息,交给已经配置好的名为“STDOUT”的appender处理,“STDOUT”appender将信息打印到控制台;
-->
<logger name="ch.qos.logback" level="info"/>
<!--*******************************************Spring********************************************************-->
<!--logger中的name是指代码的包名或类名,路径要写全,可以配置不同包中的日志输出到不同的文件中。level是日志输出级别 -->
<!--过滤掉spring的一些无用的DEBUG信息-->
<logger name="org.springframework" level="info"/>
<!-- additivity="false"表示不继承父logger的配置和父类没有关系-->
<logger name="org.springframework.core" level="info"/>
<!--*******************************************Netty*********************************************************-->
<logger name="io.netty" level="info"/>
</configuration>
+234
View File
@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zfoo</groupId>
<artifactId>monitor</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<properties>
<!-- 本项目的其它module版本号 -->
<zfoo.event.version>3.0</zfoo.event.version>
<zfoo.hotswap.version>3.0</zfoo.hotswap.version>
<zfoo.monitor.version>3.0</zfoo.monitor.version>
<zfoo.net.version>3.0</zfoo.net.version>
<zfoo.scheduler.version>3.0</zfoo.scheduler.version>
<zfoo.storage.version>3.0</zfoo.storage.version>
<zfoo.orm.version>3.0</zfoo.orm.version>
<zfoo.protocol.version>3.0</zfoo.protocol.version>
<zfoo.util.version>3.0</zfoo.util.version>
<!-- 核心spring框架 -->
<spring.version>5.3.4</spring.version>
<spring.boot.version>2.4.3</spring.boot.version>
<!-- 工具包 -->
<commons-codec.version>1.15</commons-codec.version>
<commons-io.version>2.8.0</commons-io.version>
<commons-collections.version>4.4</commons-collections.version>
<commons-lang.version>3.12.0</commons-lang.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<commons-logging.version>1.2</commons-logging.version>
<commons-log4j.version>2.14.0</commons-log4j.version>
<httpcomponents.version>4.5.13</httpcomponents.version>
<httpcore.version>4.4.14</httpcore.version>
<google.guava.version>30.1-jre</google.guava.version>
<google.protobuf.version>3.9.1</google.protobuf.version>
<google.gson.version>2.8.6</google.gson.version>
<kryo.version>5.0.3</kryo.version>
<caffeine.version>2.8.8</caffeine.version>
<jctools.version>3.2.0</jctools.version>
<hutool.version>5.5.9</hutool.version>
<oshi.version>5.7.0</oshi.version>
<snakeyaml.version>1.28</snakeyaml.version>
<!-- json和xml解析包 -->
<jackson.version>2.12.1</jackson.version>
<fastjson.version>1.2.51</fastjson.version>
<!-- office文档解析包 -->
<poi.version>4.1.2</poi.version>
<!-- 字节码增强 -->
<javassist.version>3.27.0-GA</javassist.version>
<bytebuddy.version>1.10.22</bytebuddy.version>
<!-- 网络通讯框架 -->
<netty.version>4.1.63.Final</netty.version>
<!-- 分布式zookeeper核心依赖包 -->
<zookeeper.version>3.6.1</zookeeper.version>
<curator.version>5.1.0</curator.version>
<!-- 数据库和缓存 -->
<mongodb-driver-sync.version>4.2.1</mongodb-driver-sync.version>
<jedis.version>3.3.0</jedis.version>
<!-- 消息队列中间件 -->
<rocketmq.version>4.5.2</rocketmq.version>
<!-- elastic search 中间件 -->
<elastic.search.version>7.9.3</elastic.search.version>
<elastic.search.spring.version>4.1.5</elastic.search.spring.version>
<lucene.version>8.6.2</lucene.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.13.1</junit.version>
<!-- java版本和文件编码 -->
<java.version>11</java.version>
<file.encoding>UTF-8</file.encoding>
<jakarta.version>1.3.5</jakarta.version>
<!-- maven核心插件 -->
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<versions-maven-plugin.version>2.8.1</versions-maven-plugin.version>
<project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
<maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
</properties>
<dependencies>
<!-- 依赖的util类库 -->
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>util</artifactId>
<version>${zfoo.util.version}</version>
</dependency>
<!-- 操作系统底层操作库 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>${oshi.version}</version>
</dependency>
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>scheduler</artifactId>
<version>${zfoo.scheduler.version}</version>
</dependency>
<!-- slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- logback核心包 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- logback的sl4j的实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 依赖的测试库 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<!-- 清理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${file.encoding}</encoding>
</configuration>
</plugin>
<!-- resource资源管理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>${file.encoding}</encoding>
<outputDirectory>${project.build.directory}/resource</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkMode>once</forkMode>
<threadCount>10</threadCount>
<argLine>-Dfile.encoding=${file.encoding}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,111 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.monitor.model;
import com.zfoo.monitor.util.OSUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
/**
* @author jaysunxiao
* @version 3.0
*/
public class DiskFileSystemVO implements Comparable<DiskFileSystemVO> {
private String name;
private long size;
private long available;
private long timestamp;
public static DiskFileSystemVO valueOf(String name, long size, long available, long timestamp) {
var vo = new DiskFileSystemVO();
vo.name = name;
vo.size = size;
vo.available = available;
vo.timestamp = timestamp;
return vo;
}
public String pressure() {
var usage = 1D * (size - available) / size;
if (usage >= 0.8) {
var tempVO = this.toGB();
return StringUtils.format("df - 磁盘[name:{}]空间过高[size:{}GB][available:{}GB][usage:{}][{}]"
, name, tempVO.getSize(), tempVO.getAvailable(), OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp));
}
return StringUtils.EMPTY;
}
@Override
public int compareTo(DiskFileSystemVO target) {
if (target == null) {
return 1;
}
if (!this.name.equals(target.getName())) {
return 0;
}
var a = 1D * (this.size - this.available) / this.size;
var b = 1D * (target.getSize() - target.getAvailable()) / target.getSize();
return Double.compare(a, b);
}
public DiskFileSystemVO toMB() {
var size = this.size / IOUtils.BYTES_PER_MB;
var available = this.available / IOUtils.BYTES_PER_MB;
return DiskFileSystemVO.valueOf(this.name, size, available, timestamp);
}
public DiskFileSystemVO toGB() {
var size = (long) Math.ceil(1D * this.size / IOUtils.BYTES_PER_GB);
var available = (long) Math.ceil(1D * this.available / IOUtils.BYTES_PER_GB);
return DiskFileSystemVO.valueOf(this.name, size, available, timestamp);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public long getAvailable() {
return available;
}
public void setAvailable(long available) {
this.available = available;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.monitor.model;
import com.zfoo.monitor.util.OSUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
/**
* @author jaysunxiao
* @version 3.0
*/
public class MemoryVO implements Comparable<MemoryVO> {
private long total;
private long available;
private long timestamp;
public static MemoryVO valueOf(long total, long available, long timestamp) {
var vo = new MemoryVO();
vo.total = total;
vo.available = available;
vo.timestamp = timestamp;
return vo;
}
public String pressure() {
var usage = 1D * (total - available) / total;
if (usage >= 0.8) {
var tempVO = this.toGB();
return StringUtils.format("free - 内存占用过高[total:{}GB][available:{}GB][usage:{}][{}]"
, tempVO.getTotal(), tempVO.getAvailable(), OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp));
}
return StringUtils.EMPTY;
}
@Override
public int compareTo(MemoryVO target) {
if (target == null) {
return 1;
}
var a = 1D * (this.total - this.available) / this.total;
var b = 1D * (target.getTotal() - target.getAvailable()) / target.getTotal();
return Double.compare(a, b);
}
public MemoryVO toMB() {
var total = this.total / IOUtils.BYTES_PER_MB;
var available = this.available / IOUtils.BYTES_PER_MB;
return MemoryVO.valueOf(total, available, timestamp);
}
public MemoryVO toGB() {
var total = (long) Math.ceil(1D * this.total / IOUtils.BYTES_PER_GB);
var available = (long) Math.ceil(1D * this.available / IOUtils.BYTES_PER_GB);
return MemoryVO.valueOf(total, available, timestamp);
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public long getAvailable() {
return available;
}
public void setAvailable(long available) {
this.available = available;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
@@ -0,0 +1,156 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.monitor.model;
import com.zfoo.monitor.util.OSUtils;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.util.FileUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
import com.zfoo.util.net.NetUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @author jaysunxiao
* @version 3.0
*/
public class MonitorVO {
private String uuid;
private UptimeVO uptime;
private List<DiskFileSystemVO> df;
private MemoryVO free;
private List<SarVO> sar;
public static MonitorVO valueOf(String uuid, UptimeVO uptime, List<DiskFileSystemVO> df, MemoryVO free, List<SarVO> sar) {
var vo = new MonitorVO();
vo.uuid = uuid;
vo.uptime = uptime;
vo.df = df;
vo.free = free;
vo.sar = sar;
return vo;
}
public List<String> toPressures() {
var messages = new ArrayList<String>();
var uptimeMessage = uptime.pressure();
if (!StringUtils.isBlank(uptimeMessage)) {
messages.add(uptimeMessage);
}
for (var fileSystem : df) {
var dfMessage = fileSystem.pressure();
if (!StringUtils.isBlank(dfMessage)) {
messages.add(dfMessage);
}
}
var freeMessage = free.pressure();
if (!StringUtils.isBlank(freeMessage)) {
messages.add(freeMessage);
}
for (var networkIF : sar) {
var sarMessage = networkIF.pressure();
if (!StringUtils.isBlank(sarMessage)) {
messages.add(sarMessage);
}
}
return messages;
}
@Override
public String toString() {
var builder = new StringBuilder();
builder.append(StringUtils.format("Server Monitor [ip:{}] [uuid:{}]", NetUtils.getLocalhostStr(), uuid));
builder.append(FileUtils.LS);
builder.append(StringUtils.format("1.cpu: [{}] [{}] [{}] [usage:{}] [{}]"
, uptime.getOneMinute(), uptime.getFiveMinute(), uptime.getFiftyMinute(), OSUtils.toPercent(uptime.getUsage()), TimeUtils.timeToString(uptime.getTimestamp())));
builder.append(FileUtils.LS);
builder.append(StringUtils.format("2.memory: [total:{}GB] [available:{}GB] [usage:{}] [{}]"
, free.toGB().getTotal(), free.toGB().getAvailable(), OSUtils.toPercent(1D * (free.getTotal() - free.getAvailable()) / free.getTotal()), TimeUtils.timeToString(free.getTimestamp())));
builder.append(FileUtils.LS);
builder.append("3.disk: ");
builder.append(FileUtils.LS);
df.forEach(it -> {
builder.append(StringUtils.format(" [disk:{}] [size:{}GB] [available:{}GB] [usage:{}] [{}]"
, it.getName(), it.toGB().getSize(), it.toGB().getAvailable(), OSUtils.toPercent(1D * (it.getSize() - it.getAvailable()) / it.getSize()), TimeUtils.timeToString(it.getTimestamp())));
builder.append(FileUtils.LS);
});
builder.append("4.network:");
builder.append(FileUtils.LS);
sar.forEach(it -> {
builder.append(StringUtils.format(" [interface:{}] [rxpck:{}] [txpck:{}] [rxBytes:{}] [txBytes:{}] [inErrors:{}] [outErrors:{}] [inDrops:{}] [collisions:{}] [{}]"
, it.getName(), it.getRxpck(), it.getTxpck(), it.getRxBytes(), it.getTxBytes(), it.getInErrors(), it.getOutErrors(), it.getInDrops(), it.getCollisions(), TimeUtils.timeToString(it.getTimestamp())));
builder.append(FileUtils.LS);
});
var pressures = toPressures();
if (CollectionUtils.isNotEmpty(pressures)) {
builder.append("summary of errors:");
builder.append(FileUtils.LS);
pressures.forEach(it -> builder.append(StringUtils.format(" {}", it)).append(FileUtils.LS));
}
return builder.toString();
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public UptimeVO getUptime() {
return uptime;
}
public void setUptime(UptimeVO uptime) {
this.uptime = uptime;
}
public List<DiskFileSystemVO> getDf() {
return df;
}
public void setDf(List<DiskFileSystemVO> df) {
this.df = df;
}
public MemoryVO getFree() {
return free;
}
public void setFree(MemoryVO free) {
this.free = free;
}
public List<SarVO> getSar() {
return sar;
}
public void setSar(List<SarVO> sar) {
this.sar = sar;
}
}
@@ -0,0 +1,172 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.monitor.model;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SarVO implements Comparable<SarVO> {
private String name;
private long rxpck;
private long txpck;
private long rxBytes;
private long txBytes;
private long inErrors;
private long outErrors;
private long inDrops;
private long collisions;
private long timestamp;
public static SarVO valueOf(String name, long rxpck, long txpck, long rxBytes, long txBytes
, long inErrors, long outErrors, long inDrops, long collisions, long timestamp) {
var vo = new SarVO();
vo.name = name;
vo.rxpck = rxpck;
vo.txpck = txpck;
vo.rxBytes = rxBytes;
vo.txBytes = txBytes;
vo.inErrors = inErrors;
vo.outErrors = outErrors;
vo.inDrops = inDrops;
vo.collisions = collisions;
vo.timestamp = timestamp;
return vo;
}
public String pressure() {
if (rxpck >= 5_0000) {
return StringUtils.format("sar - 网卡流量[interface:{}] [rxpck:{}] [txpck:{}] [rxBytes:{}] [txBytes:{}] [inErrors:{}] [outErrors:{}] [inDrops:{}] [collisions:{}] [{}],性能影响:危险"
, name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, TimeUtils.timeToString(timestamp));
}
if (inErrors > 0 || outErrors > 0 || inDrops > 0 || collisions > 0) {
return StringUtils.format("sar - 网卡流量[interface:{}] [rxpck:{}] [txpck:{}] [rxBytes:{}] [txBytes:{}] [inErrors:{}] [outErrors:{}] [inDrops:{}] [collisions:{}] [{}],性能影响:低"
, name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, TimeUtils.timeToString(timestamp));
}
return StringUtils.EMPTY;
}
@Override
public int compareTo(SarVO target) {
if (target == null) {
return 1;
}
if (!this.name.equals(target.getName())) {
return 0;
}
var a = this.rxpck + this.txpck;
var b = target.getRxpck() + target.getTxpck();
return Long.compare(a, b);
}
@Override
public String toString() {
return StringUtils.format("[name:{}][rxpck:{}][txpck:{}][rxBytes:{}][txBytes:{}][inErrors:{}][outErrors:{}][inDrops:{}][collisions:{}][time:{}]"
, name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, TimeUtils.timeToString(timestamp));
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getRxpck() {
return rxpck;
}
public void setRxpck(long rxpck) {
this.rxpck = rxpck;
}
public long getTxpck() {
return txpck;
}
public void setTxpck(long txpck) {
this.txpck = txpck;
}
public long getRxBytes() {
return rxBytes;
}
public void setRxBytes(long rxBytes) {
this.rxBytes = rxBytes;
}
public long getTxBytes() {
return txBytes;
}
public void setTxBytes(long txBytes) {
this.txBytes = txBytes;
}
public long getInErrors() {
return inErrors;
}
public void setInErrors(long inErrors) {
this.inErrors = inErrors;
}
public long getOutErrors() {
return outErrors;
}
public void setOutErrors(long outErrors) {
this.outErrors = outErrors;
}
public long getInDrops() {
return inDrops;
}
public void setInDrops(long inDrops) {
this.inDrops = inDrops;
}
public long getCollisions() {
return collisions;
}
public void setCollisions(long collisions) {
this.collisions = collisions;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.monitor.model;
import com.zfoo.monitor.util.OSUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
/**
* @author jaysunxiao
* @version 3.0
*/
public class UptimeVO implements Comparable<UptimeVO> {
private double oneMinute;
private double fiveMinute;
private double fiftyMinute;
private double usage;
private long timestamp;
public static UptimeVO valueOf(double oneMinute, double fiveMinute, double fiftyMinute, double usage, long timestamp) {
var vo = new UptimeVO();
vo.oneMinute = oneMinute;
vo.fiveMinute = fiveMinute;
vo.fiftyMinute = fiftyMinute;
vo.usage = usage;
vo.timestamp = timestamp;
return vo;
}
public String pressure() {
var processors = OSUtils.availableProcessors();
var one = oneMinute / processors;
var five = fiveMinute / processors;
var fifty = fiftyMinute / processors;
if (usage >= 0.8) {
return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}]过大,性能影响:危险"
, oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp));
}
if (one > 5 || five > 5 || fifty > 5) {
return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}]过大,性能影响:警告"
, oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp));
}
if (one > 4 || five > 4 || fifty > 4) {
return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}],性能影响:高"
, oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp));
}
if (one > 3 || five > 3 || fifty > 3) {
return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}],性能影响:中,考虑优化"
, oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp));
}
return StringUtils.EMPTY;
}
@Override
public int compareTo(UptimeVO target) {
if (target == null) {
return 1;
}
return Double.compare(this.usage, target.getUsage());
}
public double getOneMinute() {
return oneMinute;
}
public void setOneMinute(double oneMinute) {
this.oneMinute = oneMinute;
}
public double getFiveMinute() {
return fiveMinute;
}
public void setFiveMinute(double fiveMinute) {
this.fiveMinute = fiveMinute;
}
public double getFiftyMinute() {
return fiftyMinute;
}
public void setFiftyMinute(double fiftyMinute) {
this.fiftyMinute = fiftyMinute;
}
public double getUsage() {
return usage;
}
public void setUsage(double usage) {
this.usage = usage;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
@@ -0,0 +1,280 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.monitor.util;
import com.zfoo.monitor.model.*;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
import com.zfoo.util.security.IdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import oshi.SystemInfo;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.NetworkIF;
import oshi.software.os.OperatingSystem;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Oshi库封装的工具类,通过此工具类,可获取系统、硬件相关信息
*
* @author jaysunxiao
* @version 3.0
*/
public abstract class OSUtils {
private static final Logger logger = LoggerFactory.getLogger(OSUtils.class);
/**
* cpu的数量
*/
private static final int processors = Runtime.getRuntime().availableProcessors();
/**
* 系统信息
*/
private static final SystemInfo systemInfo = new SystemInfo();
/**
* 硬件信息
*/
private static final HardwareAbstractionLayer hardware = systemInfo.getHardware();
/**
* 操作系统信息
*/
private static final OperatingSystem os = systemInfo.getOperatingSystem();
/**
* 网络信息
*/
private static final List<NetworkIF> networkIFs = hardware.getNetworkIFs();
/**
* cpu的tick次数信息
*/
private static long[] ticks = hardware.getProcessor().getSystemCpuLoadTicks();
public static int availableProcessors() {
return processors;
}
/**
* 将小于0的num转为百分比,舍弃的部分将会做四舍五入
*/
public static String toPercent(double num) {
if (num > 1) {
throw new RuntimeException("转为百分比的num必须小于1");
}
var percentFormat = NumberFormat.getPercentInstance();
// 最大小数位数
percentFormat.setMaximumFractionDigits(2);
// 最大整数位数
percentFormat.setMaximumIntegerDigits(2);
// 最小小数位数
percentFormat.setMinimumFractionDigits(2);
// 最小整数位数
percentFormat.setMinimumIntegerDigits(2);
// 自动转换成百分比显示
return percentFormat.format(num);
}
/**
* 对应于Linux中的uptime命令,windows中无法统计,所以在windows返回的结果默认是-1
*/
public static UptimeVO uptime() {
var processor = hardware.getProcessor();
var loads = processor.getSystemLoadAverage(3);
var oneMinute = loads[0];
var fiveMinute = loads[1];
var fiftyMinute = loads[2];
var cpuTicks = processor.getSystemCpuLoadTicks();
var usage = processor.getSystemCpuLoadBetweenTicks(ticks);
ticks = cpuTicks;
return UptimeVO.valueOf(oneMinute, fiveMinute, fiftyMinute, usage, TimeUtils.now());
}
/**
* 对应于Linux中的df -h命令,兼容windows
*/
public static List<DiskFileSystemVO> df() {
var fileSystems = os.getFileSystem().getFileStores();
var df = new ArrayList<DiskFileSystemVO>();
var nameMap = new HashMap<String, Integer>();
for (var fs : fileSystems) {
var name = fs.getName();
var size = fs.getTotalSpace();
var available = fs.getFreeSpace();
var value = nameMap.get(name);
if (value == null) {
nameMap.put(name, 1);
} else {
name = name + value;
nameMap.put(name, ++value);
}
df.add(DiskFileSystemVO.valueOf(name, size, available, TimeUtils.now()));
}
return df;
}
/**
* 对应于Linux中的free命令,兼容windows
*/
public static MemoryVO free() {
var memory = hardware.getMemory();
var total = memory.getTotal();
var available = memory.getAvailable();
return MemoryVO.valueOf(total, available, TimeUtils.now());
}
/**
* 对应于Linux中的sar -n DEV 1命令,兼容windows
*/
public static List<SarVO> sar() {
var sar = new ArrayList<SarVO>();
for (var networkIF : networkIFs) {
var name = networkIF.getDisplayName() + StringUtils.SPACE + networkIF.getName();
var oldTimestamp = networkIF.getTimeStamp();
var oldBytesRecv = networkIF.getBytesRecv();
var oldBytesSent = networkIF.getBytesSent();
var oldPacketsRecv = networkIF.getPacketsRecv();
var oldPacketsSent = networkIF.getPacketsSent();
var oldInErrors = networkIF.getInErrors();
var oldOutErrors = networkIF.getOutErrors();
var oldInDrops = networkIF.getInDrops();
var oldCollisions = networkIF.getCollisions();
networkIF.updateAttributes();
var timestamp = networkIF.getTimeStamp();
var timeInterval = (timestamp - oldTimestamp) / 1000D;
var rxpck = (long) Math.ceil(((networkIF.getPacketsRecv() - oldPacketsRecv) / timeInterval));
var txpck = (long) Math.ceil((networkIF.getPacketsSent() - oldPacketsSent) / timeInterval);
var rxBytes = (long) Math.ceil((networkIF.getBytesRecv() - oldBytesRecv) / timeInterval);
var txBytes = (long) Math.ceil((networkIF.getBytesSent() - oldBytesSent) / timeInterval);
var inErrors = networkIF.getInErrors() - oldInErrors;
var outErrors = networkIF.getOutErrors() - oldOutErrors;
var inDrops = networkIF.getInDrops() - oldInDrops;
var collisions = networkIF.getCollisions() - oldCollisions;
sar.add(SarVO.valueOf(name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, timestamp));
}
return sar;
}
private static UptimeVO maxUptime;
private static Map<String, DiskFileSystemVO> maxDfMap;
private static MemoryVO maxFree;
private static Map<String, SarVO> maxSarMap;
static {
initMonitor();
}
public static void initMonitor() {
maxUptime = uptime();
maxDfMap = new ConcurrentHashMap<>(df().stream().collect(Collectors.toMap(key -> key.getName(), value -> value)));
maxFree = free();
maxSarMap = new ConcurrentHashMap<>(sar().stream().collect(Collectors.toMap(key -> key.getName(), value -> value)));
}
public static MonitorVO maxMonitor() {
var uuid = IdUtils.getUUID();
var monitor = MonitorVO.valueOf(uuid, maxUptime, new ArrayList<>(maxDfMap.values()), maxFree, new ArrayList<>(maxSarMap.values()));
initMonitor();
return monitor;
}
public static MonitorVO monitor() {
var uuid = IdUtils.getUUID();
var uptime = uptime();
var df = df();
var free = free();
var sar = sar();
if (uptime.compareTo(maxUptime) > 0) {
maxUptime = uptime;
}
for (var fileSystem : df) {
var maxFileSystem = maxDfMap.get(fileSystem.getName());
if (maxFileSystem != null && fileSystem.compareTo(maxFileSystem) > 0) {
maxDfMap.put(fileSystem.getName(), fileSystem);
}
}
if (free.compareTo(maxFree) > 0) {
maxFree = free;
}
for (var networkIF : sar) {
var maxNetworkIF = maxSarMap.get(networkIF.getName());
if (maxNetworkIF != null && networkIF.compareTo(maxNetworkIF) > 0) {
maxSarMap.put(maxNetworkIF.getName(), networkIF);
}
}
return MonitorVO.valueOf(uuid, uptime, df, free, sar);
}
public static String execCommand(String command) {
Process process = null;
InputStream inputStream = null;
try {
process = new ProcessBuilder(command.split(" "))
.redirectErrorStream(true)
.start();
//取得命令结果的输出流
inputStream = process.getInputStream();
var bytes = IOUtils.toByteArray(inputStream);
var result = StringUtils.bytesToString(bytes);
// 其他线程都等待这个线程完成
process.waitFor();
// 获取javac线程的退出值,0代表正常退出,非0代表异常中止
int exitValue = process.exitValue();
// 返回编译是否成功
if (exitValue != 0) {
throw new Exception("执行命令错误,返回码:" + exitValue);
}
return result;
} catch (Exception e) {
logger.error("命令执行未知异常", e);
} finally {
if (process != null) {
process.destroy();
}
IOUtils.closeIO(inputStream);
}
return StringUtils.EMPTY;
}
}
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.monitor;
import com.zfoo.monitor.util.OSUtils;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.util.ThreadUtils;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import oshi.SystemInfo;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ApplicationTest {
/**
* 仿Linux的uptime指令,可以用来监控cpu的负载
*/
@Test
public void uptimeTest() {
var vo = OSUtils.uptime();
System.out.println(JsonUtils.object2String(vo));
System.out.println(vo.pressure());
}
/**
* 仿Linux的df指令,可以用来监控硬盘容量
*/
@Test
public void dfTest() {
var df = OSUtils.df();
df.forEach(it -> {
System.out.println(JsonUtils.object2String(it.toGB()));
System.out.println(it.pressure());
});
}
/**
* 仿Linux的free指令,可以用来监控内存占用
*/
@Test
public void freeTest() {
var free = OSUtils.free();
System.out.println(JsonUtils.object2String(free.toGB()));
System.out.println(free.pressure());
}
/**
* 仿Linux的sar指令,可以用来监控网络IO
*/
@Test
public void sarTest() {
var sar = OSUtils.sar();
sar.forEach(it -> {
System.out.println(JsonUtils.object2String(it));
System.out.println(it.pressure());
});
}
/**
* cpu的tick大小测试
*/
@Ignore
@Test
public void cpuTest() {
var systemInfo = new SystemInfo();
var hardware = systemInfo.getHardware();
var os = systemInfo.getOperatingSystem();
while (true) {
var oldTicks = hardware.getProcessor().getSystemCpuLoadTicks();
ThreadUtils.sleep(1000);
var usage = hardware.getProcessor().getSystemCpuLoadBetweenTicks(oldTicks);
System.out.println(usage);
}
}
/**
* 控制台指令执行测试
*/
@Ignore
@Test
public void execCommandTest() {
var str = OSUtils.execCommand("cmd /c jps");
System.out.println(str);
}
@Test
public void toPercentTest() {
var num = 0.123456D;
var str = OSUtils.toPercent(num);
Assert.assertEquals(str, "12.35%");
}
@Test
public void monitorTest() {
var monitor = OSUtils.monitor();
System.out.println(monitor);
ThreadUtils.sleep(1000);
monitor = OSUtils.monitor();
System.out.println(monitor);
monitor = OSUtils.maxMonitor();
System.out.println(monitor);
}
}
+35
View File
@@ -0,0 +1,35 @@
### . 简介
1. 优雅的同步和异步请求,速度更快
2. 服务注册和发现,配置中心使用的是zookeeper,可扩展成其它注册中心
3. 自带高性能网关,自定义转发策略
4. 服务可伸缩,负载均衡,集群监控,应有尽有。
4. 基于Java11,所有的依赖包都是最新的jar包
```关键词
变态的高性能,高可用性,高伸缩性(一般指增加机器),高扩展性(一般指代码层面的开闭原则)
config,本地配置,zookeeper的注册发现,请求的负载均衡,都放在这个包下
core,核心包,服务器,客户端的统一封装
dispatcher,消息的分发
handlernetty的handler,定义了客户端,服务器的一些通用handler
protocol,消息类的注册,消息的编解码,字节码增强等
schemaspring的自定义标签的解析
session,对netty的channel的封装
task,通用任务线程池
```
### Ⅱ. 服务器架构图
<img src="./../event/tooltip/general-game-architect.jpg" width="70%" height="70%" alt="服务器架构图"/><br/>
### Ⅲ. 网络通信规范
- 客户端对服务器的请求以Request结尾,返回以Response结尾
- 服务器内部之间的调用以Ask结尾,返回以Answer结尾。
### Ⅳ. 教程
- [单机服务器教程](src/test/java/com/zfoo/net/core/tcp/server/TcpServerTest.java)
- [RPC教程](src/test/java/com/zfoo/net/core/provider/ProviderTest.java)
- [网关教程](src/test/java/com/zfoo/net/core/gateway/GatewayTest.java)
+330
View File
@@ -0,0 +1,330 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zfoo</groupId>
<artifactId>net</artifactId>
<version>3.0</version>
<packaging>jar</packaging>
<properties>
<!-- 本项目的其它module版本号 -->
<zfoo.event.version>3.0</zfoo.event.version>
<zfoo.hotswap.version>3.0</zfoo.hotswap.version>
<zfoo.monitor.version>3.0</zfoo.monitor.version>
<zfoo.net.version>3.0</zfoo.net.version>
<zfoo.scheduler.version>3.0</zfoo.scheduler.version>
<zfoo.storage.version>3.0</zfoo.storage.version>
<zfoo.orm.version>3.0</zfoo.orm.version>
<zfoo.protocol.version>3.0</zfoo.protocol.version>
<zfoo.util.version>3.0</zfoo.util.version>
<!-- 核心spring框架 -->
<spring.version>5.3.4</spring.version>
<spring.boot.version>2.4.3</spring.boot.version>
<!-- 工具包 -->
<commons-codec.version>1.15</commons-codec.version>
<commons-io.version>2.8.0</commons-io.version>
<commons-collections.version>4.4</commons-collections.version>
<commons-lang.version>3.12.0</commons-lang.version>
<commons-fileupload.version>1.4</commons-fileupload.version>
<commons-logging.version>1.2</commons-logging.version>
<commons-log4j.version>2.14.0</commons-log4j.version>
<httpcomponents.version>4.5.13</httpcomponents.version>
<httpcore.version>4.4.14</httpcore.version>
<google.guava.version>30.1-jre</google.guava.version>
<google.protobuf.version>3.9.1</google.protobuf.version>
<google.gson.version>2.8.6</google.gson.version>
<kryo.version>5.0.3</kryo.version>
<caffeine.version>2.8.8</caffeine.version>
<jctools.version>3.2.0</jctools.version>
<hutool.version>5.5.9</hutool.version>
<oshi.version>5.7.0</oshi.version>
<snakeyaml.version>1.28</snakeyaml.version>
<!-- json和xml解析包 -->
<jackson.version>2.12.1</jackson.version>
<fastjson.version>1.2.51</fastjson.version>
<!-- office文档解析包 -->
<poi.version>4.1.2</poi.version>
<!-- 字节码增强 -->
<javassist.version>3.27.0-GA</javassist.version>
<bytebuddy.version>1.10.22</bytebuddy.version>
<!-- 网络通讯框架 -->
<netty.version>4.1.63.Final</netty.version>
<!-- 分布式zookeeper核心依赖包 -->
<zookeeper.version>3.6.1</zookeeper.version>
<curator.version>5.1.0</curator.version>
<!-- 数据库和缓存 -->
<mongodb-driver-sync.version>4.2.1</mongodb-driver-sync.version>
<jedis.version>3.3.0</jedis.version>
<!-- 消息队列中间件 -->
<rocketmq.version>4.5.2</rocketmq.version>
<!-- elastic search 中间件 -->
<elastic.search.version>7.9.3</elastic.search.version>
<elastic.search.spring.version>4.1.5</elastic.search.spring.version>
<lucene.version>8.6.2</lucene.version>
<slf4j.version>1.7.30</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.13.1</junit.version>
<!-- java版本和文件编码 -->
<java.version>11</java.version>
<file.encoding>UTF-8</file.encoding>
<jakarta.version>1.3.5</jakarta.version>
<!-- maven核心插件 -->
<maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
<maven-resources-plugin.version>3.2.0</maven-resources-plugin.version>
<maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
<versions-maven-plugin.version>2.8.1</versions-maven-plugin.version>
<project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
<maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
</properties>
<dependencies>
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>util</artifactId>
<version>${zfoo.util.version}</version>
</dependency>
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>event</artifactId>
<version>${zfoo.event.version}</version>
</dependency>
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>scheduler</artifactId>
<version>${zfoo.util.version}</version>
</dependency>
<dependency>
<groupId>com.zfoo</groupId>
<artifactId>protocol</artifactId>
<version>${zfoo.protocol.version}</version>
</dependency>
<!-- 依赖的通信类库 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
<!-- 动态生成二进制字节码的javassist类库 -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
</exclusion>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>commons-lang</artifactId>
<groupId>commons-lang</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- curator /kjʊə'reɪtə/ n. 馆长; 评议员; 管理者; 监护人 -->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-framework -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>${curator.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<artifactId>zookeeper</artifactId>
<groupId>org.apache.zookeeper</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- recipe /ˈresɪpi/ n. 食谱; 处方; 烹饪法; 制作法 -->
<!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>${curator.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.ben-manes.caffeine/caffeine -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<!-- 依赖的Spring模块类库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- logback核心包 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<!-- logback的sl4j的实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 依赖的测试库 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<plugins>
<!-- 清理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
</plugin>
<!-- 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${file.encoding}</encoding>
</configuration>
</plugin>
<!-- resource资源管理插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven-resources-plugin.version}</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<encoding>${file.encoding}</encoding>
<outputDirectory>${project.build.directory}/resource</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- 测试插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<forkMode>once</forkMode>
<threadCount>10</threadCount>
<argLine>-Dfile.encoding=${file.encoding}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,161 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net;
import com.zfoo.event.manager.EventBus;
import com.zfoo.net.config.manager.IConfigManager;
import com.zfoo.net.consumer.service.IConsumer;
import com.zfoo.net.core.AbstractServer;
import com.zfoo.net.core.tcp.TcpClient;
import com.zfoo.net.dispatcher.manager.IPacketDispatcher;
import com.zfoo.net.packet.service.IPacketService;
import com.zfoo.net.schema.NetProcessor;
import com.zfoo.net.session.manager.ISessionManager;
import com.zfoo.net.task.TaskManager;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.scheduler.SchedulerContext;
import com.zfoo.util.ThreadUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import java.lang.reflect.Field;
import java.util.concurrent.ExecutorService;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NetContext implements ApplicationListener<ApplicationContextEvent>, Ordered {
private static final Logger logger = LoggerFactory.getLogger(NetContext.class);
private static NetContext instance;
private ApplicationContext applicationContext;
private IConfigManager configManager;
private IPacketService packetService;
private IPacketDispatcher packetDispatcher;
private ISessionManager sessionManager;
private IConsumer consumer;
public static NetContext getNetContext() {
return instance;
}
public static ApplicationContext getApplicationContext() {
return instance.applicationContext;
}
public static IConfigManager getConfigManager() {
return instance.configManager;
}
public static IPacketService getPacketService() {
return instance.packetService;
}
public static ISessionManager getSessionManager() {
return instance.sessionManager;
}
public static IPacketDispatcher getDispatcher() {
return instance.packetDispatcher;
}
public static IConsumer getConsumer() {
return instance.consumer;
}
@Override
public void onApplicationEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
if (instance != null) {
return;
}
NetContext.instance = this;
instance.applicationContext = event.getApplicationContext();
instance.configManager = applicationContext.getBean(IConfigManager.class);
instance.packetService = applicationContext.getBean(IPacketService.class);
instance.packetDispatcher = applicationContext.getBean(IPacketDispatcher.class);
instance.consumer = applicationContext.getBean(IConsumer.class);
instance.sessionManager = applicationContext.getBean(ISessionManager.class);
var beanNames = applicationContext.getBeanDefinitionNames();
var processor = applicationContext.getBean(NetProcessor.class);
for (var beanName : beanNames) {
processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName);
}
NetContext.getPacketService().init();
NetContext.getConfigManager().initRegistry();
} else if (event instanceof ContextClosedEvent) {
shutdownBefore();
shutdownAfter();
}
}
@Override
public int getOrder() {
return 0;
}
public synchronized static void shutdownBefore() {
SchedulerContext.shutdown();
}
public static synchronized void shutdownAfter() {
// 关闭zookeeper的客户端
NetContext.getConfigManager().getRegistry().shutdown();
// 先关闭所有session
NetContext.getSessionManager().shutdown();
// 关闭客户端和服务器
TcpClient.shutdown();
AbstractServer.shutdownAllServers();
// 关闭TaskManager
try {
Field field = EventBus.class.getDeclaredField("executors");
ReflectionUtils.makeAccessible(field);
var executors = (ExecutorService[]) ReflectionUtils.getField(field, TaskManager.getInstance());
for (ExecutorService executor : executors) {
ThreadUtils.shutdown(executor);
}
} catch (Throwable e) {
logger.error("Net thread pool failed shutdown: " + ExceptionUtils.getMessage(e));
return;
}
logger.info("Net shutdown gracefully.");
}
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.manager;
import com.zfoo.net.config.model.NetConfig;
import com.zfoo.net.consumer.balancer.AbstractConsumerLoadBalancer;
import com.zfoo.net.consumer.registry.IRegistry;
import com.zfoo.net.consumer.registry.ZookeeperRegistry;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.registration.ProtocolModule;
import com.zfoo.protocol.util.AssertionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Objects;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ConfigManager implements IConfigManager {
private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
/**
* 本地配置
*/
private NetConfig localConfig;
private AbstractConsumerLoadBalancer consumerLoadBalancer;
/**
* 注册中心
*/
private IRegistry registry;
@Override
public NetConfig getLocalConfig() {
return localConfig;
}
public void setLocalConfig(NetConfig localConfig) {
this.localConfig = localConfig;
}
@Override
public AbstractConsumerLoadBalancer consumerLoadBalancer() {
return consumerLoadBalancer;
}
@Override
public void initRegistry() {
// 通过protocol,写入provider的module的id和version
var providerConfig = localConfig.getProviderConfig();
if (Objects.nonNull(providerConfig) && CollectionUtils.isNotEmpty(providerConfig.getModules())) {
var providerModules = new ArrayList<ProtocolModule>(providerConfig.getModules().size());
for (var providerModule : providerConfig.getModules()) {
var module = ProtocolManager.moduleByModuleName(providerModule.getName());
AssertionUtils.isTrue(module != null, "服务提供者[name:{}]在协议文件中不存在", providerModule.getName());
providerModules.add(module);
}
providerConfig.setModules(providerModules);
}
// 通过protocol,写入consumer的module的id和version
var consumerConfig = localConfig.getConsumerConfig();
if (Objects.nonNull(consumerConfig) && CollectionUtils.isNotEmpty(consumerConfig.getModules())) {
var consumerModules = new ArrayList<ProtocolModule>(consumerConfig.getModules().size());
for (var providerModule : consumerConfig.getModules()) {
var module = ProtocolManager.moduleByModuleName(providerModule.getName());
AssertionUtils.isTrue(module != null, "消费者[name:{}]在协议文件中不存在", providerModule.getName());
consumerModules.add(module);
}
consumerConfig.setModules(consumerModules);
consumerLoadBalancer = AbstractConsumerLoadBalancer.valueOf(consumerConfig.getLoadBalancer());
}
registry = new ZookeeperRegistry();
registry.start();
}
@Override
public IRegistry getRegistry() {
return registry;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.manager;
import com.zfoo.net.config.model.NetConfig;
import com.zfoo.net.consumer.balancer.AbstractConsumerLoadBalancer;
import com.zfoo.net.consumer.registry.IRegistry;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IConfigManager {
NetConfig getLocalConfig();
AbstractConsumerLoadBalancer consumerLoadBalancer();
void initRegistry();
IRegistry getRegistry();
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.model;
import com.zfoo.protocol.registration.ProtocolModule;
import java.util.List;
import java.util.Objects;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ConsumerConfig {
private String loadBalancer;
private List<ProtocolModule> modules;
public static ConsumerConfig valueOf(String loadBalancer, List<ProtocolModule> modules) {
ConsumerConfig config = new ConsumerConfig();
config.loadBalancer = loadBalancer;
config.modules = modules;
return config;
}
public static ConsumerConfig valueOf(List<ProtocolModule> modules) {
ConsumerConfig config = new ConsumerConfig();
config.modules = modules;
return config;
}
public String getLoadBalancer() {
return loadBalancer;
}
public void setLoadBalancer(String loadBalancer) {
this.loadBalancer = loadBalancer;
}
public List<ProtocolModule> getModules() {
return modules;
}
public void setModules(List<ProtocolModule> modules) {
this.modules = modules;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ConsumerConfig that = (ConsumerConfig) o;
return Objects.equals(modules, that.modules);
}
@Override
public int hashCode() {
return Objects.hash(modules);
}
}
@@ -0,0 +1,81 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.model;
import java.util.Map;
import java.util.Objects;
/**
* @author jaysunxiao
* @version 3.0
*/
public class HostConfig {
private String center;
private String user;
private String password;
private Map<String, String> addressMap;
public void setCenter(String center) {
this.center = center;
}
public String getCenter() {
return center;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Map<String, String> getAddressMap() {
return addressMap;
}
public void setAddressMap(Map<String, String> addressMap) {
this.addressMap = addressMap;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HostConfig that = (HostConfig) o;
return Objects.equals(center, that.center) &&
Objects.equals(user, that.user) &&
Objects.equals(password, that.password) &&
Objects.equals(addressMap, that.addressMap);
}
@Override
public int hashCode() {
return Objects.hash(center, user, password, addressMap);
}
}
@@ -0,0 +1,90 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.model;
import java.util.Map;
import java.util.Objects;
/**
* @author jaysunxiao
* @version 3.0
*/
public class MonitorConfig {
private String center;
private String user;
private String password;
private Map<String, String> addressMap;
public static MonitorConfig valueOf(String center, String user, String password, Map<String, String> addressMap) {
MonitorConfig config = new MonitorConfig();
config.center = center;
config.user = user;
config.password = password;
config.addressMap = addressMap;
return config;
}
public String getCenter() {
return center;
}
public void setCenter(String center) {
this.center = center;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Map<String, String> getAddressMap() {
return addressMap;
}
public void setAddressMap(Map<String, String> addressMap) {
this.addressMap = addressMap;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MonitorConfig that = (MonitorConfig) o;
return Objects.equals(center, that.center) &&
Objects.equals(user, that.user) &&
Objects.equals(password, that.password) &&
Objects.equals(addressMap, that.addressMap);
}
@Override
public int hashCode() {
return Objects.hash(center, user, password, addressMap);
}
}
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.model;
import com.zfoo.net.consumer.registry.RegisterVO;
import com.zfoo.protocol.generate.GenerateOperation;
import java.util.Objects;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NetConfig {
private String id;
private String protocolLocation;
/**
* 协议生成属性变量对应于{@link GenerateOperation}
*/
private boolean foldProtocol;
private String protocolParam;
private boolean generateJsProtocol;
private boolean generateCsProtocol;
private boolean generateLuaProtocol;
private RegistryConfig registryConfig;
private MonitorConfig monitorConfig;
private HostConfig hostConfig;
private ProviderConfig providerConfig;
private ConsumerConfig consumerConfig;
public RegisterVO toLocalRegisterVO() {
return RegisterVO.valueOf(id, providerConfig, consumerConfig);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getProtocolLocation() {
return protocolLocation;
}
public void setProtocolLocation(String protocolLocation) {
this.protocolLocation = protocolLocation;
}
public boolean isFoldProtocol() {
return foldProtocol;
}
public void setFoldProtocol(boolean foldProtocol) {
this.foldProtocol = foldProtocol;
}
public String getProtocolParam() {
return protocolParam;
}
public void setProtocolParam(String protocolParam) {
this.protocolParam = protocolParam;
}
public boolean isGenerateJsProtocol() {
return generateJsProtocol;
}
public void setGenerateJsProtocol(boolean generateJsProtocol) {
this.generateJsProtocol = generateJsProtocol;
}
public boolean isGenerateCsProtocol() {
return generateCsProtocol;
}
public void setGenerateCsProtocol(boolean generateCsProtocol) {
this.generateCsProtocol = generateCsProtocol;
}
public boolean isGenerateLuaProtocol() {
return generateLuaProtocol;
}
public void setGenerateLuaProtocol(boolean generateLuaProtocol) {
this.generateLuaProtocol = generateLuaProtocol;
}
public RegistryConfig getRegistryConfig() {
return registryConfig;
}
public void setRegistryConfig(RegistryConfig registryConfig) {
this.registryConfig = registryConfig;
}
public MonitorConfig getMonitorConfig() {
return monitorConfig;
}
public void setMonitorConfig(MonitorConfig monitorConfig) {
this.monitorConfig = monitorConfig;
}
public HostConfig getHostConfig() {
return hostConfig;
}
public void setHostConfig(HostConfig hostConfig) {
this.hostConfig = hostConfig;
}
public ProviderConfig getProviderConfig() {
return providerConfig;
}
public void setProviderConfig(ProviderConfig providerConfig) {
this.providerConfig = providerConfig;
}
public ConsumerConfig getConsumerConfig() {
return consumerConfig;
}
public void setConsumerConfig(ConsumerConfig consumerConfig) {
this.consumerConfig = consumerConfig;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NetConfig netConfig = (NetConfig) o;
return generateJsProtocol == netConfig.generateJsProtocol &&
Objects.equals(id, netConfig.id) &&
Objects.equals(protocolLocation, netConfig.protocolLocation) &&
Objects.equals(registryConfig, netConfig.registryConfig) &&
Objects.equals(monitorConfig, netConfig.monitorConfig) &&
Objects.equals(hostConfig, netConfig.hostConfig) &&
Objects.equals(providerConfig, netConfig.providerConfig) &&
Objects.equals(consumerConfig, netConfig.consumerConfig);
}
@Override
public int hashCode() {
return Objects.hash(id, protocolLocation, generateJsProtocol, registryConfig, monitorConfig, hostConfig, providerConfig, consumerConfig);
}
}
@@ -0,0 +1,107 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.model;
import com.zfoo.protocol.registration.ProtocolModule;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.net.HostAndPort;
import com.zfoo.util.net.NetUtils;
import java.util.List;
import java.util.Objects;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ProviderConfig {
public static transient final int DEFAULT_PORT = 12400;
/**
* 对应于ITaskDispatch
*/
private String dispatch;
private String dispatchThread;
private String address;
private List<ProtocolModule> modules;
public static ProviderConfig valueOf(String address, List<ProtocolModule> modules) {
ProviderConfig config = new ProviderConfig();
config.address = address;
config.modules = modules;
return config;
}
public HostAndPort localHostAndPortOrDefault() {
if (StringUtils.isBlank(address)) {
var defaultHostAndPort = HostAndPort.valueOf(NetUtils.getLocalhostStr(), NetUtils.getAvailablePort(ProviderConfig.DEFAULT_PORT));
this.address = defaultHostAndPort.toHostAndPortStr();
return defaultHostAndPort;
}
return HostAndPort.valueOf(address);
}
public String getDispatch() {
return dispatch;
}
public void setDispatch(String dispatch) {
this.dispatch = dispatch;
}
public String getDispatchThread() {
return dispatchThread;
}
public void setDispatchThread(String dispatchThread) {
this.dispatchThread = dispatchThread;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<ProtocolModule> getModules() {
return modules;
}
public void setModules(List<ProtocolModule> modules) {
this.modules = modules;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ProviderConfig that = (ProviderConfig) o;
return Objects.equals(address, that.address) && Objects.equals(modules, that.modules);
}
@Override
public int hashCode() {
return Objects.hash(address, modules);
}
}
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.config.model;
import com.zfoo.protocol.util.StringUtils;
import java.util.Map;
import java.util.Objects;
/**
* @author jaysunxiao
* @version 3.0
*/
public class RegistryConfig {
private String center;
private String user;
private String password;
private Map<String, String> addressMap;
public static RegistryConfig valueOf(String center, String user, String password, Map<String, String> addressMap) {
RegistryConfig config = new RegistryConfig();
config.center = center;
config.user = user;
config.password = password;
config.addressMap = addressMap;
return config;
}
public boolean hasZookeeperAuthor() {
return !(StringUtils.isBlank(user) || StringUtils.isBlank(password));
}
public String toZookeeperAuthor() {
return user + StringUtils.COLON + password;
}
public String getCenter() {
return center;
}
public void setCenter(String center) {
this.center = center;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Map<String, String> getAddressMap() {
return addressMap;
}
public void setAddressMap(Map<String, String> addressMap) {
this.addressMap = addressMap;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RegistryConfig that = (RegistryConfig) o;
return Objects.equals(center, that.center) &&
Objects.equals(user, that.user) &&
Objects.equals(password, that.password) &&
Objects.equals(addressMap, that.addressMap);
}
@Override
public int hashCode() {
return Objects.hash(center, user, password, addressMap);
}
}
@@ -0,0 +1,93 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.balancer;
import com.zfoo.net.NetContext;
import com.zfoo.net.consumer.registry.RegisterVO;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.registration.ProtocolModule;
import com.zfoo.protocol.util.StringUtils;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class AbstractConsumerLoadBalancer implements IConsumerLoadBalancer {
public static AbstractConsumerLoadBalancer valueOf(String loadBalancer) {
AbstractConsumerLoadBalancer balancer;
switch (loadBalancer) {
case "random":
balancer = RandomConsumerLoadBalancer.getInstance();
break;
case "consistent-hash":
balancer = ConsistentHashConsumerLoadBalancer.getInstance();
break;
case "shortest-time":
balancer = ShortestTimeConsumerLoadBalancer.getInstance();
break;
default:
throw new RuntimeException(StringUtils.format("无法识别负载均衡器[{}]", loadBalancer));
}
return balancer;
}
public List<Session> getSessionsByPacket(IPacket packet) {
return getSessionsByModule(ProtocolManager.moduleByProtocolId(packet.protocolId()));
}
public List<Session> getSessionsByModule(ProtocolModule module) {
var clientSessionMap = NetContext.getSessionManager().getClientSessionMap();
var sessions = clientSessionMap.values().stream()
.filter(it -> {
var attribute = it.getAttribute(AttributeType.CONSUMER);
if (Objects.nonNull(attribute)) {
var registerVO = (RegisterVO) attribute;
if (Objects.nonNull(registerVO.getProviderConfig()) && registerVO.getProviderConfig().getModules().contains(module)) {
return true;
} else {
return false;
}
} else {
return false;
}
})
.collect(Collectors.toList());
return sessions;
}
public boolean sessionHasModule(Session session, IPacket packet) {
var attribute = session.getAttribute(AttributeType.CONSUMER);
if (Objects.isNull(attribute)) {
return false;
}
var registerVO = (RegisterVO) attribute;
if (Objects.isNull(registerVO.getProviderConfig())) {
return false;
}
var module = ProtocolManager.moduleByProtocolId(packet.protocolId());
return registerVO.getProviderConfig().getModules().contains(module);
}
}
@@ -0,0 +1,113 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.balancer;
import com.zfoo.net.NetContext;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.model.Pair;
import com.zfoo.protocol.registration.ProtocolModule;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.math.ConsistentHash;
import org.springframework.lang.Nullable;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* 一致性hash负载均衡器,同一个session总是发到同一提供者
* <p>
* 通过argument计算一致性hash
*
* @author jaysunxiao
* @version 3.0
*/
public class ConsistentHashConsumerLoadBalancer extends AbstractConsumerLoadBalancer {
public static final ConsistentHashConsumerLoadBalancer INSTANCE = new ConsistentHashConsumerLoadBalancer();
private volatile int lastClientSessionChangeId = 0;
private static final Map<ProtocolModule, ConsistentHash<String, Long>> consistentHashMap = new ConcurrentHashMap<>();
private static final int VIRTUAL_NODE_NUMS = 200;
private ConsistentHashConsumerLoadBalancer() {
}
public static ConsistentHashConsumerLoadBalancer getInstance() {
return INSTANCE;
}
/**
* 通过argument的toString计算一致性hash,所以传入的argument一般要能代表唯一性,比如用户的id
*
* @param packet 请求包
* @param argument 参数,一般要能代表唯一性,比如用户的id
* @return 调用的session
*/
@Override
public Session loadBalancer(IPacket packet, Object argument) {
if (argument == null) {
return RandomConsumerLoadBalancer.getInstance().loadBalancer(packet, argument);
}
// 如果更新时间不匹配,则更新到最新的服务提供者
var currentClientSessionChangeId = NetContext.getSessionManager().getClientSessionChangeId();
if (currentClientSessionChangeId != lastClientSessionChangeId) {
var modules = new HashSet<>(consistentHashMap.keySet());
for (var module : modules) {
updateModuleToConsistentHash(module);
}
lastClientSessionChangeId = currentClientSessionChangeId;
}
var module = ProtocolManager.moduleByProtocolId(packet.protocolId());
var consistentHash = consistentHashMap.get(module);
if (consistentHash == null) {
consistentHash = updateModuleToConsistentHash(module);
}
if (consistentHash == null) {
throw new RuntimeException(StringUtils.format("没有服务提供者提供服务[{}]", module));
}
var sid = consistentHash.getRealNode(argument).getValue();
return NetContext.getSessionManager().getClientSession(sid);
}
@Nullable
private ConsistentHash<String, Long> updateModuleToConsistentHash(ProtocolModule module) {
var sessionStringList = getSessionsByModule(module)
.stream()
.map(session -> new Pair<>(session.getAttribute(AttributeType.CONSUMER).toString(), session.getSid()))
.sorted((a, b) -> a.getKey().compareTo(b.getKey()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(sessionStringList) && !consistentHashMap.containsKey(module)) {
consistentHashMap.remove(module);
return null;
}
var consistentHash = new ConsistentHash<>(sessionStringList, VIRTUAL_NODE_NUMS);
consistentHashMap.put(module, consistentHash);
return consistentHash;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.balancer;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import org.springframework.lang.Nullable;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IConsumerLoadBalancer {
/**
* 只有一致性hash会使用这个argument参数,如果在一致性hash没有传入argument默认使用随机负载均衡
*
* @param packet 请求包
* @param argument 计算参数
* @return 一个服务提供者的session
*/
Session loadBalancer(IPacket packet, @Nullable Object argument);
default void beforeLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) {
}
default void afterLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) {
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.balancer;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.exception.RunException;
import com.zfoo.util.math.RandomUtils;
/**
* 随机负载均衡器,任选服务提供者的其中之一
*
* @author jaysunxiao
* @version 3.0
*/
public class RandomConsumerLoadBalancer extends AbstractConsumerLoadBalancer {
private static final RandomConsumerLoadBalancer INSTANCE = new RandomConsumerLoadBalancer();
private RandomConsumerLoadBalancer() {
}
public static RandomConsumerLoadBalancer getInstance() {
return INSTANCE;
}
@Override
public Session loadBalancer(IPacket packet, Object argument) {
var module = ProtocolManager.moduleByProtocolId(packet.protocolId());
var sessions = getSessionsByModule(module);
if (sessions.isEmpty()) {
throw new RunException("没有服务提供者提供服务[{}]", module);
}
return RandomUtils.randomEle(sessions);
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.balancer;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.exception.RunException;
import com.zfoo.scheduler.util.TimeUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 最少时间调用负载均衡器,优先选择调用时间最短的session
*
* @author jaysunxiao
* @version 3.0
*/
public class ShortestTimeConsumerLoadBalancer extends AbstractConsumerLoadBalancer {
private static final ShortestTimeConsumerLoadBalancer INSTANCE = new ShortestTimeConsumerLoadBalancer();
private ShortestTimeConsumerLoadBalancer() {
}
public static ShortestTimeConsumerLoadBalancer getInstance() {
return INSTANCE;
}
@Override
public Session loadBalancer(IPacket packet, Object argument) {
var module = ProtocolManager.moduleByProtocolId(packet.protocolId());
var sessions = getSessionsByModule(module);
if (sessions.isEmpty()) {
throw new RunException("没有服务提供者提供服务[{}]", module);
}
var sortedSessions = sessions.stream()
.sorted((a, b) -> {
var aMap = (Map<Short, Long>) a.getAttribute(AttributeType.RESPONSE_TIME);
var bMap = (Map<Short, Long>) b.getAttribute(AttributeType.RESPONSE_TIME);
if (aMap == null) {
return -1;
} else if (bMap == null) {
return 1;
} else {
var aTime = aMap.get(packet.protocolId());
var bTime = bMap.get(packet.protocolId());
if (aTime == null) {
return -1;
} else if (bTime == null) {
return 1;
} else {
return (aTime > bTime) ? 1 : -1;
}
}
}).findFirst();
return sortedSessions.get();
}
@Override
public void beforeLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) {
// 因为要通过最短响应时间来路由分发消息,这里使用更精确的时间
attachment.setTimestamp(TimeUtils.currentTimeMillis());
}
@Override
public void afterLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) {
var map = (Map<Short, Long>) session.getAttribute(AttributeType.RESPONSE_TIME);
if (map == null) {
map = new ConcurrentHashMap<>();
session.putAttribute(AttributeType.RESPONSE_TIME, map);
}
map.put(packet.protocolId(), TimeUtils.currentTimeMillis() - attachment.getTimestamp());
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.event;
import com.zfoo.event.model.event.IEvent;
import com.zfoo.net.consumer.registry.RegisterVO;
import com.zfoo.net.session.model.Session;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ConsumerStartEvent implements IEvent {
private RegisterVO registerVO;
private Session session;
public static ConsumerStartEvent valueOf(RegisterVO registerVO, Session session) {
var event = new ConsumerStartEvent();
event.registerVO = registerVO;
event.session = session;
return event;
}
public RegisterVO getRegisterVO() {
return registerVO;
}
public void setRegisterVO(RegisterVO registerVO) {
this.registerVO = registerVO;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.registry;
import org.apache.zookeeper.CreateMode;
import org.springframework.lang.Nullable;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IRegistry {
void start();
void checkConsumer();
void addData(String path, byte[] bytes, CreateMode mode);
void removeData(String path);
byte[] queryData(String path);
boolean haveNode(String path);
List<String> children(String path);
Set<RegisterVO> remoteProviderRegisterSet();
/**
* 监听path路径下的更新
*
* @param listenerPath 需要监听的路径
* @param updateCallback 回调方法,第一个参数是路径,第二个是变化的内容
* @param removeCallback 回调方法,第一个参数是路径,第二个是变化的内容
*/
void addListener(String listenerPath, @Nullable BiConsumer<String, byte[]> updateCallback, @Nullable Consumer<String> removeCallback);
void shutdown();
}
@@ -0,0 +1,181 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.registry;
import com.zfoo.net.config.model.ConsumerConfig;
import com.zfoo.net.config.model.ProviderConfig;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.registration.ProtocolModule;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.security.IdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author jaysunxiao
* @version 3.0
*/
public class RegisterVO {
private static final Logger logger = LoggerFactory.getLogger(RegisterVO.class);
private static final String uuid = IdUtils.getUUID();
private String id;
private ProviderConfig providerConfig;
private ConsumerConfig consumerConfig;
public static boolean providerHasConsumerModule(RegisterVO provider, RegisterVO consumer) {
if (Objects.isNull(provider) || Objects.isNull(provider.providerConfig) || CollectionUtils.isEmpty(provider.providerConfig.getModules())
|| Objects.isNull(consumer) || Objects.isNull(consumer.consumerConfig) || CollectionUtils.isEmpty(consumer.consumerConfig.getModules())) {
return false;
}
return provider.getProviderConfig().getModules().stream().anyMatch(it -> consumer.getConsumerConfig().getModules().contains(it));
}
public static RegisterVO valueOf(String id, ProviderConfig providerConfig, ConsumerConfig consumerConfig) {
RegisterVO config = new RegisterVO();
config.id = id;
config.providerConfig = providerConfig;
config.consumerConfig = consumerConfig;
return config;
}
@Nullable
public static RegisterVO parseString(String str) {
try {
var vo = new RegisterVO();
var splits = str.split("\\|");
vo.id = splits[0].trim();
String providerAddress = null;
for (int i = 1; i < splits.length; i++) {
var s = splits[i].trim();
if (s.startsWith("provider")) {
var providerModules = parseModules(s);
vo.providerConfig = ProviderConfig.valueOf(providerAddress, providerModules);
} else if (s.startsWith("consumer")) {
var consumerModules = parseModules(s);
vo.consumerConfig = ConsumerConfig.valueOf(consumerModules);
} else {
providerAddress = s;
}
}
return vo;
} catch (Exception e) {
logger.error(ExceptionUtils.getMessage(e));
return null;
}
}
private static List<ProtocolModule> parseModules(String str) {
var moduleSplits = StringUtils.substringBeforeLast(
StringUtils.substringAfterFirst(str, StringUtils.LEFT_SQUARE_BRACKET)
, StringUtils.RIGHT_SQUARE_BRACKET).split(StringUtils.COMMA);
var modules = Arrays.stream(moduleSplits)
.map(it -> it.trim())
.map(it -> it.split(StringUtils.HYPHEN))
.map(it -> new ProtocolModule(Byte.parseByte(it[0]), it[1], it[2]))
.collect(Collectors.toList());
return modules;
}
public String toProviderString() {
return toString();
}
public String toConsumerString() {
return toString() +
StringUtils.SPACE + StringUtils.VERTICAL_BAR + StringUtils.SPACE +
uuid;
}
@Override
public String toString() {
var builder = new StringBuilder();
builder.append(id);
if (Objects.nonNull(providerConfig)) {
var providerAddress = providerConfig.getAddress();
if (StringUtils.isBlank(providerAddress)) {
throw new RuntimeException(StringUtils.format("providerConfig的address不能为空"));
}
builder.append(StringUtils.SPACE).append(StringUtils.VERTICAL_BAR).append(StringUtils.SPACE);
builder.append(providerAddress);
builder.append(StringUtils.SPACE).append(StringUtils.VERTICAL_BAR).append(StringUtils.SPACE);
var providerModules = providerConfig.getModules().stream()
.map(it -> StringUtils.joinWith(StringUtils.HYPHEN, it.getId(), it.getName(), ProtocolModule.versionNumToStr(it.getVersion())))
.collect(Collectors.toList());
builder.append(StringUtils.format("provider:[{}]"
, StringUtils.joinWith(StringUtils.COMMA + StringUtils.SPACE, providerModules.toArray())));
}
if (Objects.nonNull(consumerConfig)) {
builder.append(StringUtils.SPACE).append(StringUtils.VERTICAL_BAR).append(StringUtils.SPACE);
var consumerModules = consumerConfig.getModules().stream()
.map(it -> StringUtils.joinWith(StringUtils.HYPHEN, it.getId(), it.getName(), ProtocolModule.versionNumToStr(it.getVersion())))
.collect(Collectors.toList());
builder.append(StringUtils.format("consumer:[{}]"
, StringUtils.joinWith(StringUtils.COMMA + StringUtils.SPACE, consumerModules.toArray())));
}
return builder.toString();
}
public String getId() {
return id;
}
public ProviderConfig getProviderConfig() {
return providerConfig;
}
public ConsumerConfig getConsumerConfig() {
return consumerConfig;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RegisterVO that = (RegisterVO) o;
return Objects.equals(id, that.id) && Objects.equals(providerConfig, that.providerConfig)
&& Objects.equals(consumerConfig, that.consumerConfig);
}
@Override
public int hashCode() {
return Objects.hash(id, providerConfig, consumerConfig);
}
}
@@ -0,0 +1,622 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.registry;
import com.zfoo.event.manager.EventBus;
import com.zfoo.net.NetContext;
import com.zfoo.net.consumer.event.ConsumerStartEvent;
import com.zfoo.net.core.tcp.TcpClient;
import com.zfoo.net.core.tcp.TcpServer;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.util.SessionUtils;
import com.zfoo.protocol.collection.ConcurrentArrayList;
import com.zfoo.protocol.collection.ConcurrentHashSet;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.SchedulerContext;
import com.zfoo.util.ThreadUtils;
import com.zfoo.util.net.HostAndPort;
import io.netty.util.concurrent.FastThreadLocalThread;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.CuratorCache;
import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* 服务注册,服务发现
*
* @author jaysunxiao
* @version 3.0
*/
public class ZookeeperRegistry implements IRegistry {
private static final Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class);
private static final String ROOT_PATH = "/zfoo";
private static final String PROVIDER_ROOT_PATH = ROOT_PATH + "/provider";
private static final String CONSUMER_ROOT_PATH = ROOT_PATH + "/consumer";
private static final long RETRY_SECONDS = 5;
private static final ExecutorService executor = Executors.newSingleThreadExecutor(new ConfigThreadFactory());
private static class ConfigThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
// config-p1-t1 = config-pool-1-thread-1
ConfigThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "config-p" + poolNumber.getAndIncrement() + "-t";
}
@Override
public Thread newThread(Runnable runnable) {
Thread t = new FastThreadLocalThread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
t.setDaemon(false);
t.setPriority(Thread.NORM_PRIORITY);
t.setUncaughtExceptionHandler((thread, e) -> logger.error(thread.toString(), e));
return t;
}
}
private CuratorFramework curator;
/**
* provider的监听
*/
private CuratorCache providerCuratorCache;
/**
* consumer需要消费的provider集合
*/
private Set<RegisterVO> providerCacheSet = new ConcurrentHashSet<>();
/**
* 本地注册信息
*/
private RegisterVO localRegisterVO = NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO();
/**
* addListener中的cache全部会被添加到这个集合中,这个集合不包括providerCuratorCache
*/
private List<CuratorCache> listenerList = new ConcurrentArrayList<>();
@Override
public void start() {
var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig();
if (Objects.isNull(registryConfig)) {
logger.warn("没有配置注册中心registry,将不能启用服务注册和发现");
return;
}
// 先启动本地服务提供者,再启动curator
startProvider();
startCurator();
startProviderCache();
}
private void startProvider() {
var providerConfig = NetContext.getConfigManager().getLocalConfig().getProviderConfig();
if (Objects.isNull(providerConfig)) {
logger.warn("没有发现服务提供者,不对外提供服务");
return;
}
var providerServer = new TcpServer(providerConfig.localHostAndPortOrDefault());
providerServer.start();
}
private void startCurator() {
var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig();
if (!registryConfig.getCenter().toLowerCase().matches("zookeeper")) {
throw new IllegalArgumentException(StringUtils
.format("[center:{}]注册中心只能是zookeeper", JsonUtils.object2String(registryConfig)));
}
var zookeeperConnectStr = HostAndPort.toHostAndPortListStr(HostAndPort.toHostAndPortList(registryConfig.getAddressMap().values()));
var builder = CuratorFrameworkFactory.builder();
builder.connectString(zookeeperConnectStr);
if (registryConfig.hasZookeeperAuthor()) {
builder.authorization("digest", StringUtils.bytes(registryConfig.toZookeeperAuthor()));
}
builder.sessionTimeoutMs(40_000);
builder.connectionTimeoutMs(10_000);
builder.retryPolicy(new RetryNTimes(1, 3_000));
curator = builder.build();
curator.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState state) {
switch (state) {
case LOST:
// 忽略配置中心失去连接,使用本地配置的缓存
logger.error("[zookeeper:{}]失去连接,使用缓存", zookeeperConnectStr);
break;
case SUSPENDED:
case READ_ONLY:
logger.warn("[zookeeper:{}]忽略的[state{}]", zookeeperConnectStr, state);
break;
case CONNECTED:
case RECONNECTED:
createZookeeperRootPath();
initZookeeper();
break;
default:
logger.error("[zookeeper:{}]未知状态[state{}]", zookeeperConnectStr, state);
}
}
}, executor);
curator.start();
try {
curator.blockUntilConnected();
} catch (Throwable t) {
throw new RuntimeException("启动zookeeper异常", t);
}
}
private void createZookeeperRootPath() {
try {
// 创建zookeeper的根路径
var rootStat = curator.checkExists().forPath(ROOT_PATH);
if (Objects.isNull(rootStat)) {
var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig();
var builder = curator.create();
builder.creatingParentsIfNeeded();
if (registryConfig.hasZookeeperAuthor()) {
var zookeeperAuthorStr = registryConfig.toZookeeperAuthor();
var aclList = List.of(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest(zookeeperAuthorStr))));
builder.withACL(aclList);
}
builder.withMode(CreateMode.PERSISTENT);
builder.forPath(ROOT_PATH, StringUtils.bytes(registryConfig.getCenter()));
} else {
var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig();
var bytes = curator.getData().storingStatIn(new Stat()).forPath(ROOT_PATH);
var rootPathData = StringUtils.bytesToString(bytes);
// 检查zookeeper根节点的内容
if (!rootPathData.equals(registryConfig.getCenter())) {
throw new RuntimeException(StringUtils.format("zookeeper的rootPath[{}]内容配置错误[{}],期望的内容是[{}],请检查相关节点并重新启动", ROOT_PATH, rootPathData, registryConfig.getCenter()));
}
// 检查zookeeper根节点的权限
if (registryConfig.hasZookeeperAuthor()) {
try {
var providerRootPathAclList = curator.getACL().forPath(ROOT_PATH);
AssertionUtils.notEmpty(providerRootPathAclList);
AssertionUtils.isTrue(providerRootPathAclList.size() == 1);
var zookeeperAuthorStr = registryConfig.toZookeeperAuthor();
var aclList = List.of(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest(zookeeperAuthorStr))));
AssertionUtils.isTrue(providerRootPathAclList.get(0).equals(aclList.get(0)));
} catch (Exception e) {
throw new RuntimeException(StringUtils.format("zookeeper的rootPath[{}]权限配置错误[{}]", ROOT_PATH, ExceptionUtils.getMessage(e)));
}
}
}
var providerStat = curator.checkExists().forPath(PROVIDER_ROOT_PATH);
if (Objects.isNull(providerStat)) {
curator.create()
.withMode(CreateMode.PERSISTENT)
.forPath(PROVIDER_ROOT_PATH, StringUtils.EMPTY_BYTES);
}
var consumerStat = curator.checkExists().forPath(CONSUMER_ROOT_PATH);
if (Objects.isNull(consumerStat)) {
curator.create()
.withMode(CreateMode.PERSISTENT)
.forPath(CONSUMER_ROOT_PATH, StringUtils.EMPTY_BYTES);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void startProviderCache() {
// 初始化providerCache
providerCuratorCache = CuratorCache.builder(curator, PROVIDER_ROOT_PATH)
.withExceptionHandler(e -> {
logger.error("providerCuratorCache未知异常", e);
initZookeeper();
})
.build();
providerCuratorCache.listenable().addListener(new CuratorCacheListener() {
@Override
public void event(Type type, ChildData oldData, ChildData newData) {
switch (type) {
case NODE_CHANGED:
logger.error("不需要处理的[oldData:{}][newData:{}]", childDataToString(oldData), childDataToString(newData));
initZookeeper();
break;
case NODE_CREATED:
var providerStr = StringUtils.substringAfterFirst(newData.getPath(), PROVIDER_ROOT_PATH + StringUtils.SLASH);
var provider = RegisterVO.parseString(providerStr);
if (RegisterVO.providerHasConsumerModule(provider, localRegisterVO)) {
providerCacheSet.add(provider);
checkConsumer();
logger.info("发现新的订阅服务[{}]", providerStr);
}
break;
case NODE_DELETED:
var oldProviderStr = StringUtils.substringAfterFirst(oldData.getPath(), PROVIDER_ROOT_PATH + StringUtils.SLASH);
var oldProvider = RegisterVO.parseString(oldProviderStr);
if (providerCacheSet.contains(oldProvider)) {
providerCacheSet.remove(oldProvider);
checkConsumer();
logger.info("取消订阅服务[{}]", oldProviderStr);
}
break;
default:
}
}
@Override
public void initialized() {
initZookeeper();
}
}, executor);
providerCuratorCache.start();
}
private void initZookeeper() {
executor.execute(() -> {
try {
initLocalProvider();
initConsumerCache();
} catch (Exception e) {
logger.error("zookeeper初始化失败,等待[{}]秒,重新初始化", RETRY_SECONDS, e);
SchedulerContext.getSchedulerManager().schedule(new Runnable() {
@Override
public void run() {
initZookeeper();
}
}, RETRY_SECONDS, TimeUnit.SECONDS);
}
});
}
private void initLocalProvider() throws Exception {
if (Objects.nonNull(localRegisterVO.getProviderConfig())) {
var localProviderVoStr = localRegisterVO.toProviderString();
var localProviderPath = PROVIDER_ROOT_PATH + StringUtils.SLASH + localProviderVoStr;
var localProviderStat = curator.checkExists().forPath(localProviderPath);
if (Objects.isNull(localProviderStat)) {
curator.create()
.withMode(CreateMode.EPHEMERAL)
.forPath(localProviderPath, StringUtils.EMPTY.getBytes());
logger.info("注册服务成功[{}]", localProviderVoStr);
} else {
// 如果服务提供者已经有节点了,防止这个节点是是上次来不及删除的临时节点
var curatorSessionId = curator.getZookeeperClient().getZooKeeper().getSessionId();
var providerNodeSessionId = localProviderStat.getEphemeralOwner();
if (curatorSessionId != providerNodeSessionId) {
curator.delete()
.guaranteed()
.deletingChildrenIfNeeded()
.withVersion(localProviderStat.getVersion())
.forPath(localProviderPath);
throw new RuntimeException(StringUtils.format("curator[sessionId:{}]和providerNode[sessionId:{}]的session不一致"
, curatorSessionId, providerNodeSessionId));
}
}
}
}
private void initConsumerCache() throws Exception {
// 初始化providerCacheSet
var remoteProviderSet = curator.getChildren().forPath(PROVIDER_ROOT_PATH).stream()
.filter(it -> !StringUtils.isBlank(it) && !"null".equals(it))
.map(it -> RegisterVO.parseString(it))
.filter(it -> Objects.nonNull(it))
.filter(it -> RegisterVO.providerHasConsumerModule(it, localRegisterVO))
.collect(Collectors.toSet());
providerCacheSet.clear();
providerCacheSet.addAll(remoteProviderSet);
// 初始化consumerproviderCacheSet改变会导致消费者改变
checkConsumer();
}
@Override
public void checkConsumer() {
if (curator.getState() == CuratorFrameworkState.STOPPED) {
return;
}
executor.execute(() -> doCheckConsumer());
}
private void doCheckConsumer() {
if (curator.getState() != CuratorFrameworkState.STARTED) {
logger.error("curator还没有启动,忽略本次consumer的检查");
return;
}
logger.info("开始通过[providerCacheSet:{}]检查[consumer:{}]", providerCacheSet, NetContext.getSessionManager().getClientSessionMap().size());
var recheckFlag = false;
for (var providerCache : providerCacheSet) {
var consumerClientList = NetContext.getSessionManager().getClientSessionMap().values().stream()
.filter(it -> {
var attribute = it.getAttribute(AttributeType.CONSUMER);
return Objects.nonNull(attribute) && attribute.equals(providerCache);
})
.collect(Collectors.toList());
if (consumerClientList.size() == 1) {
var consumer = consumerClientList.get(0);
if (SessionUtils.isActive(consumer)) {
continue;
} else {
recheckFlag = true;
NetContext.getSessionManager().removeClientSession(consumer);
logger.error("[consumer:{}]失去连接,从clientSession中移除", consumer);
continue;
}
} else if (consumerClientList.size() > 1) {
logger.error("[consumerClientList:{}]中有多个重复的[RegisterVO:{}]", consumerClientList, providerCache);
continue;
}
var client = new TcpClient(HostAndPort.valueOf(providerCache.getProviderConfig().getAddress()));
var session = client.start();
if (Objects.isNull(session)) {
logger.error("[consumer:{}]启动失败,等待[{}]秒,重新检查consumer", providerCache, RETRY_SECONDS);
recheckFlag = true;
} else {
session.putAttribute(AttributeType.CONSUMER, providerCache);
EventBus.asyncSubmit(ConsumerStartEvent.valueOf(providerCache, session));
try {
var path = CONSUMER_ROOT_PATH + StringUtils.SLASH + localRegisterVO.toConsumerString();
var stat = curator.checkExists().forPath(path);
if (Objects.isNull(stat)) {
curator.create()
.withMode(CreateMode.EPHEMERAL)
.forPath(path);
} else {
curator.setData().forPath(path);
}
} catch (Exception e) {
// 因为并不关心consumer的状态,这种失败只需要记录一个错误日志就可以了
logger.error("consumer写入zookeeper失败", e);
}
}
}
if (recheckFlag) {
SchedulerContext.getSchedulerManager().schedule(new Runnable() {
@Override
public void run() {
checkConsumer();
}
}, RETRY_SECONDS, TimeUnit.SECONDS);
}
}
@Override
public void addData(String path, byte[] bytes, CreateMode mode) {
try {
var providerStat = curator.checkExists().forPath(path);
if (Objects.isNull(providerStat)) {
curator.create()
.creatingParentsIfNeeded()
.withMode(mode)
.forPath(path, bytes);
} else {
curator.setData().forPath(path, bytes);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void removeData(String path) {
try {
curator.delete().guaranteed().deletingChildrenIfNeeded().forPath(path);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public byte[] queryData(String path) {
try {
return curator.getData().storingStatIn(new Stat()).forPath(path);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean haveNode(String path) {
try {
return Objects.nonNull(curator.checkExists().forPath(path));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> children(String path) {
try {
var children = curator.getChildren().forPath(path).stream()
.filter(it -> !StringUtils.isBlank(it) && !"null".equals(it))
.collect(Collectors.toList());
return children;
} catch (Exception e) {
logger.error("未知异常", e);
} catch (Throwable t) {
logger.error("未知错误", t);
}
return Collections.emptyList();
}
@Override
public Set<RegisterVO> remoteProviderRegisterSet() {
try {
var remoteProviderSet = curator.getChildren().forPath(PROVIDER_ROOT_PATH).stream()
.filter(it -> !StringUtils.isBlank(it) && !"null".equals(it))
.map(it -> RegisterVO.parseString(it))
.filter(it -> Objects.nonNull(it))
.collect(Collectors.toSet());
return remoteProviderSet;
} catch (Exception e) {
logger.error("未知异常", e);
} catch (Throwable t) {
logger.error("未知错误", t);
}
return Collections.emptySet();
}
@Override
public void addListener(String listenerPath, BiConsumer<String, byte[]> updateCallback, Consumer<String> removeCallback) {
try {
var providerStat = curator.checkExists().forPath(listenerPath);
if (Objects.isNull(providerStat)) {
curator.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath(listenerPath, StringUtils.EMPTY_BYTES);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
var listener = CuratorCache.builder(curator, listenerPath).build();
listener.listenable().addListener(new CuratorCacheListener() {
@Override
public void event(Type type, ChildData oldData, ChildData newData) {
switch (type) {
case NODE_CHANGED:
case NODE_CREATED:
logger.info("listener child updated [oldData:{}] [newData:{}]", childDataToString(oldData), childDataToString(newData));
if (updateCallback != null) {
try {
updateCallback.accept(newData.getPath(), newData.getData());
} catch (Exception e) {
logger.error("listener child updated error", e);
}
}
break;
case NODE_DELETED:
if (removeCallback != null) {
removeCallback.accept(oldData.getPath());
}
break;
default:
}
}
}, executor);
listener.start();
listenerList.add(listener);
}
@Override
public void shutdown() {
try {
if (curator.getState() == CuratorFrameworkState.STARTED) {
// 删除服务提供者的临时节点
if (Objects.nonNull(localRegisterVO.getProviderConfig())) {
var localProviderPath = PROVIDER_ROOT_PATH + StringUtils.SLASH + localRegisterVO.toProviderString();
var localProviderStat = curator.checkExists().forPath(localProviderPath);
if (Objects.nonNull(localProviderStat)) {
curator.delete().guaranteed().deletingChildrenIfNeeded().forPath(localProviderPath);
}
}
// 删除服务消费者的临时节点
if (Objects.nonNull(localRegisterVO.getConsumerConfig())) {
var localConsumerPath = CONSUMER_ROOT_PATH + StringUtils.SLASH + localRegisterVO.toConsumerString();
var localConsumerStat = curator.checkExists().forPath(localConsumerPath);
if (Objects.nonNull(localConsumerStat)) {
curator.delete().guaranteed().deletingChildrenIfNeeded().forPath(localConsumerPath);
}
}
}
} catch (Throwable e) {
logger.error(ExceptionUtils.getMessage(e));
}
try {
listenerList.forEach(it -> IOUtils.closeIO(it));
IOUtils.closeIO(providerCuratorCache, curator);
ThreadUtils.shutdown(executor);
} catch (Throwable e) {
logger.error(ExceptionUtils.getMessage(e));
}
}
private String childDataToString(ChildData childData) {
if (childData == null) {
return StringUtils.EMPTY;
}
// 只打印data数据比较小的内容
if (childData.getData() == null || childData.getData().length <= 8) {
return childData.toString();
}
return StringUtils.format("[path:{}] [stat:{}] [dataSize:{}]", childData.getPath(), childData.getStat(), childData.getData().length);
}
}
@@ -0,0 +1,117 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.service;
import com.zfoo.net.NetContext;
import com.zfoo.net.dispatcher.manager.PacketDispatcher;
import com.zfoo.net.dispatcher.model.answer.AsyncAnswer;
import com.zfoo.net.dispatcher.model.answer.SyncAnswer;
import com.zfoo.net.dispatcher.model.exception.ErrorResponseException;
import com.zfoo.net.dispatcher.model.exception.NetTimeOutException;
import com.zfoo.net.dispatcher.model.exception.UnexpectedProtocolException;
import com.zfoo.net.packet.common.Error;
import com.zfoo.net.packet.model.NoAnswerAttachment;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.math.HashUtils;
import com.zfoo.util.math.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 服务调度和负载均衡,两个关键点:摘除故障节点,负载均衡
* <p>
* 在clientSession中选择一个可用的session,最终还是调用的IPacketDispatcherManager中的方法
*
* @author jaysunxiao
* @version 3.0
*/
public class Consumer implements IConsumer {
private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
@Override
public void send(IPacket packet, Object argument) {
try {
var loadBalancer = NetContext.getConfigManager().consumerLoadBalancer();
var session = loadBalancer.loadBalancer(packet, argument);
var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument);
NetContext.getDispatcher().send(session, packet, NoAnswerAttachment.valueOf(executorConsistentHash));
} catch (Throwable t) {
logger.error("consumer发送未知异常", t);
}
}
@Override
public <T extends IPacket> SyncAnswer<T> syncAsk(IPacket packet, Class<T> answerClass, Object argument) throws Exception {
var loadBalancer = NetContext.getConfigManager().consumerLoadBalancer();
var session = loadBalancer.loadBalancer(packet, argument);
// 下面的代码逻辑同PacketDispatcher的syncAsk,如果修改的话,记得一起修改
var clientAttachment = new SignalPacketAttachment();
var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument);
clientAttachment.setExecutorConsistentHash(executorConsistentHash);
try {
session.addClientSignalAttachment(clientAttachment);
// load balancer之前调用
loadBalancer.beforeLoadBalancer(session, packet, clientAttachment);
NetContext.getDispatcher().send(session, packet, clientAttachment);
IPacket responsePacket = clientAttachment.getResponseFuture().get(PacketDispatcher.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
if (responsePacket.protocolId() == Error.errorProtocolId()) {
throw new ErrorResponseException((Error) responsePacket);
}
if (answerClass != null && answerClass != responsePacket.getClass()) {
throw new UnexpectedProtocolException(StringUtils.format("client expect protocol:[{}], but found protocol:[{}]"
, answerClass, responsePacket.getClass().getName()));
}
var syncAnswer = new SyncAnswer<>((T) responsePacket, clientAttachment);
// load balancer之后调用
loadBalancer.afterLoadBalancer(session, packet, clientAttachment);
return syncAnswer;
} catch (TimeoutException e) {
throw new NetTimeOutException(StringUtils.format("syncRequest timeout exception, ask:[{}], attachment:[{}]"
, JsonUtils.object2String(packet), JsonUtils.object2String(clientAttachment)));
} finally {
session.removeClientSignalAttachment(clientAttachment);
}
}
@Override
public <T extends IPacket> AsyncAnswer<T> asyncAsk(IPacket packet, Class<T> answerClass, Object argument) {
var loadBalancer = NetContext.getConfigManager().consumerLoadBalancer();
var session = loadBalancer.loadBalancer(packet, argument);
var asyncAnswer = NetContext.getDispatcher().asyncAsk(session, packet, answerClass, argument);
// load balancer之前调用
loadBalancer.beforeLoadBalancer(session, packet, asyncAnswer.getFutureAttachment());
// load balancer之后调用
asyncAnswer.thenAccept(responsePacket -> loadBalancer.afterLoadBalancer(session, packet, asyncAnswer.getFutureAttachment()));
return asyncAnswer;
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.consumer.service;
import com.zfoo.net.dispatcher.model.answer.AsyncAnswer;
import com.zfoo.net.dispatcher.model.answer.SyncAnswer;
import com.zfoo.protocol.IPacket;
import org.springframework.lang.Nullable;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IConsumer {
/**
* 直接发送,不需要任何返回值
*
* @param packet 需要发送的包
* @param argument 计算负载均衡的参数,比如用户的id
*/
void send(IPacket packet, @Nullable Object argument);
<T extends IPacket> SyncAnswer<T> syncAsk(IPacket packet, Class<T> answerClass, @Nullable Object argument) throws Exception;
<T extends IPacket> AsyncAnswer<T> asyncAsk(IPacket packet, Class<T> answerClass, @Nullable Object argument);
}
@@ -0,0 +1,94 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core;
import com.zfoo.net.NetContext;
import com.zfoo.net.handler.BaseDispatcherHandler;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.util.net.HostAndPort;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollSocketChannel;
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;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class AbstractClient implements IClient {
private static final Logger logger = LoggerFactory.getLogger(AbstractClient.class);
private static final EventLoopGroup nioEventLoopGroup = Epoll.isAvailable()
? new EpollEventLoopGroup(Runtime.getRuntime().availableProcessors() + 1, new DefaultThreadFactory("netty-client", true))
: new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() + 1, new DefaultThreadFactory("netty-client", true));
private String hostAddress;
private int port;
private Bootstrap bootstrap;
public AbstractClient(HostAndPort host) {
this.hostAddress = host.getHost();
this.port = host.getPort();
}
public abstract ChannelInitializer<? extends Channel> channelChannelInitializer();
@Override
public synchronized Session start() {
return doStart(channelChannelInitializer());
}
private synchronized Session doStart(ChannelInitializer<? extends Channel> channelChannelInitializer) {
this.bootstrap = new Bootstrap();
this.bootstrap.group(nioEventLoopGroup)
.channel(Epoll.isAvailable() ? EpollSocketChannel.class : NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(channelChannelInitializer());
var channelFuture = bootstrap.connect(hostAddress, port);
channelFuture.syncUninterruptibly();
if (channelFuture.isSuccess()) {
if (channelFuture.channel().isActive()) {
var channel = channelFuture.channel();
var session = BaseDispatcherHandler.initChannel(channel);
NetContext.getSessionManager().addClientSession(session);
logger.info("TcpClient started at [{}]", channel.localAddress());
return session;
}
} else if (channelFuture.cause() != null) {
logger.error(ExceptionUtils.getMessage(channelFuture.cause()));
} else {
logger.error("启动客户端[client:{}]未知错误", this);
}
return null;
}
public synchronized static void shutdown() {
AbstractServer.shutdownEventLoopGracefully(nioEventLoopGroup);
}
}
@@ -0,0 +1,140 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core;
import com.zfoo.util.net.HostAndPort;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.EventExecutorGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class AbstractServer implements IServer {
private static final Logger logger = LoggerFactory.getLogger(AbstractServer.class);
// 所有的服务器都可以在这个列表中取到
private static final List<AbstractServer> allServers = new ArrayList<>(1);
private String hostAddress;
private int port;
// 配置服务端nio线程组,服务端接受客户端连接
private EventLoopGroup bossGroup;
// SocketChannel的网络读写
private EventLoopGroup workerGroup;
private ChannelFuture channelFuture;
private Channel channel;
public AbstractServer(HostAndPort host) {
this.hostAddress = host.getHost();
this.port = host.getPort();
}
public abstract ChannelInitializer<? extends Channel> channelChannelInitializer();
@Override
public void start() {
doStart(channelChannelInitializer());
}
protected synchronized void doStart(ChannelInitializer<? extends Channel> channelChannelInitializer) {
var cpuNum = Runtime.getRuntime().availableProcessors();
bossGroup = Epoll.isAvailable()
? new EpollEventLoopGroup(Math.max(1, cpuNum / 4), new DefaultThreadFactory("netty-boss", true))
: new NioEventLoopGroup(Math.max(1, cpuNum / 4), new DefaultThreadFactory("netty-boss", true));
workerGroup = Epoll.isAvailable()
? new EpollEventLoopGroup(cpuNum * 2, new DefaultThreadFactory("netty-worker", true))
: new NioEventLoopGroup(cpuNum * 2, new DefaultThreadFactory("netty-worker", true));
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.TCP_NODELAY, true)
.childHandler(channelChannelInitializer);
// 绑定端口,同步等待成功
// channelFuture = bootstrap.bind(hostAddress, port).sync();
// 等待服务端监听端口关闭
// channelFuture.channel().closeFuture().sync();
// 异步
channelFuture = bootstrap.bind(hostAddress, port);
channelFuture.syncUninterruptibly();
channel = channelFuture.channel();
allServers.add(this);
logger.info("TcpServer started at [{}:{}]", hostAddress, port);
}
@Override
public synchronized void shutdown() {
shutdownEventLoopGracefully(bossGroup);
shutdownEventLoopGracefully(workerGroup);
if (channelFuture != null) {
try {
channelFuture.channel().close().syncUninterruptibly();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
if (channel != null) {
try {
channel.close();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
public synchronized static void shutdownEventLoopGracefully(EventExecutorGroup executor) {
try {
if (executor.isShutdown() || executor.isTerminated()) {
executor.shutdownGracefully();
}
} catch (Exception e) {
logger.error("EventLoop Thread pool [{}] is failed to shutdown! ", executor, e);
return;
}
logger.info("EventLoop Thread pool [{}] shuts down gracefully.", executor);
}
public synchronized static void shutdownAllServers() {
allServers.forEach(it -> it.shutdown());
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core;
import com.zfoo.net.session.model.Session;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IClient {
Session start();
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IServer {
void start();
void shutdown();
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway;
import com.zfoo.net.core.AbstractServer;
import com.zfoo.net.handler.GatewayDispatcherHandler;
import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler;
import com.zfoo.net.handler.idle.ServerIdleHandler;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.util.net.HostAndPort;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import java.util.function.BiFunction;
/**
* @author jaysunxiao
* @version 3.0
*/
public class GatewayServer extends AbstractServer {
private static final Logger logger = LoggerFactory.getLogger(GatewayServer.class);
private BiFunction<Session, IPacket, Boolean> packetFilter;
public GatewayServer(HostAndPort host, @Nullable BiFunction<Session, IPacket, Boolean> packetFilter) {
super(host);
this.packetFilter = packetFilter;
}
@Override
public ChannelInitializer<SocketChannel> channelChannelInitializer() {
return new GatewayChannelHandler(packetFilter);
}
private static class GatewayChannelHandler extends ChannelInitializer<SocketChannel> {
private BiFunction<Session, IPacket, Boolean> packetFilter;
public GatewayChannelHandler(BiFunction<Session, IPacket, Boolean> packetFilter) {
this.packetFilter = packetFilter;
}
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new IdleStateHandler(0, 0, 180));
channel.pipeline().addLast(new ServerIdleHandler());
channel.pipeline().addLast(new TcpPacketCodecHandler());
channel.pipeline().addLast(new GatewayDispatcherHandler(packetFilter));
}
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway;
/**
* 网关负载均衡使用计算一致性hash的参数,如果packet继承了这个接口,则网关的一致性hash负载均衡优先使用这个接口计算一致性hash;
*
* @author jaysunxiao
* @version 3.0
*/
public interface IGatewayLoadBalancer {
Object loadBalancerConsistentHashObject();
}
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway;
import com.zfoo.net.core.AbstractServer;
import com.zfoo.net.handler.GatewayDispatcherHandler;
import com.zfoo.net.handler.codec.websocket.WebSocketCodecHandler;
import com.zfoo.net.handler.idle.ServerIdleHandler;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.util.net.HostAndPort;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
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.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import java.util.function.BiFunction;
/**
* @author jaysunxiao
* @version 3.0
*/
public class WebsocketGatewayServer extends AbstractServer {
private static final Logger logger = LoggerFactory.getLogger(WebsocketGatewayServer.class);
private BiFunction<Session, IPacket, Boolean> packetFilter;
public WebsocketGatewayServer(HostAndPort host, @Nullable BiFunction<Session, IPacket, Boolean> packetFilter) {
super(host);
this.packetFilter = packetFilter;
}
@Override
public ChannelInitializer<SocketChannel> channelChannelInitializer() {
return new GatewayChannelHandler(packetFilter);
}
private static class GatewayChannelHandler extends ChannelInitializer<SocketChannel> {
private BiFunction<Session, IPacket, Boolean> packetFilter;
public GatewayChannelHandler(BiFunction<Session, IPacket, Boolean> packetFilter) {
this.packetFilter = packetFilter;
}
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new IdleStateHandler(0, 0, 180));
channel.pipeline().addLast(new ServerIdleHandler());
channel.pipeline().addLast(new HttpServerCodec());
channel.pipeline().addLast(new ChunkedWriteHandler());
channel.pipeline().addLast(new HttpObjectAggregator(64 * 1024));
channel.pipeline().addLast(new WebSocketServerProtocolHandler("/websocket"));
channel.pipeline().addLast(new WebSocketCodecHandler());
channel.pipeline().addLast(new GatewayDispatcherHandler(packetFilter));
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway;
import com.zfoo.net.core.AbstractServer;
import com.zfoo.net.handler.GatewayDispatcherHandler;
import com.zfoo.net.handler.codec.websocket.WebSocketCodecHandler;
import com.zfoo.net.handler.idle.ServerIdleHandler;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.util.net.HostAndPort;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
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.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLException;
import java.io.InputStream;
import java.util.function.BiFunction;
/**
* @author jaysunxiao
* @version 3.0
*/
public class WebsocketSslGatewayServer extends AbstractServer {
private static final Logger logger = LoggerFactory.getLogger(WebsocketSslGatewayServer.class);
private SslContext sslContext;
private BiFunction<Session, IPacket, Boolean> packetFilter;
public WebsocketSslGatewayServer(HostAndPort host, InputStream pem, InputStream key, BiFunction<Session, IPacket, Boolean> packetFilter) {
super(host);
try {
this.sslContext = SslContextBuilder.forServer(pem, key).build();
} catch (SSLException e) {
logger.error(ExceptionUtils.getMessage(e));
}
this.packetFilter = packetFilter;
}
@Override
public ChannelInitializer<SocketChannel> channelChannelInitializer() {
return new GatewayChannelHandler(sslContext, packetFilter);
}
private static class GatewayChannelHandler extends ChannelInitializer<SocketChannel> {
private SslContext sslContext;
private BiFunction<Session, IPacket, Boolean> packetFilter;
public GatewayChannelHandler(SslContext sslContext, BiFunction<Session, IPacket, Boolean> packetFilter) {
this.sslContext = sslContext;
this.packetFilter = packetFilter;
}
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new IdleStateHandler(0, 0, 180));
channel.pipeline().addLast(new ServerIdleHandler());
channel.pipeline().addLast(sslContext.newHandler(channel.alloc()));
channel.pipeline().addLast(new HttpServerCodec());
channel.pipeline().addLast(new ChunkedWriteHandler());
channel.pipeline().addLast(new HttpObjectAggregator(64 * 1024));
channel.pipeline().addLast(new WebSocketServerProtocolHandler("/"));
channel.pipeline().addLast(new WebSocketCodecHandler());
channel.pipeline().addLast(new GatewayDispatcherHandler(packetFilter));
}
}
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway.model;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class AuthUidAsk implements IPacket {
public static final transient short PROTOCOL_ID = 22;
private String gatewayHostAndPort;
private long sid;
private long uid;
public static AuthUidAsk valueOf(String gatewayHostAndPort, long sid, long uid) {
var ask = new AuthUidAsk();
ask.gatewayHostAndPort = gatewayHostAndPort;
ask.sid = sid;
ask.uid = uid;
return ask;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public String getGatewayHostAndPort() {
return gatewayHostAndPort;
}
public void setGatewayHostAndPort(String gatewayHostAndPort) {
this.gatewayHostAndPort = gatewayHostAndPort;
}
public long getSid() {
return sid;
}
public void setSid(long sid) {
this.sid = sid;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
}
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway.model;
import com.zfoo.protocol.IPacket;
/**
* 网关登录成功过后,将uid授权给网关
*
* @author jaysunxiao
* @version 3.0
*/
public class AuthUidToGatewayCheck implements IPacket {
public static final transient short PROTOCOL_ID = 20;
private long uid;
public static AuthUidToGatewayCheck valueOf(long uid) {
var authUidToGateway = new AuthUidToGatewayCheck();
authUidToGateway.uid = uid;
return authUidToGateway;
}
public static long getAuthProtocolId() {
return PROTOCOL_ID;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway.model;
import com.zfoo.protocol.IPacket;
/**
* 网关登录成功过后,将uid授权给网关的返回
*
* @author jaysunxiao
* @version 3.0
*/
public class AuthUidToGatewayConfirm implements IPacket {
public static final transient short PROTOCOL_ID = 21;
private long uid;
public static AuthUidToGatewayConfirm valueOf(long uid) {
var authUidToGateway = new AuthUidToGatewayConfirm();
authUidToGateway.uid = uid;
return authUidToGateway;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway.model;
import com.zfoo.event.model.event.IEvent;
/**
* @author jaysunxiao
* @version 3.0
*/
public class AuthUidToGatewayEvent implements IEvent {
private long sid;
private long uid;
public static AuthUidToGatewayEvent valueOf(long sid, long uid) {
var event = new AuthUidToGatewayEvent();
event.sid = sid;
event.uid = uid;
return event;
}
public long getSid() {
return sid;
}
public void setSid(long sid) {
this.sid = sid;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway.model;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class GatewaySessionInactiveAsk implements IPacket {
public static final transient short PROTOCOL_ID = 23;
private String gatewayHostAndPort;
private long sid;
private long uid;
public static GatewaySessionInactiveAsk valueOf(String gatewayHostAndPort, long sid, long uid) {
var ask = new GatewaySessionInactiveAsk();
ask.gatewayHostAndPort = gatewayHostAndPort;
ask.sid = sid;
ask.uid = uid;
return ask;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public String getGatewayHostAndPort() {
return gatewayHostAndPort;
}
public void setGatewayHostAndPort(String gatewayHostAndPort) {
this.gatewayHostAndPort = gatewayHostAndPort;
}
public long getSid() {
return sid;
}
public void setSid(long sid) {
this.sid = sid;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway.model;
import com.zfoo.event.model.event.IEvent;
/**
* @author jaysunxiao
* @version 3.0
*/
public class GatewaySessionInactiveEvent implements IEvent {
private long sid;
private long uid;
public static GatewaySessionInactiveEvent valueOf(long sid, long uid) {
var event = new GatewaySessionInactiveEvent();
event.sid = sid;
event.uid = uid;
return event;
}
public long getSid() {
return sid;
}
public void setSid(long sid) {
this.sid = sid;
}
public long getUid() {
return uid;
}
public void setUid(long uid) {
this.uid = uid;
}
}
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.gateway.model;
import com.zfoo.protocol.IPacket;
import java.util.Map;
/**
* 同步网关的session信息到push
*
* @author jaysunxiao
* @version 3.0
*/
public class GatewaySynchronizeSidAsk implements IPacket {
public static final transient short PROTOCOL_ID = 24;
private String gatewayHostAndPort;
private Map<Long, Long> sidMap;
public static GatewaySynchronizeSidAsk valueOf(String gatewayHostAndPort, Map<Long, Long> sidMap) {
var ask = new GatewaySynchronizeSidAsk();
ask.gatewayHostAndPort = gatewayHostAndPort;
ask.sidMap = sidMap;
return ask;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public static short gatewaySynchronizeProtocolId() {
return PROTOCOL_ID;
}
public Map<Long, Long> getSidMap() {
return sidMap;
}
public void setSidMap(Map<Long, Long> sidMap) {
this.sidMap = sidMap;
}
public String getGatewayHostAndPort() {
return gatewayHostAndPort;
}
public void setGatewayHostAndPort(String gatewayHostAndPort) {
this.gatewayHostAndPort = gatewayHostAndPort;
}
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.tcp;
import com.zfoo.net.core.AbstractClient;
import com.zfoo.net.handler.ClientDispatcherHandler;
import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler;
import com.zfoo.net.handler.idle.ClientIdleHandler;
import com.zfoo.util.net.HostAndPort;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
public class TcpClient extends AbstractClient {
private static final Logger logger = LoggerFactory.getLogger(TcpClient.class);
public TcpClient(HostAndPort host) {
super(host);
}
@Override
public ChannelInitializer<? extends Channel> channelChannelInitializer() {
return new TcpChannelInitHandler();
}
private static class TcpChannelInitHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new IdleStateHandler(0, 0, 60));
channel.pipeline().addLast(new ClientIdleHandler());
channel.pipeline().addLast(new TcpPacketCodecHandler());
channel.pipeline().addLast(new ClientDispatcherHandler());
}
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.tcp;
import com.zfoo.net.core.AbstractServer;
import com.zfoo.net.handler.ServerDispatcherHandler;
import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler;
import com.zfoo.net.handler.idle.ServerIdleHandler;
import com.zfoo.util.net.HostAndPort;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
public class TcpServer extends AbstractServer {
private static final Logger logger = LoggerFactory.getLogger(TcpServer.class);
public TcpServer(HostAndPort host) {
super(host);
}
@Override
public ChannelInitializer<SocketChannel> channelChannelInitializer() {
return new TcpChannelHandler();
}
private static class TcpChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline().addLast(new IdleStateHandler(0, 0, 180));
channel.pipeline().addLast(new ServerIdleHandler());
channel.pipeline().addLast(new TcpPacketCodecHandler());
channel.pipeline().addLast(new ServerDispatcherHandler());
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.tcp.model;
import com.zfoo.event.model.event.IEvent;
import com.zfoo.net.session.model.Session;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ServerSessionInactiveEvent implements IEvent {
private Session session;
public static ServerSessionInactiveEvent valueOf(Session session) {
var event = new ServerSessionInactiveEvent();
event.session = session;
return event;
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
}
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.core.websocket;
import com.zfoo.net.core.AbstractServer;
import com.zfoo.net.handler.ServerDispatcherHandler;
import com.zfoo.net.handler.codec.websocket.WebSocketCodecHandler;
import com.zfoo.util.net.HostAndPort;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
public class WebsocketServer extends AbstractServer {
private static final Logger logger = LoggerFactory.getLogger(WebsocketServer.class);
public WebsocketServer(HostAndPort host) {
super(host);
}
@Override
public ChannelInitializer<SocketChannel> channelChannelInitializer() {
return new WebSocketServerInitializer();
}
public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel channel) {
ChannelPipeline pipeline = channel.pipeline();
// 编解码 http 请求
pipeline.addLast(new HttpServerCodec());
// 写文件内容,支持异步发送大的码流,一般用于发送文件流
pipeline.addLast(new ChunkedWriteHandler());
// 聚合解码 HttpRequest/HttpContent/LastHttpContent 到 FullHttpRequest
// 保证接收的 Http 请求的完整性
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
// 处理其他的 WebSocketFrame
pipeline.addLast(new WebSocketServerProtocolHandler("/websocket"));
// 编解码WebSocketFrame二进制协议
pipeline.addLast(new WebSocketCodecHandler());
pipeline.addLast(new ServerDispatcherHandler());
}
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.manager;
import com.zfoo.net.dispatcher.model.answer.AsyncAnswer;
import com.zfoo.net.dispatcher.model.answer.SyncAnswer;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import org.springframework.lang.Nullable;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IPacketDispatcher {
void send(Session session, IPacket packet);
/**
* send()和receive()是消息的发送和接收的入口,可以直接调用,是最轻量级发送和接收方式
*/
void send(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment);
void receive(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment);
void doReceive(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment);
/**
* attentionsyncRequest和asyncRequest只能客户端调用
* 同一个客户端可以同时发送多条同步或者异步消息。
* 服务器对每个请求消息也只能回复一条消息,不能在处理一条不同或者异步消息的时候回复多条消息。
*
* @param session 一个网络通信的会话
* @param packet 一个网络通信包,消息体
* @param answerClass 等待返回包的class类。
* 如果为null,则不会检查这个class类的协议号是否和返回消息体的协议号相等;
* 如果不为null,会检查返回包的协议号。为null的情况主要用在网关。
* @param <T> 请求消息需要服务器返回的类型
* @param argument 参数,主要用来计算一致性hashId。
* 1.IConsumer会使用这个参数计算负载到哪个服务提供者;
* 2.服务提供者收到请求过后会使用这个参数来计算再哪个线程执行任务;
* 3.如果是异步请求,消费者收到消息过后会通过这个参数计算再哪个线程执行回调。
* 综上所述,这个参数会在上面三种情况使用。
* @return 服务器返回的消息Response
* @throws Exception 如果超时或者其它异常
*/
<T extends IPacket> SyncAnswer<T> syncAsk(Session session, IPacket packet, @Nullable Class<T> answerClass, @Nullable Object argument) throws Exception;
<T extends IPacket> AsyncAnswer<T> asyncAsk(Session session, IPacket packet, @Nullable Class<T> answerClass, @Nullable Object argument);
void registerPacketReceiverDefinition(Object bean);
}
@@ -0,0 +1,414 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.manager;
import com.zfoo.event.manager.EventBus;
import com.zfoo.event.model.event.IEvent;
import com.zfoo.net.NetContext;
import com.zfoo.net.core.gateway.model.AuthUidToGatewayCheck;
import com.zfoo.net.core.gateway.model.AuthUidToGatewayConfirm;
import com.zfoo.net.core.gateway.model.AuthUidToGatewayEvent;
import com.zfoo.net.dispatcher.model.anno.PacketReceiver;
import com.zfoo.net.dispatcher.model.answer.AsyncAnswer;
import com.zfoo.net.dispatcher.model.answer.SyncAnswer;
import com.zfoo.net.dispatcher.model.exception.ErrorResponseException;
import com.zfoo.net.dispatcher.model.exception.NetTimeOutException;
import com.zfoo.net.dispatcher.model.exception.UnexpectedProtocolException;
import com.zfoo.net.dispatcher.model.vo.EnhanceUtils;
import com.zfoo.net.dispatcher.model.vo.IPacketReceiver;
import com.zfoo.net.dispatcher.model.vo.PacketReceiverDefinition;
import com.zfoo.net.packet.common.Error;
import com.zfoo.net.packet.common.Heartbeat;
import com.zfoo.net.packet.model.EncodedPacketInfo;
import com.zfoo.net.packet.model.GatewayPacketAttachment;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.net.packet.service.PacketService;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.net.task.TaskManager;
import com.zfoo.net.task.model.ReceiveTask;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.util.AssertionUtils;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.ReflectionUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.math.HashUtils;
import com.zfoo.util.math.RandomUtils;
import io.netty.util.concurrent.FastThreadLocal;
import javassist.CannotCompileException;
import javassist.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* 消息派发
*
* @author jaysunxiao
* @version 3.0
*/
public class PacketDispatcher implements IPacketDispatcher {
private static final Logger logger = LoggerFactory.getLogger(PacketDispatcher.class);
public static final long DEFAULT_TIMEOUT = 3000;
/**
* 客户端和服务端都有接受packet的方法,packetReceiverList对应的就是包的接收方法
*/
private final IPacketReceiver[] packetReceiverList = new IPacketReceiver[ProtocolManager.MAX_PROTOCOL_NUM];
/**
* 会把receive收到的attachment存储在这个地方,只针对task线程。
* doWithReceivePacket会设置receivePacketAttachment,但是在方法调用完成会取消,不需要过多关注。
* asyncRequest会再次设置receivePacketAttachment,需要重点关注。
*/
private final FastThreadLocal<SignalPacketAttachment> serverReceiveSignalPacketAttachment = new FastThreadLocal<>();
@Override
public void receive(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment) {
if (packet.protocolId() == Heartbeat.heartbeatProtocolId()) {
logger.info("heartbeat");
return;
}
// 发送者(客户端)同步和异步消息的接收,发送者通过packetId判断重复
if (packetAttachment != null) {
switch (packetAttachment.packetType()) {
case SIGNAL_PACKET:
var signalPacketAttachment = (SignalPacketAttachment) packetAttachment;
if (signalPacketAttachment.isClient()) {
// 服务器收到signalPacketAttachment,不做任何处理
signalPacketAttachment.setClient(false);
} else {
// 客户端收到服务器应答,客户端发送的时候isClient为true,服务器收到的时候将其设置为false
var attachment = (SignalPacketAttachment) session.removeClientSignalAttachment(signalPacketAttachment);
if (attachment != null) {
attachment.getResponseFuture().complete(packet);
} else {
logger.error("client receives packet:[{}] and packetAttachment:[{}] from server, but clientPacketAttachmentMap has no attachment, perhaps timeout exception."
, JsonUtils.object2String(packet), JsonUtils.object2String(packetAttachment));
}
return;
}
break;
case GATEWAY_PACKET:
var gatewayPacketAttachment = (GatewayPacketAttachment) packetAttachment;
if (gatewayPacketAttachment.isClient()) {
gatewayPacketAttachment.setClient(false);
} else {
var gatewaySession = NetContext.getSessionManager().getServerSession(gatewayPacketAttachment.getSid());
if (gatewaySession != null) {
var signalAttachment = gatewayPacketAttachment.getSignalPacketAttachment();
if (signalAttachment != null) {
signalAttachment.setClient(false);
}
// 网关授权,授权完成直接返回
if (AuthUidToGatewayCheck.getAuthProtocolId() == packet.protocolId()) {
var uid = ((AuthUidToGatewayCheck) packet).getUid();
if (uid <= 0) {
logger.error("错误的网关授权信息,uid必须大于0");
return;
}
gatewaySession.putAttribute(AttributeType.UID, uid);
EventBus.asyncSubmit(AuthUidToGatewayEvent.valueOf(gatewaySession.getSid(), uid));
NetContext.getDispatcher().send(session, AuthUidToGatewayConfirm.valueOf(uid), new GatewayPacketAttachment(gatewaySession, null));
return;
}
send(gatewaySession, packet, signalAttachment);
} else {
logger.error("gateway receives packet:[{}] and packetAttachment:[{}] from server" +
", but serverSessionMap has no session[id:{}], perhaps client disconnected from gateway."
, JsonUtils.object2String(packet), JsonUtils.object2String(packetAttachment), gatewayPacketAttachment.getSid());
}
return;
}
break;
case NORMAL_PACKET:
break;
default:
break;
}
}
// 正常发送消息的接收
TaskManager.getInstance().addTask(new ReceiveTask(session, packet, packetAttachment));
}
@Override
public void send(Session session, IPacket packet, IPacketAttachment packetAttachment) {
if (session == null) {
logger.error("session is null and can not be sent.");
return;
}
if (packet == null) {
logger.error("packet is null and can not be sent.");
return;
}
var packetInfo = EncodedPacketInfo.valueOf(packet, packetAttachment);
var channel = session.getChannel();
channel.writeAndFlush(packetInfo);
}
@Override
public void send(Session session, IPacket packet) {
// 服务器异步返回的消息的发送会有signalPacketAttachment,验证返回的消息是否满足
var serverSignalPacketAttachment = serverReceiveSignalPacketAttachment.get();
if (serverSignalPacketAttachment != null) {
if (serverSignalPacketAttachment.isClient()) {
// 客户端发送的时候不应该有serverSignalPacketAttachment
logger.error("client can not have serverSignalPacketAttachment:[{}] and packet:[{}]", serverSignalPacketAttachment, packet);
} else if (Error.errorProtocolId() == packet.protocolId()) {
// 错误信息直接返回
}
}
send(session, packet, serverSignalPacketAttachment);
}
@Override
public <T extends IPacket> SyncAnswer<T> syncAsk(Session session, IPacket packet, @Nullable Class<T> answerClass, @Nullable Object argument) throws Exception {
var clientAttachment = new SignalPacketAttachment();
var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument);
clientAttachment.setExecutorConsistentHash(executorConsistentHash);
try {
session.addClientSignalAttachment(clientAttachment);
send(session, packet, clientAttachment);
IPacket responsePacket = clientAttachment.getResponseFuture().get(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
if (responsePacket.protocolId() == Error.errorProtocolId()) {
throw new ErrorResponseException((Error) responsePacket);
}
if (answerClass != null && answerClass != responsePacket.getClass()) {
throw new UnexpectedProtocolException(StringUtils.format("client expect protocol:[{}], but found protocol:[{}]"
, answerClass, responsePacket.getClass().getName()));
}
return new SyncAnswer<>((T) responsePacket, clientAttachment);
} catch (TimeoutException e) {
throw new NetTimeOutException(StringUtils.format("syncRequest timeout exception, ask:[{}], attachment:[{}]"
, JsonUtils.object2String(packet), JsonUtils.object2String(clientAttachment)));
} finally {
session.removeClientSignalAttachment(clientAttachment);
}
}
@Override
public <T extends IPacket> AsyncAnswer<T> asyncAsk(Session session, IPacket packet, @Nullable Class<T> answerClass, @Nullable Object argument) {
var clientAttachment = new SignalPacketAttachment();
var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument);
clientAttachment.setExecutorConsistentHash(executorConsistentHash);
// 服务器在同步或异步的消息处理中,又调用了同步或异步的方法,这时候threadReceiverAttachment不为空
var serverSignalPacketAttachment = serverReceiveSignalPacketAttachment.get();
try {
var asyncAnswer = new AsyncAnswer<T>();
asyncAnswer.setFutureAttachment(clientAttachment);
clientAttachment.getResponseFuture()
.completeOnTimeout(null, DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
.thenApply(response -> {
if (response == null) {
throw new NetTimeOutException(StringUtils.format("asyncRequest timeout exception, ask:[{}], attachment:[{}]"
, JsonUtils.object2String(packet), JsonUtils.object2String(clientAttachment)));
}
if (response.protocolId() == Error.errorProtocolId()) {
throw new ErrorResponseException((Error) response);
}
if (answerClass != null && answerClass != response.getClass()) {
throw new UnexpectedProtocolException(StringUtils.format("client expect protocol:[{}], but found protocol:[{}]"
, answerClass, response.getClass().getName()));
}
return response;
})
.whenCompleteAsync((responsePacket, e) -> {
try {
session.removeClientSignalAttachment(clientAttachment);
// 如果有异常的话,whenCompleteAsync的下一个thenAccept不会执行
if (e != null) {
logger.error(ExceptionUtils.getMessage(e));
return;
}
// 接收者在同步或异步的消息处理中,又调用了异步的方法,这时候threadServerAttachment不为空
if (serverSignalPacketAttachment != null) {
serverReceiveSignalPacketAttachment.set(serverSignalPacketAttachment);
}
// 异步返回,回调业务逻辑
asyncAnswer.setFuturePacket((T) responsePacket);
asyncAnswer.consume();
} catch (Exception exception) {
logger.error("consume response error requestPacket:[{}] and responsePacket:[{}]",
JsonUtils.object2String(packet), JsonUtils.object2String(responsePacket), exception);
} finally {
if (serverSignalPacketAttachment != null) {
serverReceiveSignalPacketAttachment.set(null);
}
}
}, TaskManager.getInstance().getExecutorByConsistentHash(executorConsistentHash));
session.addClientSignalAttachment(clientAttachment);
// 等到上层调用whenComplete才会发送消息
asyncAnswer.setAskCallback(() -> send(session, packet, clientAttachment));
return asyncAnswer;
} catch (Exception e) {
session.removeClientSignalAttachment(clientAttachment);
throw e;
}
}
/**
* 正常消息的接收
* <p>
* 发送者同时能发送多个包
* 接收者同时只能处理一个session的一个包,同一个发送者发送过来的包排队处理
*/
@Override
public void doReceive(Session session, IPacket packet, IPacketAttachment packetAttachment) {
try {
var packetReceiver = packetReceiverList[packet.protocolId()];
if (packetReceiver == null) {
throw new RuntimeException(StringUtils.format("no any packetReceiverDefinition found for this [packet:{}]", packet.getClass().getName()));
}
// 接收者(服务器)同步和异步消息的接收
if (packetAttachment != null) {
switch (packetAttachment.packetType()) {
case SIGNAL_PACKET:
serverReceiveSignalPacketAttachment.set((SignalPacketAttachment) packetAttachment);
break;
default:
break;
}
}
// 调用PacketReceiver
packetReceiver.invoke(session, packet, packetAttachment);
} catch (Exception e) {
logger.error(StringUtils.format("e[{}][{}]未知exception异常[e:{}]", session.getAttribute(AttributeType.UID), session.getSid(), e.getMessage()), e);
} catch (Throwable t) {
logger.error(StringUtils.format("e[{}][{}]未知error错误[t:{}]", session.getAttribute(AttributeType.UID), session.getSid(), t.getMessage()), t);
} finally {
// 如果有服务器在处理同步或者异步消息的时候由于错误没有返回给客户端消息,则可能会残留serverAttachment,所以先移除
if (packetAttachment != null) {
switch (packetAttachment.packetType()) {
case SIGNAL_PACKET:
serverReceiveSignalPacketAttachment.set(null);
break;
default:
break;
}
}
}
}
@Override
public void registerPacketReceiverDefinition(Object bean) {
var clazz = bean.getClass();
if (!ReflectionUtils.isPOJOClass(clazz)) {
return;
}
var methods = ReflectionUtils.getMethodsByAnnoInPOJOClass(clazz, PacketReceiver.class);
for (var method : methods) {
var paramClazzs = method.getParameterTypes();
AssertionUtils.isTrue(paramClazzs.length == 2 || paramClazzs.length == 3
, "[class:{}] [method:{}] must have two or three parameter!", bean.getClass().getName(), method.getName());
AssertionUtils.isTrue(Session.class.isAssignableFrom(paramClazzs[0])
, "[class:{}] [method:{}],the first parameter must be Session type parameter Exception.", bean.getClass().getName(), method.getName());
AssertionUtils.isTrue(IPacket.class.isAssignableFrom(paramClazzs[1])
, "[class:{}] [method:{}],the second parameter must be IPacket type parameter Exception.", bean.getClass().getName(), method.getName());
AssertionUtils.isTrue(paramClazzs.length != 3 || IPacketAttachment.class.isAssignableFrom(paramClazzs[2])
, "[class:{}] [method:{}],the third parameter must be IPacketAttachment type parameter Exception.", bean.getClass().getName(), method.getName());
var packetClazz = (Class<? extends IEvent>) paramClazzs[1];
var attachmentClazz = paramClazzs.length == 3 ? paramClazzs[2] : null;
var packetName = packetClazz.getCanonicalName();
var methodName = method.getName();
AssertionUtils.isTrue(Modifier.isPublic(method.getModifiers())
, "[class:{}] [method:{}] [packet:{}] must use 'public' as modifier!", bean.getClass().getName(), methodName, packetName);
AssertionUtils.isTrue(!Modifier.isStatic(method.getModifiers())
, "[class:{}] [method:{}] [packet:{}] can not use 'static' as modifier!", bean.getClass().getName(), methodName, packetName);
var expectedMethodName = StringUtils.format("at{}", packetClazz.getSimpleName());
AssertionUtils.isTrue(methodName.equals(expectedMethodName)
, "[class:{}] [method:{}] [packet:{}] expects '{}' as method name!", bean.getClass().getName(), methodName, packetName, expectedMethodName);
// 如果以Request结尾的请求,那么attachment应该为GatewayAttachment
// 如果以Ask结尾的请求,那么attachment不能为GatewayAttachment
if (attachmentClazz != null) {
if (packetName.endsWith(PacketService.NET_REQUEST_SUFFIX)) {
AssertionUtils.isTrue(attachmentClazz.equals(GatewayPacketAttachment.class)
, "[class:{}] [method:{}] [packet:{}] must use [attachment:{}]!", bean.getClass().getName(), methodName, packetName, GatewayPacketAttachment.class.getCanonicalName());
} else if (packetName.endsWith(PacketService.NET_ASK_SUFFIX)) {
AssertionUtils.isTrue(!attachmentClazz.equals(GatewayPacketAttachment.class)
, "[class:{}] [method:{}] [packet:{}] can not match with [attachment:{}]!", bean.getClass().getName(), methodName, packetName, GatewayPacketAttachment.class.getCanonicalName());
}
}
try {
var protocolIdField = packetClazz.getDeclaredField(ProtocolManager.PROTOCOL_ID);
ReflectionUtils.makeAccessible(protocolIdField);
var protocolId = (short) protocolIdField.get(null);
var receiverDefinition = new PacketReceiverDefinition(bean, method, packetClazz, attachmentClazz);
var enhanceReceiverDefinition = EnhanceUtils.createPacketReceiver(receiverDefinition);
packetReceiverList[protocolId] = enhanceReceiverDefinition;
} catch (NoSuchFieldException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException | CannotCompileException | NotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.anno;
import java.lang.annotation.*;
/**
* @author jaysunxiao
* @version 3.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface PacketReceiver {
}
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.answer;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.protocol.IPacket;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* @author jaysunxiao
* @version 3.0
*/
public class AsyncAnswer<T extends IPacket> implements IAsyncAnswer<T> {
private T futurePacket;
private SignalPacketAttachment futureAttachment;
private List<Consumer<T>> consumerList = new ArrayList<>(2);
private Runnable askCallback;
@Override
public IAsyncAnswer<T> thenAccept(Consumer<T> consumer) {
consumerList.add(consumer);
return this;
}
@Override
public void whenComplete(Consumer<T> consumer) {
thenAccept(consumer);
askCallback.run();
}
public void consume() {
consumerList.forEach(it -> it.accept(futurePacket));
}
public T getFuturePacket() {
return futurePacket;
}
public void setFuturePacket(T futurePacket) {
this.futurePacket = futurePacket;
}
public SignalPacketAttachment getFutureAttachment() {
return futureAttachment;
}
public void setFutureAttachment(SignalPacketAttachment futureAttachment) {
this.futureAttachment = futureAttachment;
}
public Runnable getAskCallback() {
return askCallback;
}
public void setAskCallback(Runnable askCallback) {
this.askCallback = askCallback;
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.answer;
import com.zfoo.protocol.IPacket;
import java.util.function.Consumer;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IAsyncAnswer<T extends IPacket> {
IAsyncAnswer<T> thenAccept(Consumer<T> consumer);
/**
* 接收到异步返回的消息,并处理这个消息,异步请求必须要调用这个方法
*/
void whenComplete(Consumer<T> consumer);
}
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.answer;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface ISyncAnswer<T extends IPacket> {
/**
* @return 请求的返回包
*/
T packet();
/**
* @return 同步和异步控制的附加包
*/
SignalPacketAttachment attachment();
}
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.answer;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SyncAnswer<T extends IPacket> implements ISyncAnswer<T> {
private T packet;
private SignalPacketAttachment attachment;
public SyncAnswer(T packet, SignalPacketAttachment attachment) {
this.packet = packet;
this.attachment = attachment;
}
@Override
public T packet() {
return packet;
}
@Override
public SignalPacketAttachment attachment() {
return attachment;
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.exception;
import com.zfoo.net.packet.common.Error;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ErrorResponseException extends RuntimeException {
public ErrorResponseException(Error error) {
super(error.toString());
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.exception;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NetTimeOutException extends RuntimeException {
public NetTimeOutException(String s) {
super(s);
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.exception;
/**
* @author jaysunxiao
* @version 3.0
*/
public class UnexpectedProtocolException extends RuntimeException {
public UnexpectedProtocolException(String s) {
super(s);
}
}
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.vo;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.security.IdUtils;
import javassist.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class EnhanceUtils {
static {
var classArray = new Class<?>[]{
IPacket.class,
IPacketAttachment.class,
IPacketReceiver.class,
Session.class
};
var classPool = ClassPool.getDefault();
for (var clazz : classArray) {
if (classPool.find(clazz.getCanonicalName()) == null) {
ClassClassPath classPath = new ClassClassPath(clazz);
classPool.insertClassPath(classPath);
}
}
}
public static IPacketReceiver createPacketReceiver(PacketReceiverDefinition definition) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
var classPool = ClassPool.getDefault();
Object bean = definition.getBean();
Method method = definition.getMethod();
Class<?> packetClazz = definition.getPacketClazz();
Class<?> attachmentClazz = definition.getAttachmentClazz();
// 定义类名称
CtClass enhanceClazz = classPool.makeClass(EnhanceUtils.class.getCanonicalName() + "Dispatcher" + IdUtils.getLocalIntId());
enhanceClazz.addInterface(classPool.get(IPacketReceiver.class.getCanonicalName()));
// 定义类中的一个成员
CtField field = new CtField(classPool.get(bean.getClass().getCanonicalName()), "bean", enhanceClazz);
field.setModifiers(Modifier.PRIVATE);
enhanceClazz.addField(field);
// 定义类的构造器
CtConstructor constructor = new CtConstructor(classPool.get(new String[]{bean.getClass().getCanonicalName()}), enhanceClazz);
constructor.setBody("{this.bean=$1;}");
constructor.setModifiers(Modifier.PUBLIC);
enhanceClazz.addConstructor(constructor);
// 定义类实现的接口方法
CtMethod invokeMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "invoke", classPool.get(new String[]{Session.class.getCanonicalName(), IPacket.class.getCanonicalName(), IPacketAttachment.class.getCanonicalName()}), enhanceClazz);
invokeMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL);
if (attachmentClazz == null) {
// 强制类型转换
String invokeMethodBody = StringUtils.format("{this.bean.{}($1, ({})$2);}", method.getName(), packetClazz.getCanonicalName());
invokeMethod.setBody(invokeMethodBody);
} else {
String invokeMethodBody = StringUtils.format("{this.bean.{}($1, ({})$2, ({})$3);}", method.getName(), packetClazz.getCanonicalName(), attachmentClazz.getCanonicalName());
invokeMethod.setBody(invokeMethodBody);
}
enhanceClazz.addMethod(invokeMethod);
// 释放缓存
enhanceClazz.detach();
Class<?> resultClazz = enhanceClazz.toClass(IPacketReceiver.class);
Constructor<?> resultConstructor = resultClazz.getConstructor(bean.getClass());
IPacketReceiver receiver = (IPacketReceiver) resultConstructor.newInstance(bean);
return receiver;
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.vo;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IPacketReceiver {
void invoke(Session session, IPacket packet, IPacketAttachment attachment);
}
@@ -0,0 +1,98 @@
/*
* Copyright (C) 2020 The zfoo 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.zfoo.net.dispatcher.model.vo;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.util.ReflectionUtils;
import java.lang.reflect.Method;
/**
* @author jaysunxiao
* @version 3.0
*/
public class PacketReceiverDefinition implements IPacketReceiver {
/**
* 一个facade的bean,这个bean里有void methodName(Session session,CM_Int cm)接受的方法
*/
private Object bean;
/**
* 接受的方法void methodName(Session session,CM_Int cm)
*/
private Method method;
/**
* 接收的包的Class类,如CM_Int
*/
private Class<?> packetClazz;
/**
* 接收的包的附加包的Class类,如GatewayPacketAttachment
*/
private Class<?> attachmentClazz;
public PacketReceiverDefinition(Object bean, Method method, Class<?> packetClazz, Class<?> attachmentClazz) {
this.bean = bean;
this.method = method;
this.packetClazz = packetClazz;
this.attachmentClazz = attachmentClazz;
ReflectionUtils.makeAccessible(method);
}
@Override
public void invoke(Session session, IPacket packet, IPacketAttachment attachment) {
if (attachmentClazz == null) {
ReflectionUtils.invokeMethod(bean, method, session, packet);
} else {
ReflectionUtils.invokeMethod(bean, method, session, packet, attachment);
}
}
public Object getBean() {
return bean;
}
public void setBean(Object bean) {
this.bean = bean;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Class<?> getPacketClazz() {
return packetClazz;
}
public void setPacketClazz(Class<?> packetClazz) {
this.packetClazz = packetClazz;
}
public Class<?> getAttachmentClazz() {
return attachmentClazz;
}
public void setAttachmentClazz(Class<?> attachmentClazz) {
this.attachmentClazz = attachmentClazz;
}
}

Some files were not shown because too many files have changed in this diff Show More