14 changed files with 689 additions and 23 deletions
@ -0,0 +1,19 @@
|
||||
package net.sopod.soim.router.cache.annotation; |
||||
|
||||
import java.lang.annotation.*; |
||||
|
||||
/** |
||||
* Sync |
||||
* 修改数据的方法操作 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 17:18 |
||||
*/ |
||||
@Documented |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Target({ElementType.METHOD}) |
||||
public @interface Sync { |
||||
|
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,42 @@
|
||||
package net.sopod.soim.router.datasync.server; |
||||
|
||||
import net.sopod.soim.router.cache.DataSync; |
||||
|
||||
import java.util.Queue; |
||||
import java.util.concurrent.ConcurrentLinkedQueue; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
/** |
||||
* DataUpdateTrigger |
||||
* 序列化后,放入处理异步队列发送给监听者 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 16:14 |
||||
*/ |
||||
public class DataChangeTrigger { |
||||
|
||||
private static final DataChangeTrigger INSTANCE = new DataChangeTrigger(); |
||||
|
||||
public static DataChangeTrigger instance() { |
||||
return INSTANCE; |
||||
} |
||||
|
||||
private AtomicInteger seqCounter = new AtomicInteger(); |
||||
|
||||
private Queue<SyncLog> logQueue = new ConcurrentLinkedQueue<>(); |
||||
|
||||
public <T extends DataSync> void onUpdate(SyncTypes.SyncType<T> syncType, String dataKey, String method, Object[] args) { |
||||
// 序列化 args,避免后续更改
|
||||
// logQueue.add()
|
||||
} |
||||
|
||||
public <T extends DataSync> void onAdd(SyncTypes.SyncType<T> syncType, T data) { |
||||
// 序列化 data,避免后续更改
|
||||
|
||||
} |
||||
|
||||
public <T extends DataSync> void onRemove(SyncTypes.SyncType<T> syncType, String dataKey) { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,30 @@
|
||||
package net.sopod.soim.router.datasync.server; |
||||
|
||||
import io.netty.bootstrap.Bootstrap; |
||||
import io.netty.channel.ChannelInitializer; |
||||
import io.netty.channel.nio.NioEventLoopGroup; |
||||
import io.netty.channel.socket.SocketChannel; |
||||
|
||||
/** |
||||
* SyncClient |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 15:03 |
||||
*/ |
||||
public class SyncClient { |
||||
|
||||
public SyncClient() { |
||||
NioEventLoopGroup group = new NioEventLoopGroup(2); |
||||
new Bootstrap() |
||||
.group(group) |
||||
.handler(new ChannelInitializer<SocketChannel>() { |
||||
@Override |
||||
protected void initChannel(SocketChannel channel) throws Exception { |
||||
channel.pipeline() |
||||
.addLast(new SyncLogDataCodec()); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,200 @@
|
||||
package net.sopod.soim.router.datasync.server; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import lombok.Data; |
||||
import lombok.experimental.Accessors; |
||||
import net.sopod.soim.common.util.Jackson; |
||||
import org.apache.dubbo.common.io.Bytes; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import javax.annotation.Nullable; |
||||
import java.io.Serializable; |
||||
import java.lang.reflect.Method; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
/** |
||||
* SyncLog |
||||
* syncLog serialize |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 11:37 |
||||
*/ |
||||
@Data |
||||
@Accessors(chain = true) |
||||
public class SyncLog implements Serializable { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SyncLog.class); |
||||
|
||||
private static final long serialVersionUID = -8925525709590423526L; |
||||
|
||||
private static final short MAGIC = 0x7a21; |
||||
|
||||
public static final int OPT_ADD = 1; |
||||
public static final int OPT_REMOVE = 2; |
||||
public static final int OPT_UPDATE = 3; |
||||
|
||||
/** 日志序列号保证顺序 */ |
||||
private int logSeq; |
||||
|
||||
/** |
||||
* 操作类型: |
||||
* 1.新增 |
||||
* 2.删除 |
||||
* 3.更新 |
||||
*/ |
||||
private int operateType; |
||||
|
||||
/** |
||||
* {@link SyncTypes} ordinal |
||||
*/ |
||||
private int syncDataType; |
||||
|
||||
/** 数据id标示 */ |
||||
private String dataKey; |
||||
|
||||
/** ================ 新增参数:序列化后的数据 ===================== */ |
||||
private String addSerializeData; |
||||
|
||||
/** ================ 更新参数 ===================== */ |
||||
private String clazz; |
||||
|
||||
private String method; |
||||
|
||||
private Object[] args; |
||||
|
||||
public byte[] toBytes() { |
||||
byte[] clazzBytes = clazz.getBytes(); |
||||
byte[] methodBytes = method.getBytes(); |
||||
int argSize = args == null ? 0 : args.length; |
||||
|
||||
byte[][] byteArgs = new byte[argSize][]; |
||||
if (args != null) { |
||||
for (int i = 0; i < args.length; i++) { |
||||
// 反序列化时根据方法参数类型json反序列化
|
||||
// TODO null
|
||||
String argJson = Jackson.json().serialize(args[i]); |
||||
byteArgs[i] = argJson.getBytes(StandardCharsets.UTF_8); |
||||
} |
||||
} |
||||
int argsByteLen = 0; |
||||
for (byte[] byteArg : byteArgs) { |
||||
argsByteLen += 4; |
||||
argsByteLen += byteArg.length; |
||||
} |
||||
// 总长 + 同步数据类型(byte) +
|
||||
byte[] bytes = new byte[ |
||||
2 // 魔术
|
||||
+ 4 // 请求体总长度
|
||||
+ 1 // 同步数据类型
|
||||
+ 4 // clazz 字节长度
|
||||
+ clazzBytes.length // clazz字节
|
||||
+ 4 // method 字节长度
|
||||
+ methodBytes.length // method 字节
|
||||
+ 4 // args参数个数
|
||||
+ argsByteLen // args参数字节
|
||||
]; |
||||
int offset = 0; |
||||
Bytes.short2bytes(MAGIC, bytes, offset); |
||||
offset += 2; |
||||
|
||||
Bytes.int2bytes(bytes.length - 6, bytes, offset); |
||||
offset += 4; |
||||
|
||||
bytes[offset] = (byte)syncDataType; |
||||
offset += 1; |
||||
|
||||
Bytes.int2bytes(clazzBytes.length, bytes, offset); |
||||
offset += 4; |
||||
System.arraycopy(clazzBytes, 0, bytes, offset, clazzBytes.length); |
||||
offset += clazzBytes.length; |
||||
|
||||
Bytes.int2bytes(methodBytes.length, bytes, offset); |
||||
offset += 4; |
||||
System.arraycopy(methodBytes, 0, bytes, offset, methodBytes.length); |
||||
offset += methodBytes.length; |
||||
|
||||
Bytes.int2bytes(argSize, bytes, offset); |
||||
offset += 4; |
||||
if (argsByteLen > 0) { |
||||
for (byte[] byteArg : byteArgs) { |
||||
Bytes.int2bytes(byteArg.length, bytes, offset); |
||||
offset += 4; |
||||
System.arraycopy(byteArg, 0, bytes, offset, byteArg.length); |
||||
offset += byteArg.length; |
||||
} |
||||
} |
||||
return bytes; |
||||
} |
||||
|
||||
public static SyncLog read(ByteBuf buf) { |
||||
short magic = buf.readShort(); |
||||
if (magic != MAGIC) { |
||||
throw new IllegalStateException("unknown bytes magic error"); |
||||
} |
||||
SyncLog log = new SyncLog(); |
||||
// 后续bytes长度
|
||||
int dataLen = buf.readInt(); |
||||
log.syncDataType = buf.readByte(); |
||||
int clazzLen = buf.readInt(); |
||||
byte[] clazzBytes = new byte[clazzLen]; |
||||
buf.readBytes(clazzBytes); |
||||
log.clazz = new String(clazzBytes, StandardCharsets.UTF_8); |
||||
int methodLen = buf.readInt(); |
||||
byte[] methodBytes = clazzLen >= methodLen ? clazzBytes : new byte[methodLen]; |
||||
buf.readBytes(methodBytes, 0, methodLen); |
||||
log.method = new String(methodBytes, 0, methodLen, StandardCharsets.UTF_8); |
||||
int argSize = buf.readInt(); |
||||
log.args = new Object[argSize]; |
||||
if (argSize > 0) { |
||||
Method method = getClassMethod(log.clazz, log.method); |
||||
if (method == null) { |
||||
throw new IllegalStateException("类" + log.clazz + "方法" + log.method + "未找到"); |
||||
} |
||||
Class<?>[] paramTypes = method.getParameterTypes(); |
||||
if (paramTypes.length != argSize) { |
||||
throw new IllegalStateException("类" + log.clazz + "方法" + log.method + "指定参数" + argSize + "个,查到参数" + paramTypes.length + "个"); |
||||
} |
||||
// 复用 bytes
|
||||
byte[] argBytes = new byte[0]; |
||||
for (int i = 0; i < argSize; i++) { |
||||
int argLen = buf.readInt(); |
||||
argBytes = argBytes.length >= argLen ? argBytes : new byte[argLen]; |
||||
buf.readBytes(argBytes, 0, argLen); |
||||
String argJson = new String(argBytes, 0, argLen, StandardCharsets.UTF_8); |
||||
Object arg = Jackson.json().deserialize(argJson, paramTypes[i]); |
||||
log.args[i] = arg; |
||||
} |
||||
} |
||||
return log; |
||||
} |
||||
|
||||
/** |
||||
* TODO 缓存反射结果 |
||||
*/ |
||||
@Nullable |
||||
private static Method getClassMethod(String clazzName, String methodName) { |
||||
try { |
||||
Class<?> clazz = Class.forName(clazzName); |
||||
Method[] methods = clazz.getDeclaredMethods(); |
||||
for (Method method : methods) { |
||||
if (method.getName().equals(methodName)) { |
||||
return method; |
||||
} |
||||
} |
||||
} catch (ClassNotFoundException e) { |
||||
logger.error("获取类型方法失败: {}.{}", clazzName, methodName, e); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
new SyncLog() |
||||
.setSyncDataType(SyncTypes.ROUTER_USER.ordinal()) |
||||
.setClazz("") |
||||
.setMethod("") |
||||
.setArgs(new Object[]{}); |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,50 @@
|
||||
package net.sopod.soim.router.datasync.server; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.channel.*; |
||||
import io.netty.handler.codec.ByteToMessageDecoder; |
||||
import io.netty.handler.codec.MessageToByteEncoder; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* SyncDataInboundHandler |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 10:20 |
||||
*/ |
||||
public class SyncLogDataCodec |
||||
extends CombinedChannelDuplexHandler<SyncLogDataCodec.SyncDataDecoder, |
||||
SyncLogDataCodec.SyncDataEncoder> { |
||||
|
||||
public SyncLogDataCodec() { |
||||
super(new SyncDataDecoder(), new SyncDataEncoder()); |
||||
} |
||||
|
||||
/** |
||||
* 解码器 |
||||
*/ |
||||
public static class SyncDataDecoder extends ByteToMessageDecoder { |
||||
|
||||
@Override |
||||
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { |
||||
SyncLog syncLog = SyncLog.read(byteBuf); |
||||
list.add(syncLog); |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 编码器 |
||||
*/ |
||||
public static class SyncDataEncoder extends MessageToByteEncoder<SyncLog> { |
||||
|
||||
@Override |
||||
protected void encode(ChannelHandlerContext channelHandlerContext, SyncLog syncLog, ByteBuf byteBuf) throws Exception { |
||||
byte[] bytes = syncLog.toBytes(); |
||||
byteBuf.writeBytes(bytes); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,19 @@
|
||||
package net.sopod.soim.router.datasync.server; |
||||
|
||||
import io.netty.channel.ChannelHandlerContext; |
||||
import io.netty.channel.SimpleChannelInboundHandler; |
||||
|
||||
/** |
||||
* SyncLogInboundHandler |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 14:57 |
||||
*/ |
||||
public class SyncLogInboundHandler extends SimpleChannelInboundHandler<SyncLog> { |
||||
|
||||
@Override |
||||
protected void channelRead0(ChannelHandlerContext channelHandlerContext, SyncLog syncLog) throws Exception { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,50 @@
|
||||
package net.sopod.soim.router.datasync.server; |
||||
|
||||
import io.netty.bootstrap.ServerBootstrap; |
||||
import io.netty.channel.Channel; |
||||
import io.netty.channel.ChannelInitializer; |
||||
import io.netty.channel.ChannelPipeline; |
||||
import io.netty.channel.nio.NioEventLoopGroup; |
||||
import io.netty.channel.socket.nio.NioServerSocketChannel; |
||||
import io.netty.handler.logging.LogLevel; |
||||
import io.netty.handler.logging.LoggingHandler; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** |
||||
* SyncServer |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 10:02 |
||||
*/ |
||||
public class SyncServer { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SyncServer.class); |
||||
|
||||
public SyncServer() { |
||||
NioEventLoopGroup boss = new NioEventLoopGroup(1); |
||||
NioEventLoopGroup worker = new NioEventLoopGroup(2); |
||||
ServerBootstrap serverBoot = new ServerBootstrap() |
||||
.group(boss, worker) |
||||
.channel(NioServerSocketChannel.class) |
||||
.childHandler(new ChannelInitializer<>() { |
||||
@Override |
||||
protected void initChannel(Channel channel) throws Exception { |
||||
LogLevel logLevel = logger.isDebugEnabled() ? LogLevel.DEBUG |
||||
: logger.isInfoEnabled() ? LogLevel.INFO |
||||
: logger.isWarnEnabled() ? LogLevel.WARN |
||||
: logger.isErrorEnabled() ? LogLevel.ERROR : LogLevel.INFO; |
||||
ChannelPipeline pipeline = channel.pipeline(); |
||||
pipeline.addLast(new LoggingHandler(logLevel)) |
||||
.addLast(new SyncLogDataCodec()) |
||||
.addLast(new SyncLogInboundHandler()); |
||||
} |
||||
}); |
||||
serverBoot.bind(8080); |
||||
} |
||||
|
||||
public void start() { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,126 @@
|
||||
package net.sopod.soim.router.datasync.server; |
||||
|
||||
import net.sopod.soim.common.util.StringUtil; |
||||
import net.sopod.soim.router.cache.DataSync; |
||||
import net.sopod.soim.router.cache.RouterUser; |
||||
import net.sopod.soim.router.cache.SoImUserCache; |
||||
|
||||
import javax.annotation.Nullable; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
/** |
||||
* SyncTypeEnum |
||||
* 需要同步的数据类型 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-05 10:40 |
||||
*/ |
||||
public class SyncTypes { |
||||
|
||||
/** |
||||
* 模拟枚举根据顺序值获取数据类型对象 |
||||
*/ |
||||
public static <T extends DataSync> SyncType<T> getSyncType(int ordinal) { |
||||
return SyncType.getSyncType(ordinal); |
||||
} |
||||
|
||||
/** |
||||
* 用户数据同步处理: |
||||
* 改: 代理对象方法监控(getData(dataKey), updateMethod(args)) |
||||
* 增: 增数据(data) |
||||
* 删: 删数据(dataKey) |
||||
*/ |
||||
public static final SyncType<RouterUser> ROUTER_USER = new SyncType<>(RouterUser.class, "ROUTER_USER") { |
||||
@Override |
||||
public String getDataKey(RouterUser data) { |
||||
return StringUtil.toString(data.getUid()); |
||||
} |
||||
|
||||
@Override |
||||
public RouterUser getData(String uid) { |
||||
return SoImUserCache.getInstance().get(Long.valueOf(uid)); |
||||
} |
||||
|
||||
@Override |
||||
public boolean addData(RouterUser data) { |
||||
SoImUserCache.getInstance().put(data.getUid(), data); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public boolean removeData(String uid) { |
||||
return null != SoImUserCache.getInstance().remove(Long.valueOf(uid)); |
||||
} |
||||
}; |
||||
|
||||
public static abstract class SyncType<T extends DataSync> { |
||||
private static final List<SyncType<?>> TYPES = new ArrayList<>(); |
||||
private static int ordinalSeq = Byte.MIN_VALUE; |
||||
|
||||
private final int ordinal; |
||||
private final Class<T> dataType; |
||||
private final String name; |
||||
|
||||
SyncType(Class<T> dataType, String name) { |
||||
this.ordinal = ordinalSeq++; |
||||
if (ordinal > Byte.MAX_VALUE) { |
||||
throw new IllegalStateException("类型超过255个,需修改协议"); |
||||
} |
||||
this.dataType = dataType; |
||||
this.name = name; |
||||
TYPES.add(this); |
||||
} |
||||
|
||||
public Class<T> dataType() { |
||||
return dataType; |
||||
} |
||||
|
||||
public String name() { |
||||
return name; |
||||
} |
||||
|
||||
public int ordinal() { |
||||
return ordinal; |
||||
} |
||||
|
||||
public abstract String getDataKey(T data); |
||||
|
||||
public abstract T getData(String key); |
||||
|
||||
public abstract boolean addData(T data); |
||||
|
||||
public abstract boolean removeData(String key); |
||||
|
||||
@Nullable |
||||
@SuppressWarnings("unchecked") |
||||
static <T extends DataSync> SyncType<T> getSyncType(int ordinal) { |
||||
return (SyncType<T>) (ordinal < TYPES.size() ? TYPES.get(ordinal) : null); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "SyncType{" + |
||||
"ordinal=" + ordinal + |
||||
", dataType=" + dataType + |
||||
", name='" + name + '\'' + |
||||
'}' + "@" + Integer.toHexString(this.hashCode()); |
||||
} |
||||
|
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
System.out.println(ROUTER_USER.ordinal()); |
||||
System.out.println(SyncTypes.getSyncType(0)); |
||||
System.out.println(SyncTypes.getSyncType(1)); |
||||
System.out.println(SyncTypes.getSyncType(2)); |
||||
Class<RouterUser> routerUserClass = SyncTypes.ROUTER_USER.dataType(); |
||||
|
||||
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(); |
||||
map.put("1", "2"); |
||||
System.out.println(map.remove("2")); |
||||
System.out.println(map.remove("1")); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue