init project

This commit is contained in:
jaysunxiao committed 2021-05-20 14:17:21 +08:00
1 parent b7f28da485
commit bce060a28e
573 files changed
+84513

No files matched your search

@@ -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);
// 初始化consumer,providerCacheSet改变会导致消费者改变
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);
/**
* attention:syncRequest和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;
}
}
@@ -0,0 +1,73 @@
/*
* 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.handler;
import com.zfoo.net.NetContext;
import com.zfoo.net.packet.model.DecodedPacketInfo;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.net.util.SessionUtils;
import com.zfoo.protocol.util.StringUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
@ChannelHandler.Sharable
public class BaseDispatcherHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(BaseDispatcherHandler.class);
public static final AttributeKey<Session> SESSION_KEY = AttributeKey.valueOf("session");
public static Session initChannel(Channel channel) {
var sessionAttr = channel.attr(SESSION_KEY);
var session = new Session(channel);
var setSuccessful = sessionAttr.compareAndSet(null, session);
if (!setSuccessful) {
channel.close();
throw new RuntimeException(StringUtils.format("无法设置[channel:{}]的session", channel));
}
session.putAttribute(AttributeType.CHANNEL_REMOTE_ADDRESS, StringUtils.substringAfterFirst(channel.remoteAddress().toString(), StringUtils.SLASH));
return session;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
var session = SessionUtils.getSession(ctx);
if (session == null) {
return;
}
DecodedPacketInfo decodedPacketInfo = (DecodedPacketInfo) msg;
NetContext.getDispatcher().receive(session, decodedPacketInfo.getPacket(), decodedPacketInfo.getPacketAttachment());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
try {
logger.error("[session{}]未知异常", SessionUtils.sessionInfo(ctx), cause);
} finally {
ctx.close();
}
}
}
@@ -0,0 +1,61 @@
/*
* 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.handler;
import com.zfoo.net.NetContext;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.util.SessionUtils;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
@ChannelHandler.Sharable
public class ClientDispatcherHandler extends BaseDispatcherHandler {
private static final Logger logger = LoggerFactory.getLogger(ClientDispatcherHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
logger.info("client channel [{}] is active", SessionUtils.sessionInfo(ctx));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
var session = SessionUtils.getSession(ctx);
if (session != null) {
var consumeAttribute = session.getAttribute(AttributeType.CONSUMER);
NetContext.getSessionManager().removeClientSession(session);
// 如果是消费者inactive,还需要触发客户端消费者检查事件,以便重新连接
if (consumeAttribute != null) {
NetContext.getConfigManager().getRegistry().checkConsumer();
}
}
logger.warn("[channel:{}] is inactive", SessionUtils.sessionInfo(ctx));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.error("[session{}]未知异常", SessionUtils.sessionInfo(ctx), cause);
}
}
@@ -0,0 +1,132 @@
/*
* 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.handler;
import com.zfoo.event.manager.EventBus;
import com.zfoo.net.NetContext;
import com.zfoo.net.consumer.balancer.ConsistentHashConsumerLoadBalancer;
import com.zfoo.net.core.gateway.IGatewayLoadBalancer;
import com.zfoo.net.core.gateway.model.GatewaySessionInactiveEvent;
import com.zfoo.net.packet.common.Heartbeat;
import com.zfoo.net.packet.common.Ping;
import com.zfoo.net.packet.common.Pong;
import com.zfoo.net.packet.model.DecodedPacketInfo;
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.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.net.util.SessionUtils;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.scheduler.util.TimeUtils;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.BiFunction;
/**
* @author jaysunxiao
* @version 3.0
*/
@ChannelHandler.Sharable
public class GatewayDispatcherHandler extends ServerDispatcherHandler {
private static final Logger logger = LoggerFactory.getLogger(GatewayDispatcherHandler.class);
private BiFunction<Session, IPacket, Boolean> packetFilter;
public GatewayDispatcherHandler(BiFunction<Session, IPacket, Boolean> packetFilter) {
this.packetFilter = packetFilter;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 请求者的session,一般是serverSession
var session = SessionUtils.getSession(ctx);
if (session == null) {
return;
}
var decodedPacketInfo = (DecodedPacketInfo) msg;
var packet = decodedPacketInfo.getPacket();
if (packet.protocolId() == Heartbeat.heartbeatProtocolId()) {
return;
}
if (packet.protocolId() == Ping.pingProtocolId()) {
NetContext.getDispatcher().send(session, Pong.valueOf(TimeUtils.now()), null);
return;
}
// 过滤非法包
if (packetFilter != null && packetFilter.apply(session, packet)) {
throw new IllegalArgumentException(StringUtils.format("[session:{}]发送了一个非法包[{}]"
, SessionUtils.sessionInfo(ctx), JsonUtils.object2String(packet)));
}
var signalAttachment = (SignalPacketAttachment) decodedPacketInfo.getPacketAttachment();
var gatewayPacketAttachment = new GatewayPacketAttachment(session, signalAttachment);
// 网关优先使用IGatewayLoadBalancer作为一致性hash的计算参数,然后才会使用客户端的session做参数
if (packet instanceof IGatewayLoadBalancer) {
var loadBalancerConsistentHashObject = ((IGatewayLoadBalancer) packet).loadBalancerConsistentHashObject();
gatewayPacketAttachment.useExecutorConsistentHash(loadBalancerConsistentHashObject);
forwardingPacket(packet, gatewayPacketAttachment, loadBalancerConsistentHashObject);
return;
} else {
// 使用用户的uid做一致性hash
var uid = (Long) session.getAttribute(AttributeType.UID);
if (uid != null) {
forwardingPacket(packet, gatewayPacketAttachment, uid);
return;
}
}
// 再使用session的sid做一致性hash,因为每次客户端连接过来sid都会改变,所以客户端重写建立连接的话可能会被路由到其它的服务器
// 如果有特殊需求的话,可以考虑去重写网关的转发策略
var sid = session.getSid();
forwardingPacket(packet, gatewayPacketAttachment, sid);
}
/**
* 转发网关收到的包
*/
private void forwardingPacket(IPacket packet, IPacketAttachment attachment, Object argument) {
try {
var consumerSession = ConsistentHashConsumerLoadBalancer.getInstance().loadBalancer(packet, argument);
NetContext.getDispatcher().send(consumerSession, packet, attachment);
} catch (Exception e) {
logger.error("网关发生异常", e);
} catch (Throwable t) {
logger.error("网关发生错误", t);
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
var session = SessionUtils.getSession(ctx);
if (session == null) {
return;
}
var sid = session.getSid();
var uid = (Long) session.getAttribute(AttributeType.UID);
EventBus.asyncSubmit(GatewaySessionInactiveEvent.valueOf(sid, uid == null ? 0 : uid.longValue()));
super.channelInactive(ctx);
}
}
@@ -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.handler;
import com.zfoo.event.manager.EventBus;
import com.zfoo.net.NetContext;
import com.zfoo.net.core.tcp.model.ServerSessionInactiveEvent;
import com.zfoo.net.util.SessionUtils;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
@ChannelHandler.Sharable
public class ServerDispatcherHandler extends BaseDispatcherHandler {
private static final Logger logger = LoggerFactory.getLogger(ServerDispatcherHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
var session = initChannel(ctx.channel());
NetContext.getSessionManager().addServerSession(session);
logger.info("server channel [{}] is active", SessionUtils.sessionInfo(ctx));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
var session = SessionUtils.getSession(ctx);
if (session == null) {
return;
}
NetContext.getSessionManager().removeServerSession(session);
EventBus.asyncSubmit(ServerSessionInactiveEvent.valueOf(session));
logger.warn("[channel:{}] is inactive", SessionUtils.sessionInfo(ctx));
}
}
@@ -0,0 +1,99 @@
/*
* 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.handler.codec.tcp;
import com.zfoo.net.NetContext;
import com.zfoo.net.packet.model.DecodedPacketInfo;
import com.zfoo.net.packet.model.EncodedPacketInfo;
import com.zfoo.net.util.SessionUtils;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* header(4byte) + protocolId(2byte) + packet
* header = body(bytes.length) + protocolId.length(2byte)
*
* @author jaysunxiao
* @version 3.0
*/
public class TcpPacketCodecHandler extends ByteToMessageCodec<EncodedPacketInfo> {
private static final Logger logger = LoggerFactory.getLogger(TcpPacketCodecHandler.class);
// 数据包的最大长度限制,防止恶意的攻击
private static final int MAX_LENGTH = 1 * IOUtils.BITS_PER_MB;
private int length;
private boolean remain = false;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
if (!remain) {
// 不够读一个int
if (in.readableBytes() <= ProtocolManager.PROTOCOL_HEAD_LENGTH) {
return;
}
length = in.readInt();
remain = true;
}
// 如果长度超过限制,则抛出异常断开连接
if (length > MAX_LENGTH) {
throw new IllegalArgumentException(StringUtils
.format("[session:{}]的包头长度[length:{}]超过最大长度[maxLength:{}]限制"
, SessionUtils.sessionInfo(ctx), length, MAX_LENGTH));
}
// ByteBuf里的数据太小
if (in.readableBytes() < length) {
return;
}
remain = false;
DecodedPacketInfo packetInfo = NetContext.getPacketService().read(in);
out.add(packetInfo);
} catch (Exception e) {
logger.error("[session:{}]解码exception异常", SessionUtils.sessionInfo(ctx), e);
throw e;
} catch (Throwable t) {
logger.error("[session:{}]解码throwable错误", SessionUtils.sessionInfo(ctx), t);
throw t;
}
}
@Override
protected void encode(ChannelHandlerContext ctx, EncodedPacketInfo packetInfo, ByteBuf out) {
try {
NetContext.getPacketService().write(out, packetInfo.getPacket(), packetInfo.getPacketAttachment());
} catch (Exception e) {
logger.error("[session:{}][{}]编码exception异常", SessionUtils.sessionInfo(ctx), packetInfo.getPacket().getClass().getSimpleName(), e);
throw e;
} catch (Throwable t) {
logger.error("[session:{}][{}]编码throwable错误", SessionUtils.sessionInfo(ctx), packetInfo.getPacket().getClass().getSimpleName(), t);
throw t;
}
}
}
@@ -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.net.handler.codec.websocket;
import com.zfoo.net.NetContext;
import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler;
import com.zfoo.net.packet.model.DecodedPacketInfo;
import com.zfoo.net.packet.model.EncodedPacketInfo;
import com.zfoo.net.util.SessionUtils;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.util.IOUtils;
import com.zfoo.protocol.util.JsonUtils;
import com.zfoo.protocol.util.StringUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* header(4byte) + protocolId(2byte) + packet
* header = body(bytes.length) + protocolId.length(2byte)
*
* @author jaysunxiao
* @version 3.0
*/
public class WebSocketCodecHandler extends MessageToMessageCodec<WebSocketFrame, EncodedPacketInfo> {
private static final Logger logger = LoggerFactory.getLogger(TcpPacketCodecHandler.class);
// 数据包的最大长度限制,防止恶意的攻击
private static final int MAX_LENGTH = 100 * IOUtils.BYTES_PER_KB;
private int length;
private boolean remain = false;
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, WebSocketFrame webSocketFrame, List<Object> list) {
try {
ByteBuf in = webSocketFrame.content();
if (!remain) {
// 不够读一个int
if (in.readableBytes() <= ProtocolManager.PROTOCOL_HEAD_LENGTH) {
return;
}
length = in.readInt();
remain = true;
}
// 如果长度超过限制,则抛出异常断开连接
if (length > MAX_LENGTH) {
throw new IllegalArgumentException(StringUtils
.format("[session:{}]的包头长度[length:{}]超过最大长度[maxLength:{}]限制"
, SessionUtils.sessionInfo(channelHandlerContext), length, MAX_LENGTH));
}
// ByteBuf里的数据太小
if (in.readableBytes() < length) {
return;
}
remain = false;
DecodedPacketInfo packetInfo = NetContext.getPacketService().read(in);
list.add(packetInfo);
} catch (Exception e) {
logger.error("exception异常", e);
throw e;
} catch (Throwable t) {
logger.error("throwable错误", t);
throw t;
}
}
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, EncodedPacketInfo out, List<Object> list) {
try {
ByteBuf byteBuf = Unpooled.directBuffer();
byteBuf.clear();
NetContext.getPacketService().write(byteBuf, out.getPacket(), out.getPacketAttachment());
list.add(new BinaryWebSocketFrame(byteBuf));
} catch (Exception e) {
logger.error("[{}]编码exception异常", JsonUtils.object2String(out), e);
throw e;
} catch (Throwable t) {
logger.error("[{}]编码throwable错误", JsonUtils.object2String(out), t);
throw t;
}
}
}
@@ -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.handler.idle;
import com.zfoo.net.packet.common.Heartbeat;
import com.zfoo.net.packet.model.EncodedPacketInfo;
import com.zfoo.net.util.SessionUtils;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
@ChannelHandler.Sharable
public class ClientIdleHandler extends ChannelDuplexHandler {
private static final Logger logger = LoggerFactory.getLogger(ClientIdleHandler.class);
private static final EncodedPacketInfo heartbeatPacket = EncodedPacketInfo.valueOf(Heartbeat.getInstance(), null);
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.ALL_IDLE) {
logger.warn("client sends heartbeat packet to {}", SessionUtils.sessionInfo(ctx));
ctx.channel().writeAndFlush(heartbeatPacket);
}
}
}
}
@@ -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.net.handler.idle;
import com.zfoo.net.util.SessionUtils;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
@ChannelHandler.Sharable
public class ServerIdleHandler extends ChannelDuplexHandler {
private static final Logger logger = LoggerFactory.getLogger(ServerIdleHandler.class);
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.ALL_IDLE) {
logger.warn("[channel:{}] is time out for close", SessionUtils.sessionInfo(ctx));
}
ctx.close();
}
}
}
@@ -0,0 +1,102 @@
/*
* 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.packet.common;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Error implements IPacket {
public static final transient short PROTOCOL_ID = 101;
private int module;
private int errorCode;
private String errorMessage;
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public static short errorProtocolId() {
return PROTOCOL_ID;
}
@Override
public String toString() {
FormattingTuple message = MessageFormatter.arrayFormat(
"module:[{}], errorCode:[{}], errorMessage:[{}]", new Object[]{module, errorCode, errorMessage});
return message.getMessage();
}
public static Error valueOf(int module, int errorCode, String errorMessage) {
Error response = new Error();
response.module = module;
response.errorCode = errorCode;
response.errorMessage = errorMessage;
return response;
}
public static Error valueOf(IPacket packet, int errorCode, String errorMessage) {
Error response = new Error();
response.module = ProtocolManager.getProtocol(packet.protocolId()).module();
response.errorCode = errorCode;
response.errorMessage = errorMessage;
return response;
}
public static Error valueOf(IPacket packet, int errorCode) {
return valueOf(packet, errorCode, null);
}
public static Error valueOf(IPacket packet, String errorMessage) {
return valueOf(packet, 0, errorMessage);
}
public static Error valueOf(String errorMessage) {
return valueOf(0, 0, errorMessage);
}
public int getModule() {
return module;
}
public void setModule(int module) {
this.module = module;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
@@ -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.packet.common;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Heartbeat implements IPacket {
public static final transient short PROTOCOL_ID = 102;
private static Heartbeat INSTANCE = new Heartbeat();
public static Heartbeat getInstance() {
return INSTANCE;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public static short heartbeatProtocolId() {
return PROTOCOL_ID;
}
}
@@ -0,0 +1,83 @@
/*
* 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.packet.common;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
/**
* 通用的返回,既可以用在远程调用,又可以嵌套在其它协议里
*
* @author jaysunxiao
* @version 3.0
*/
public class Message implements IPacket {
public static final transient short PROTOCOL_ID = 100;
private byte module;
/**
* 1是成功,其它的均视为失败的请求
*/
private int code;
private String message;
public static Message valueOf(IPacket packet, int code, String message) {
var mess = new Message();
mess.module = ProtocolManager.moduleByProtocolId(packet.protocolId()).getId();
mess.code = code;
mess.message = message;
return mess;
}
public static Message valueOf(IPacket packet, int code) {
return Message.valueOf(packet, code, null);
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public boolean success() {
return code == 1;
}
public byte getModule() {
return module;
}
public void setModule(byte module) {
this.module = module;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -0,0 +1,62 @@
/*
* 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.packet.common;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.zfoo.protocol.IPacket;
/**
* Long + String
*
* @author jaysunxiao
* @version 3.0
*/
public class PairLS implements IPacket {
public static final transient short PROTOCOL_ID = 113;
@JsonSerialize(using = ToStringSerializer.class)
private long key;
private String value;
public static PairLS valueOf(long key, String value) {
var pair = new PairLS();
pair.key = key;
pair.value = value;
return pair;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public long getKey() {
return key;
}
public void setKey(long key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -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.packet.common;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.zfoo.protocol.IPacket;
import java.util.Comparator;
/**
* @author jaysunxiao
* @version 3.0
*/
public class PairLong implements IPacket {
public static final transient short PROTOCOL_ID = 111;
public static transient final Comparator<PairLong> NATURAL_VALUE_COMPARATOR = (a, b) -> Long.compare(a.getValue(), b.getValue());
@JsonSerialize(using = ToStringSerializer.class)
private long key;
@JsonSerialize(using = ToStringSerializer.class)
private long value;
public static PairLong valueOf(long key, long value) {
var pair = new PairLong();
pair.key = key;
pair.value = value;
return pair;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public long getKey() {
return key;
}
public void setKey(long key) {
this.key = key;
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
}
@@ -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.packet.common;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class PairString implements IPacket {
public static final transient short PROTOCOL_ID = 112;
private String key;
private String value;
public static PairString valueOf(String key, String value) {
var pair = new PairString();
pair.key = key;
pair.value = value;
return pair;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@@ -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.packet.common;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Ping implements IPacket {
public static final transient short PROTOCOL_ID = 103;
public static short pingProtocolId() {
return PROTOCOL_ID;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
}
@@ -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.packet.common;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Pong implements IPacket {
public static final transient short PROTOCOL_ID = 104;
/**
* 服务器当前的时间戳
*/
private long time;
public static Pong valueOf(long time) {
var pong = new Pong();
pong.time = time;
return pong;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
@@ -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.net.packet.common;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.zfoo.protocol.IPacket;
/**
* Long + String + String
*
* @author jaysunxiao
* @version 3.0
*/
public class TripleLSS implements IPacket {
public static final transient short PROTOCOL_ID = 116;
@JsonSerialize(using = ToStringSerializer.class)
private long left;
private String middle;
private String right;
public static TripleLSS valueOf(long left, String middle, String right) {
var triple = new TripleLSS();
triple.left = left;
triple.middle = middle;
triple.right = right;
return triple;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public long getLeft() {
return left;
}
public void setLeft(long left) {
this.left = left;
}
public String getMiddle() {
return middle;
}
public void setMiddle(String middle) {
this.middle = middle;
}
public String getRight() {
return right;
}
public void setRight(String right) {
this.right = right;
}
}
@@ -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.net.packet.common;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class TripleLong implements IPacket {
public static final transient short PROTOCOL_ID = 114;
@JsonSerialize(using = ToStringSerializer.class)
private long left;
@JsonSerialize(using = ToStringSerializer.class)
private long middle;
@JsonSerialize(using = ToStringSerializer.class)
private long right;
public static TripleLong valueOf(long left, long middle, long right) {
var triple = new TripleLong();
triple.left = left;
triple.middle = middle;
triple.right = right;
return triple;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public long getLeft() {
return left;
}
public void setLeft(long left) {
this.left = left;
}
public long getMiddle() {
return middle;
}
public void setMiddle(long middle) {
this.middle = middle;
}
public long getRight() {
return right;
}
public void setRight(long right) {
this.right = right;
}
}
@@ -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.packet.common;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class TripleString implements IPacket {
public static final transient short PROTOCOL_ID = 115;
private String left;
private String middle;
private String right;
public static TripleString valueOf(String left, String middle, String right) {
var triple = new TripleString();
triple.left = left;
triple.middle = middle;
triple.right = right;
return triple;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public String getLeft() {
return left;
}
public void setLeft(String left) {
this.left = left;
}
public String getMiddle() {
return middle;
}
public void setMiddle(String middle) {
this.middle = middle;
}
public String getRight() {
return right;
}
public void setRight(String right) {
this.right = right;
}
}
@@ -0,0 +1,59 @@
/*
* 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.packet.model;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public class DecodedPacketInfo {
/**
* 解码后的包
*/
private IPacket packet;
/**
* 解码后的包的附加包
*/
private IPacketAttachment packetAttachment;
public static DecodedPacketInfo valueOf(IPacket packet, IPacketAttachment packetAttachment) {
DecodedPacketInfo packetInfo = new DecodedPacketInfo();
packetInfo.packet = packet;
packetInfo.packetAttachment = packetAttachment;
return packetInfo;
}
public IPacket getPacket() {
return packet;
}
public void setPacket(IPacket packet) {
this.packet = packet;
}
public IPacketAttachment getPacketAttachment() {
return packetAttachment;
}
public void setPacketAttachment(IPacketAttachment packetAttachment) {
this.packetAttachment = packetAttachment;
}
}
@@ -0,0 +1,84 @@
/*
* 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.packet.model;
import com.zfoo.protocol.IPacket;
import org.springframework.lang.Nullable;
/**
* 被解码后的Packet的信息
*
* @author jaysunxiao
* @version 3.0
*/
public class EncodedPacketInfo {
/**
* 解码后的包
*/
private IPacket packet;
/**
* 解码后的包的附加包
*/
private IPacketAttachment packetAttachment;
/**
* 长度
*/
private int length;
/**
* 加密所用时间
*/
private long encodedTime;
public static EncodedPacketInfo valueOf(IPacket packet, @Nullable IPacketAttachment packetAttachment) {
EncodedPacketInfo packetInfo = new EncodedPacketInfo();
packetInfo.packet = packet;
packetInfo.packetAttachment = packetAttachment;
return packetInfo;
}
public IPacket getPacket() {
return packet;
}
public void setPacket(IPacket packet) {
this.packet = packet;
}
public IPacketAttachment getPacketAttachment() {
return packetAttachment;
}
public void setPacketAttachment(IPacketAttachment packetAttachment) {
this.packetAttachment = packetAttachment;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public long getEncodedTime() {
return encodedTime;
}
public void setEncodedTime(long encodedTime) {
this.encodedTime = encodedTime;
}
}
@@ -0,0 +1,151 @@
/*
* 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.packet.model;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.util.math.HashUtils;
import org.springframework.lang.Nullable;
/**
* 附加包对业务层透明,禁止在业务层使用
*
* @author jaysunxiao
* @version 3.0
*/
public class GatewayPacketAttachment implements IPacketAttachment {
public static final transient short PROTOCOL_ID = 1;
/**
* session的id,一般是客户端连接网关的那个sid
*/
private long sid;
/**
* 用戶Id,从网关转发到后面的消息必须要附带用户的Id信息,要不然无法知道是哪个用户发过来的,0代表没有用户id
*/
private long uid;
/**
* 是否使用consistentHashId作为一致性hashId
*/
private boolean useExecutorConsistentHash;
/**
* 用来在TaskManage中计算一致性hash的参数
*/
private int executorConsistentHash;
/**
* true为客户端,false为服务端
*/
private boolean client;
/**
* 客户端发到网关的可能是一个带有同步或者异步的附加包,网关转发的时候需要把这个附加包给带上
*/
private SignalPacketAttachment signalPacketAttachment;
public GatewayPacketAttachment() {
}
public GatewayPacketAttachment(Session session, @Nullable SignalPacketAttachment signalPacketAttachment) {
this.client = true;
this.sid = session.getSid();
var uid = session.getAttribute(AttributeType.UID);
this.uid = uid == null ? 0 : (long) uid;
this.signalPacketAttachment = signalPacketAttachment;
}
public GatewayPacketAttachment(long sid, long uid) {
this.sid = sid;
this.uid = uid;
}
@Override
public PacketAttachmentType packetType() {
return PacketAttachmentType.GATEWAY_PACKET;
}
@Override
public int executorConsistentHash() {
if (useExecutorConsistentHash) {
return executorConsistentHash;
} else {
return HashUtils.fnvHash(uid);
}
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public void useExecutorConsistentHash(Object argument) {
this.useExecutorConsistentHash = true;
this.executorConsistentHash = HashUtils.fnvHash(argument);
}
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;
}
public boolean isUseExecutorConsistentHash() {
return useExecutorConsistentHash;
}
public void setUseExecutorConsistentHash(boolean useExecutorConsistentHash) {
this.useExecutorConsistentHash = useExecutorConsistentHash;
}
public int getExecutorConsistentHash() {
return executorConsistentHash;
}
public void setExecutorConsistentHash(int executorConsistentHash) {
this.executorConsistentHash = executorConsistentHash;
}
public boolean isClient() {
return client;
}
public void setClient(boolean client) {
this.client = client;
}
public SignalPacketAttachment getSignalPacketAttachment() {
return signalPacketAttachment;
}
public void setSignalPacketAttachment(SignalPacketAttachment signalPacketAttachment) {
this.signalPacketAttachment = signalPacketAttachment;
}
}
@@ -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.packet.model;
import com.zfoo.protocol.IPacket;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IPacketAttachment extends IPacket {
PacketAttachmentType packetType();
/**
* 用来确定这条消息在哪一个线程处理
*
* @return 一致性hashId
*/
int executorConsistentHash();
}
@@ -0,0 +1,60 @@
/*
* 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.packet.model;
/**
* 附加包对业务层透明,禁止在业务层使用
*
* @author jaysunxiao
* @version 3.0
*/
public class NoAnswerAttachment implements IPacketAttachment {
public static final transient short PROTOCOL_ID = 2;
/**
* 用来在TaskManage中计算一致性hash的参数
*/
private int executorConsistentHash;
public static NoAnswerAttachment valueOf(int executorConsistentHash) {
var attachment = new NoAnswerAttachment();
attachment.executorConsistentHash = executorConsistentHash;
return attachment;
}
@Override
public PacketAttachmentType packetType() {
return PacketAttachmentType.NO_ANSWER_PACKET;
}
@Override
public int executorConsistentHash() {
return executorConsistentHash;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
public int getExecutorConsistentHash() {
return executorConsistentHash;
}
public void setExecutorConsistentHash(int executorConsistentHash) {
this.executorConsistentHash = executorConsistentHash;
}
}
@@ -0,0 +1,72 @@
/*
* 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.packet.model;
import java.util.HashMap;
import java.util.Map;
/**
* @author jaysunxiao
* @version 3.0
*/
public enum PacketAttachmentType {
/**
* 正常的附加包
*/
NORMAL_PACKET((byte) 0, null),
/**
* 带有同步或者异步信息的附加包
*/
SIGNAL_PACKET((byte) 1, SignalPacketAttachment.class),
/**
* 带有网关信息的附加包
*/
GATEWAY_PACKET((byte) 2, GatewayPacketAttachment.class),
/**
* 无返回消息的附加包
*/
NO_ANSWER_PACKET((byte) 3, NoAnswerAttachment.class),
;
public static final Map<Byte, PacketAttachmentType> map = new HashMap<>(values().length);
static {
for (var packetType : PacketAttachmentType.values()) {
map.put(packetType.packetType, packetType);
}
}
public static PacketAttachmentType getPacketType(byte packetType) {
return map.getOrDefault(packetType, PacketAttachmentType.NORMAL_PACKET);
}
public byte getPacketType() {
return packetType;
}
private byte packetType;
private Class<? extends IPacketAttachment> clazz;
PacketAttachmentType(byte packetType, Class<? extends IPacketAttachment> clazz) {
this.packetType = packetType;
this.clazz = clazz;
}
}
@@ -0,0 +1,135 @@
/*
* 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.packet.model;
import com.zfoo.protocol.IPacket;
import com.zfoo.scheduler.util.TimeUtils;
import com.zfoo.util.security.IdUtils;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
/**
* 附加包对业务层透明,禁止在业务层使用
*
* @author jaysunxiao
* @version 3.0
*/
public class SignalPacketAttachment implements IPacketAttachment {
public static final transient short PROTOCOL_ID = 0;
/**
* 唯一标识一个packet, 唯一表示一个PacketAttachment,hashcode() and equals() 也通过packetId计算
*/
private int packetId = IdUtils.getLocalIntId();
/**
* 用来在TaskManage中计算一致性hash的参数
*/
private int executorConsistentHash = -1;
/**
* true为客户端,false为服务端
*/
private boolean client = true;
/**
* 客户端发送的时间
*/
private transient long timestamp = TimeUtils.now();
/**
* 客户端收到服务器回复的时候回调的方法
*/
private transient CompletableFuture<IPacket> responseFuture = new CompletableFuture<>();
public SignalPacketAttachment() {
}
@Override
public PacketAttachmentType packetType() {
return PacketAttachmentType.SIGNAL_PACKET;
}
@Override
public int executorConsistentHash() {
return executorConsistentHash;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
@Override
public short protocolId() {
return PROTOCOL_ID;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SignalPacketAttachment that = (SignalPacketAttachment) o;
return Objects.equals(packetId, that.packetId);
}
@Override
public int hashCode() {
return Objects.hash(packetId);
}
public int getPacketId() {
return packetId;
}
public void setPacketId(int packetId) {
this.packetId = packetId;
}
public int getExecutorConsistentHash() {
return executorConsistentHash;
}
public void setExecutorConsistentHash(int executorConsistentHash) {
this.executorConsistentHash = executorConsistentHash;
}
public boolean isClient() {
return client;
}
public void setClient(boolean client) {
this.client = client;
}
public CompletableFuture<IPacket> getResponseFuture() {
return responseFuture;
}
public void setResponseFuture(CompletableFuture<IPacket> responseFuture) {
this.responseFuture = responseFuture;
}
}
@@ -0,0 +1,34 @@
/*
* 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.packet.service;
import com.zfoo.net.packet.model.DecodedPacketInfo;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.protocol.IPacket;
import io.netty.buffer.ByteBuf;
import org.springframework.lang.Nullable;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface IPacketService {
void init();
DecodedPacketInfo read(ByteBuf buffer);
void write(ByteBuf buffer, IPacket packet, @Nullable IPacketAttachment packetAttachment);
}
@@ -0,0 +1,151 @@
/*
* 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.packet.service;
import com.zfoo.net.NetContext;
import com.zfoo.net.packet.model.DecodedPacketInfo;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.protocol.IPacket;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.buffer.ByteBufUtils;
import com.zfoo.protocol.exception.ExceptionUtils;
import com.zfoo.protocol.generate.GenerateOperation;
import com.zfoo.protocol.generate.GenerateProtocolFile;
import com.zfoo.protocol.registration.IProtocolRegistration;
import com.zfoo.protocol.xml.XmlProtocols;
import com.zfoo.util.DomUtils;
import io.netty.buffer.ByteBuf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ResourceUtils;
import java.io.IOException;
import java.util.function.Predicate;
/**
* @author jaysunxiao
* @version 3.0
*/
public class PacketService implements IPacketService {
private static final Logger logger = LoggerFactory.getLogger(PacketService.class);
/**
* 网络包的约定规则如下:
* 1. 客户端的请求约定以Request结尾,服务器的响应约定以Response结尾
* 2. 服务器内部请求约定以Ask结尾,服务器内部的响应约定以Answer结尾
* 3. 服务器主动通知客户端以Notice结尾
* 4. 公共的协议放在common模块
*/
public static final String NET_REQUEST_SUFFIX = "Request";
public static final String NET_RESPONSE_SUFFIX = "Response";
public static final String NET_ASK_SUFFIX = "Ask";
public static final String NET_ANSWER_SUFFIX = "Answer";
public static final String NET_NOTICE_SUFFIX = "Notice";
public static final String NET_COMMON_MODULE = "common";
private Predicate<IProtocolRegistration> netGenerateProtocolFilter = registration
-> ProtocolManager.moduleByModuleId(registration.module()).getName().matches(NET_COMMON_MODULE)
|| registration.protocolConstructor().getDeclaringClass().getSimpleName().endsWith(NET_REQUEST_SUFFIX)
|| registration.protocolConstructor().getDeclaringClass().getSimpleName().endsWith(NET_RESPONSE_SUFFIX)
|| registration.protocolConstructor().getDeclaringClass().getSimpleName().endsWith(NET_NOTICE_SUFFIX);
public PacketService() {
}
@Override
public void init() {
var applicationContext = NetContext.getApplicationContext();
var protocolLocation = NetContext.getConfigManager().getLocalConfig().getProtocolLocation();
var foldProtocol = NetContext.getConfigManager().getLocalConfig().isFoldProtocol();
var protocolParam = NetContext.getConfigManager().getLocalConfig().getProtocolParam();
var generateJsProtocol = NetContext.getConfigManager().getLocalConfig().isGenerateJsProtocol();
var generateCsharpProtocol = NetContext.getConfigManager().getLocalConfig().isGenerateCsProtocol();
var generateLuaProtocol = NetContext.getConfigManager().getLocalConfig().isGenerateLuaProtocol();
var generateOperation = new GenerateOperation();
generateOperation.setFoldProtocol(foldProtocol);
generateOperation.setProtocolParam(protocolParam);
generateOperation.setGenerateJsProtocol(generateJsProtocol);
generateOperation.setGenerateCsharpProtocol(generateCsharpProtocol);
generateOperation.setGenerateLuaProtocol(generateLuaProtocol);
// 设置生成协议的过滤器
GenerateProtocolFile.generateProtocolFilter = netGenerateProtocolFilter;
// 解析protocol.xml文件,并将协议生成ProtocolRegistration
var resource = applicationContext.getResource(ResourceUtils.CLASSPATH_URL_PREFIX + protocolLocation);
try {
var xmlProtocols = DomUtils.inputStream2Object(resource.getInputStream(), XmlProtocols.class);
ProtocolManager.initProtocol(xmlProtocols, generateOperation);
} catch (IOException e) {
logger.error(ExceptionUtils.getMessage(e));
throw new RuntimeException(e);
}
}
@Override
public DecodedPacketInfo read(ByteBuf buffer) {
// 包的长度在上一层已经解析过
// 解析包体
var packet = ProtocolManager.read(buffer);
// 解析包的附加包
var attachment = ByteBufUtils.readBoolean(buffer);
var packetAttachment = attachment ? ((IPacketAttachment) ProtocolManager.read(buffer)) : null;
return DecodedPacketInfo.valueOf(packet, packetAttachment);
}
@Override
public void write(ByteBuf buffer, IPacket packet, IPacketAttachment packetAttachment) {
if (packet == null) {
logger.error("packet is null and can not be sent.");
return;
}
buffer.clear();
// 预留写入包的长度,一个int字节大小
buffer.writeInt(ProtocolManager.PROTOCOL_HEAD_LENGTH);
// 写入包packet
ProtocolManager.write(buffer, packet);
// 写入包的附加包packetAttachment
if (packetAttachment == null) {
ByteBufUtils.writeBoolean(buffer, false);
} else {
ByteBufUtils.writeBoolean(buffer, true);
// 写入包的附加包attachment
ProtocolManager.write(buffer, packetAttachment);
}
int length = buffer.readableBytes();
int packetLength = length - ProtocolManager.PROTOCOL_HEAD_LENGTH;
buffer.writerIndex(0);
buffer.writeInt(packetLength);
buffer.writerIndex(length);
}
}
@@ -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.net.schema;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NamespaceHandler extends NamespaceHandlerSupport {
private final String NET_TAG = "config";
@Override
public void init() {
registerBeanDefinitionParser(NET_TAG, new NetDefinitionParser());
}
}
@@ -0,0 +1,237 @@
/*
* 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.schema;
import com.zfoo.net.NetContext;
import com.zfoo.net.config.manager.ConfigManager;
import com.zfoo.net.config.model.*;
import com.zfoo.net.consumer.service.Consumer;
import com.zfoo.net.dispatcher.manager.PacketDispatcher;
import com.zfoo.net.packet.service.PacketService;
import com.zfoo.net.session.manager.SessionManager;
import com.zfoo.protocol.ProtocolManager;
import com.zfoo.protocol.registration.ProtocolModule;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.DomUtils;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
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 NetDefinitionParser implements BeanDefinitionParser {
@Override
public AbstractBeanDefinition parse(Element element, ParserContext parserContext) {
Class<?> clazz;
String name;
BeanDefinitionBuilder builder;
// 注册NetConfig
parseNetConfig(element, parserContext);
// 注册NetSpringContext
clazz = NetContext.class;
name = StringUtils.uncapitalize(clazz.getName());
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition());
// 注册ConfigManager
clazz = ConfigManager.class;
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
builder.addPropertyReference("localConfig", NetConfig.class.getCanonicalName());
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
// 注册NetProcessor
clazz = NetProcessor.class;
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
// 注册ProtocolManager
clazz = ProtocolManager.class;
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
// 注册PacketService
clazz = PacketService.class;
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
// 注册PacketDispatcherManager
clazz = PacketDispatcher.class;
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
// 注册Consumer
clazz = Consumer.class;
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
// 注册SessionManager
clazz = SessionManager.class;
builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
return builder.getBeanDefinition();
}
private void parseNetConfig(Element element, ParserContext parserContext) {
var clazz = NetConfig.class;
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
resolvePlaceholder("id", "id", builder, element, parserContext);
resolvePlaceholder("protocol-location", "protocolLocation", builder, element, parserContext);
resolvePlaceholder("generate-js-protocol", "generateJsProtocol", builder, element, parserContext);
resolvePlaceholder("generate-cs-protocol", "generateCsProtocol", builder, element, parserContext);
resolvePlaceholder("generate-lua-protocol", "generateLuaProtocol", builder, element, parserContext);
resolvePlaceholder("fold-protocol", "foldProtocol", builder, element, parserContext);
resolvePlaceholder("protocol-param", "protocolParam", builder, element, parserContext);
var registryElement = DomUtils.getFirstChildElementByTagName(element, "registry");
if (registryElement != null) {
parseRegistryConfig(registryElement, parserContext);
builder.addPropertyReference("registryConfig", RegistryConfig.class.getCanonicalName());
}
var monitorElement = DomUtils.getFirstChildElementByTagName(element, "monitor");
if (monitorElement != null) {
parseMonitorConfig(monitorElement, parserContext);
builder.addPropertyReference("monitorConfig", MonitorConfig.class.getCanonicalName());
}
var hostElement = DomUtils.getFirstChildElementByTagName(element, "host");
if (hostElement != null) {
builder.addPropertyReference("hostConfig", HostConfig.class.getCanonicalName());
parseHostConfig(hostElement, parserContext);
}
var providerElement = DomUtils.getFirstChildElementByTagName(element, "provider");
if (providerElement != null) {
builder.addPropertyReference("providerConfig", ProviderConfig.class.getCanonicalName());
parseProviderConfig(providerElement, parserContext);
}
var consumerElement = DomUtils.getFirstChildElementByTagName(element, "consumer");
if (consumerElement != null) {
parseConsumerConfig(consumerElement, parserContext);
builder.addPropertyReference("consumerConfig", ConsumerConfig.class.getCanonicalName());
}
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
}
private void parseRegistryConfig(Element element, ParserContext parserContext) {
var clazz = RegistryConfig.class;
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
resolvePlaceholder("center", "center", builder, element, parserContext);
resolvePlaceholder("user", "user", builder, element, parserContext);
resolvePlaceholder("password", "password", builder, element, parserContext);
var addressMap = parseAddress(element, parserContext);
builder.addPropertyValue("addressMap", addressMap);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
}
private void parseMonitorConfig(Element element, ParserContext parserContext) {
var clazz = MonitorConfig.class;
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
resolvePlaceholder("center", "center", builder, element, parserContext);
resolvePlaceholder("user", "user", builder, element, parserContext);
resolvePlaceholder("password", "password", builder, element, parserContext);
var addressMap = parseAddress(element, parserContext);
builder.addPropertyValue("addressMap", addressMap);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
}
private void parseHostConfig(Element element, ParserContext parserContext) {
var clazz = HostConfig.class;
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
resolvePlaceholder("center", "center", builder, element, parserContext);
resolvePlaceholder("user", "user", builder, element, parserContext);
resolvePlaceholder("password", "password", builder, element, parserContext);
var addressMap = parseAddress(element, parserContext);
builder.addPropertyValue("addressMap", addressMap);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
}
private void parseProviderConfig(Element element, ParserContext parserContext) {
var clazz = ProviderConfig.class;
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
resolvePlaceholder("dispatch", "dispatch", builder, element, parserContext);
resolvePlaceholder("dispatch-thread", "dispatchThread", builder, element, parserContext);
resolvePlaceholder("address", "address", builder, element, parserContext);
var providerModules = parseModules("provider", element, parserContext);
builder.addPropertyValue("modules", providerModules);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
}
private void parseConsumerConfig(Element element, ParserContext parserContext) {
var clazz = ConsumerConfig.class;
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
resolvePlaceholder("load-balancer", "loadBalancer", builder, element, parserContext);
var consumerModules = parseModules("consumer", element, parserContext);
builder.addPropertyValue("modules", consumerModules);
parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition());
}
private ManagedList<BeanDefinitionHolder> parseModules(String param, Element element, ParserContext parserContext) {
var moduleElementList = DomUtils.getChildElementsByTagName(element, "module");
var modules = new ManagedList<BeanDefinitionHolder>();
var environment = parserContext.getReaderContext().getEnvironment();
for (var i = 0; i < moduleElementList.size(); i++) {
var addressElement = moduleElementList.get(i);
var clazz = ProtocolModule.class;
var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz);
builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("name")));
modules.add(new BeanDefinitionHolder(builder.getBeanDefinition(), StringUtils.format("{}.{}{}", clazz.getCanonicalName(), param, i)));
}
return modules;
}
private ManagedMap<String, String> parseAddress(Element element, ParserContext parserContext) {
var addressElementList = DomUtils.getChildElementsByTagName(element, "address");
var addressMap = new ManagedMap<String, String>();
for (var addressElement : addressElementList) {
var name = addressElement.getAttribute("name");
var urlAttribute = addressElement.getAttribute("url");
var url = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(urlAttribute);
addressMap.put(name, url);
}
return addressMap;
}
private void resolvePlaceholder(String attributeName, String fieldName, BeanDefinitionBuilder builder, Element element, ParserContext parserContext) {
var attributeValue = element.getAttribute(attributeName);
var environment = parserContext.getReaderContext().getEnvironment();
var placeholder = environment.resolvePlaceholders(attributeValue);
builder.addPropertyValue(fieldName, placeholder);
}
}
@@ -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.schema;
import com.zfoo.net.NetContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* @author jaysunxiao
* @version 3.0
*/
public class NetProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (NetContext.getNetContext() == null) {
return bean;
}
NetContext.getDispatcher().registerPacketReceiverDefinition(bean);
return bean;
}
}
@@ -0,0 +1,47 @@
/*
* 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.session.manager;
import com.zfoo.net.session.model.Session;
import java.util.Map;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface ISessionManager {
void addServerSession(Session session);
void removeServerSession(Session session);
Session getServerSession(Long id);
Map<Long, Session> getServerSessionMap();
void addClientSession(Session session);
void removeClientSession(Session session);
Session getClientSession(Long id);
Map<Long, Session> getClientSessionMap();
int getClientSessionChangeId();
void shutdown();
}
@@ -0,0 +1,133 @@
/*
* 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.session.manager;
import com.zfoo.net.session.model.Session;
import com.zfoo.net.util.SessionUtils;
import com.zfoo.util.security.IdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author jaysunxiao
* @version 3.0
*/
public class SessionManager implements ISessionManager {
private static final Logger logger = LoggerFactory.getLogger(SessionManager.class);
/**
* 作为服务器,被别的客户端连接的Session
*/
private final Map<Long, Session> serverSessionMap = new ConcurrentHashMap<>();
/**
* 作为客户端,连接别的服务器的Session
*/
private final Map<Long, Session> clientSessionMap = new ConcurrentHashMap<>();
private volatile int clientSessionChangeId = IdUtils.getLocalIntId();
@Override
public void addServerSession(Session session) {
if (serverSessionMap.containsKey(session.getSid())) {
logger.error("server收到重复的[session:{}]", SessionUtils.sessionInfo(session));
return;
}
serverSessionMap.put(session.getSid(), session);
}
@Override
public void removeServerSession(Session session) {
if (!serverSessionMap.containsKey(session.getSid())) {
logger.error("SessionManager中的serverSession没有包含[session:{}],所以无法移除", SessionUtils.sessionInfo(session));
return;
}
serverSessionMap.remove(session.getSid());
session.close();
}
@Override
public Session getServerSession(Long id) {
return serverSessionMap.get(id);
}
@Override
public Map<Long, Session> getServerSessionMap() {
return Collections.unmodifiableMap(serverSessionMap);
}
@Override
public void addClientSession(Session session) {
if (clientSessionMap.containsKey(session.getSid())) {
logger.error("client收到重复的[session:{}]", SessionUtils.sessionInfo(session));
return;
}
clientSessionMap.put(session.getSid(), session);
clientSessionChangeId = IdUtils.getLocalIntId();
}
@Override
public void removeClientSession(Session session) {
if (!clientSessionMap.containsKey(session.getSid())) {
logger.error("SessionManager中的clientSession没有包含[session:{}],所以无法移除", SessionUtils.sessionInfo(session));
return;
}
clientSessionMap.remove(session.getSid());
session.close();
clientSessionChangeId = IdUtils.getLocalIntId();
}
@Override
public Session getClientSession(Long id) {
return clientSessionMap.get(id);
}
@Override
public Map<Long, Session> getClientSessionMap() {
return Collections.unmodifiableMap(clientSessionMap);
}
@Override
public int getClientSessionChangeId() {
return clientSessionChangeId;
}
@Override
public synchronized void shutdown() {
clientSessionMap.values().forEach(it -> {
try {
it.close();
} catch (Exception e) {
logger.error("关闭[session:{}]发生未知异常", SessionUtils.sessionInfo(it), e);
}
});
serverSessionMap.values().forEach(it -> {
try {
it.close();
} catch (Exception e) {
logger.error("关闭[session:{}]发生未知异常", SessionUtils.sessionInfo(it), e);
}
});
logger.info("已关闭客户端和服务器所有的session");
}
}
@@ -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.session.model;
/**
* @author jaysunxiao
* @version 3.0
*/
public enum AttributeType {
CHANNEL_REMOTE_ADDRESS,
/**
* 一般是客户端session
*/
CONSUMER,
RESPONSE_TIME,
/**
* session的uid
*/
UID,
/**
* 网关ip
*/
GATEWAY_HOST_AND_PORT,
;
}
@@ -0,0 +1,127 @@
/*
* 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.session.model;
import com.zfoo.net.packet.model.IPacketAttachment;
import com.zfoo.net.packet.model.SignalPacketAttachment;
import com.zfoo.protocol.util.StringUtils;
import io.netty.channel.Channel;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author jaysunxiao
* @version 3.0
*/
public class Session {
private static final AtomicLong ATOMIC_LONG = new AtomicLong(0);
/**
* session的id
*/
private long sid;
private Channel channel;
/**
* Session附带的属性参数
*/
private Map<AttributeType, Object> attributes = new EnumMap<>(AttributeType.class);
/**
* 客户端Session控制同步或异步的附加包,key:packetId
*/
private Map<Integer, SignalPacketAttachment> clientSignalPacketAttachmentMap = new ConcurrentHashMap<>();
public Session(Channel channel) {
if (channel == null) {
throw new IllegalArgumentException("channel不能为空");
}
this.sid = ATOMIC_LONG.getAndIncrement();
this.channel = channel;
}
public void addClientSignalAttachment(SignalPacketAttachment packetAttachment) {
clientSignalPacketAttachmentMap.put(packetAttachment.getPacketId(), packetAttachment);
}
public IPacketAttachment removeClientSignalAttachment(SignalPacketAttachment packetAttachment) {
return clientSignalPacketAttachmentMap.remove(packetAttachment.getPacketId());
}
@Override
public String toString() {
return StringUtils.format("[sid:{}] [channel:{}] [attributes:{}]", sid, channel, attributes);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Session session = (Session) o;
return sid == session.sid;
}
@Override
public int hashCode() {
return Objects.hash(sid);
}
public long getSid() {
return sid;
}
public void setSid(long sid) {
this.sid = sid;
}
public synchronized void putAttribute(AttributeType key, Object value) {
attributes.put(key, value);
}
public synchronized void removeAttribute(AttributeType key) {
attributes.remove(key);
}
public Object getAttribute(AttributeType key) {
return attributes.get(key);
}
public Map<Integer, IPacketAttachment> getClientSignalPacketAttachmentMap() {
return Collections.unmodifiableMap(clientSignalPacketAttachmentMap);
}
public Channel getChannel() {
return channel;
}
public void close() {
channel.close();
clientSignalPacketAttachmentMap.clear();
}
}
@@ -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.net.task;
import com.zfoo.net.task.model.ReceiveTask;
import java.util.concurrent.ExecutorService;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface ITaskManager {
void addTask(ReceiveTask task);
ExecutorService getExecutorByConsistentHash(int hash);
}
@@ -0,0 +1,112 @@
/*
* 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.task;
import com.zfoo.net.NetContext;
import com.zfoo.net.task.model.AbstractTaskDispatch;
import com.zfoo.net.task.model.ITaskDispatch;
import com.zfoo.net.task.model.ReceiveTask;
import com.zfoo.protocol.util.StringUtils;
import com.zfoo.util.math.RandomUtils;
import io.netty.util.concurrent.FastThreadLocalThread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 final class TaskManager implements ITaskManager {
private static final Logger logger = LoggerFactory.getLogger(TaskManager.class);
private static final TaskManager INSTANCE = new TaskManager();
// 线程池的大小
public static final int EXECUTOR_SIZE;
private static final ITaskDispatch taskDispatch;
/**
* 使用不同的线程池,让线程池之间实现隔离,互不影响
*/
private static final ExecutorService[] executors;
static {
var localConfig = NetContext.getConfigManager().getLocalConfig();
var providerConfig = localConfig.getProviderConfig();
var dispatch = providerConfig == null ? "consistent-hash" : providerConfig.getDispatch();
var dispatchThread = (providerConfig == null || StringUtils.isBlank(providerConfig.getDispatchThread()))
? "default" : providerConfig.getDispatchThread();
EXECUTOR_SIZE = "default".equals(dispatchThread) ? (Runtime.getRuntime().availableProcessors() + 1) : Integer.parseInt(dispatchThread);
taskDispatch = AbstractTaskDispatch.valueOf(dispatch);
executors = new ExecutorService[EXECUTOR_SIZE];
for (int i = 0; i < executors.length; i++) {
var namedThreadFactory = new TaskThreadFactory();
executors[i] = Executors.newSingleThreadExecutor(namedThreadFactory);
}
}
private static class TaskThreadFactory 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;
TaskThreadFactory() {
var s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "task-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;
}
}
private TaskManager() {
}
public static TaskManager getInstance() {
return INSTANCE;
}
@Override
public void addTask(ReceiveTask task) {
taskDispatch.getExecutor(task).execute(task);
}
@Override
public ExecutorService getExecutorByConsistentHash(int executorConsistentHash) {
if (executorConsistentHash >= 0) {
return executors[Math.abs(executorConsistentHash % EXECUTOR_SIZE)];
} else {
return executors[RandomUtils.randomInt(TaskManager.EXECUTOR_SIZE)];
}
}
}
@@ -0,0 +1,37 @@
/*
* 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.task.model;
import com.zfoo.protocol.util.StringUtils;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class AbstractTaskDispatch implements ITaskDispatch {
public static ITaskDispatch valueOf(String taskDispatchName) {
switch (taskDispatchName) {
case "random":
return new RandomTaskDispatch();
case "sessionId":
return new SessionIdTaskDispatch();
case "consistent-hash":
return new ConsistentHashTaskDispatch();
default:
throw new RuntimeException(StringUtils.format("没有找到对应的taskDispatch[{}]", taskDispatchName));
}
}
}
@@ -0,0 +1,43 @@
/*
* 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.task.model;
import com.zfoo.net.task.TaskManager;
import java.util.concurrent.ExecutorService;
/**
* @author jaysunxiao
* @version 3.0
*/
public class ConsistentHashTaskDispatch extends AbstractTaskDispatch {
private static ConsistentHashTaskDispatch instance = new ConsistentHashTaskDispatch();
public static ConsistentHashTaskDispatch getInstance() {
return instance;
}
@Override
public ExecutorService getExecutor(ReceiveTask receiveTask) {
var packetAttachment = receiveTask.getPacketAttachment();
if (packetAttachment == null) {
return SessionIdTaskDispatch.getInstance().getExecutor(receiveTask);
}
return TaskManager.getInstance().getExecutorByConsistentHash(packetAttachment.executorConsistentHash());
}
}
@@ -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.task.model;
import java.util.concurrent.ExecutorService;
/**
* @author jaysunxiao
* @version 3.0
*/
public interface ITaskDispatch {
ExecutorService getExecutor(ReceiveTask receiveTask);
}
@@ -0,0 +1,37 @@
/*
* 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.task.model;
import com.zfoo.net.task.TaskManager;
import java.util.concurrent.ExecutorService;
/**
* @author jaysunxiao
* @version 3.0
*/
public class RandomTaskDispatch extends AbstractTaskDispatch {
private static final RandomTaskDispatch INSTANCE = new RandomTaskDispatch();
public static RandomTaskDispatch getInstance() {
return INSTANCE;
}
@Override
public ExecutorService getExecutor(ReceiveTask receiveTask) {
return TaskManager.getInstance().getExecutorByConsistentHash(-1);
}
}
@@ -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.task.model;
import com.zfoo.net.NetContext;
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 final class ReceiveTask implements Runnable {
private Session session;
private IPacket packet;
private IPacketAttachment packetAttachment;
public ReceiveTask(Session session, IPacket packet, IPacketAttachment packetAttachment) {
this.session = session;
this.packet = packet;
this.packetAttachment = packetAttachment;
}
@Override
public void run() {
NetContext.getDispatcher().doReceive(session, packet, packetAttachment);
}
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
public IPacket getPacket() {
return packet;
}
public void setPacket(IPacket packet) {
this.packet = packet;
}
public IPacketAttachment getPacketAttachment() {
return packetAttachment;
}
public void setPacketAttachment(IPacketAttachment packetAttachment) {
this.packetAttachment = packetAttachment;
}
}
@@ -0,0 +1,27 @@
package com.zfoo.net.task.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class SafeRunnable implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(SafeRunnable.class);
@Override
public void run() {
try {
doRun();
} catch (Exception e) {
logger.error("未知exception异常", e);
} catch (Throwable t) {
logger.error("未知throwable异常", t);
}
}
public abstract void doRun();
}
@@ -0,0 +1,41 @@
/*
* 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.task.model;
import com.zfoo.net.task.TaskManager;
import com.zfoo.util.math.HashUtils;
import java.util.concurrent.ExecutorService;
/**
* 同一个session总是分配到同一个线程池执行
*
* @author jaysunxiao
* @version 3.0
*/
public class SessionIdTaskDispatch extends AbstractTaskDispatch {
private static final SessionIdTaskDispatch INSTANCE = new SessionIdTaskDispatch();
public static SessionIdTaskDispatch getInstance() {
return INSTANCE;
}
@Override
public ExecutorService getExecutor(ReceiveTask receiveTask) {
var session = receiveTask.getSession();
return TaskManager.getInstance().getExecutorByConsistentHash(HashUtils.fnvHash(session.getSid()));
}
}
@@ -0,0 +1,62 @@
/*
* 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.util;
import com.zfoo.net.session.model.AttributeType;
import com.zfoo.net.session.model.Session;
import com.zfoo.protocol.util.StringUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import static com.zfoo.net.handler.BaseDispatcherHandler.SESSION_KEY;
/**
* @author jaysunxiao
* @version 3.0
*/
public abstract class SessionUtils {
private static final String CHANNEL_INFO_TEMPLATE = "[ip:{}][sid:{}][uid:{}]";
public static boolean isActive(Session session) {
return session != null && session.getChannel().isActive();
}
public static boolean isActive(Channel session) {
return session != null && session.isActive();
}
public static Session getSession(ChannelHandlerContext ctx) {
var sessionAttr = ctx.channel().attr(SESSION_KEY);
return sessionAttr.get();
}
public static String sessionInfo(ChannelHandlerContext ctx) {
var session = SessionUtils.getSession(ctx);
if (session == null) {
return StringUtils.format(CHANNEL_INFO_TEMPLATE, ctx.channel());
}
return sessionInfo(session);
}
public static String sessionInfo(Session session) {
if (session == null) {
return CHANNEL_INFO_TEMPLATE;
}
var remoteAddress = session.getAttribute(AttributeType.CHANNEL_REMOTE_ADDRESS);
return StringUtils.format(CHANNEL_INFO_TEMPLATE, remoteAddress, session.getSid(), session.getAttribute(AttributeType.UID));
}
}
@@ -0,0 +1,197 @@
/*
* 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.util;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.zfoo.event.manager.EventBus;
import com.zfoo.protocol.collection.CollectionUtils;
import com.zfoo.protocol.model.Pair;
import com.zfoo.scheduler.manager.SchedulerManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* guava的缓存都是单个存,单个更新,但是实际的项目很多都是批量查询,批量更新。
* <p>
* 优势:
* 1.支持批量查找,批量更新。
* 2.可以防止缓存穿透,缓存击穿,缓存雪崩
* <p>
* 当查找的key不存在的时候,返回defaultValue,并且将defaultValue放入缓存。所以通过
*
* @author jaysunxiao
* @version 3.0
*/
public class SimpleCache<K, V> {
/**
* 每次最多更新1000条数据
*/
private static final int BATCH_RELOAD_SIZE = 1000;
private LoadingCache<K, V> cache;
private ConcurrentLinkedQueue linkedQueue;
private long expiredAccessDuration;
private long refreshDuration;
private Function<List<K>, List<Pair<K, V>>> batchReloadCallback;
private Function<K, V> defaultValueBuilder;
/**
* @param expiredAccessDuration 访问过期时间,毫秒;通常情况下,这个值比refreshDuration大会得到更好的缓存效果,一般是2倍
* @param refreshDuration 刷新实际那,毫秒;因为是通过后台scheduler去更新缓存,所以更新的refresh可能是这个值的2倍
* @param maxSize 缓存大小
* @param batchReloadCallback 一组key取value的回调方法
* @param defaultValueBuilder 默认值构建
* @return 简单的缓存
*/
public static <K, V> SimpleCache<K, V> build(long expiredAccessDuration, long refreshDuration, long maxSize
, Function<List<K>, List<Pair<K, V>>> batchReloadCallback
, Function<K, V> defaultValueBuilder) {
var linkedQueue = new ConcurrentLinkedQueue<K>();
// 没有用guava的expireAfterWrite的原因是容易造成缓存击穿
var cache = Caffeine.newBuilder()
.expireAfterAccess(expiredAccessDuration, TimeUnit.MILLISECONDS)
.refreshAfterWrite(refreshDuration, TimeUnit.MILLISECONDS)
.maximumSize(maxSize)
.recordStats()
.build(new CacheLoader<K, V>() {
@Override
public @Nullable V load(@NonNull K key) {
// 因为通过SimpleCache封装过后,上层逻辑是不会调用guava的cache的get方法,所以理论上load不会执行
var resultList = batchReloadCallback.apply(List.of(key));
return CollectionUtils.isEmpty(resultList) ? defaultValueBuilder.apply(key) : resultList.get(0).getValue();
}
@Override
public @Nullable V reload(@NonNull K key, @NonNull V oldValue) {
linkedQueue.offer(key);
// 先返回老的值,等周期任务刷新新的值
return oldValue;
}
});
SchedulerManager.getInstance().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 不在任务调度线程中执行耗时任务,因为任务调度线程只有一个线程池
EventBus.asyncExecute().execute(new Runnable() {
@Override
public void run() {
var list = new ArrayList<K>();
while (!linkedQueue.isEmpty()) {
var key = linkedQueue.poll();
list.add(key);
if (list.size() >= BATCH_RELOAD_SIZE) {
var result = batchReloadCallback.apply(list);
result.forEach(it -> cache.put(it.getKey(), it.getValue()));
list.clear();
}
}
if (CollectionUtils.isNotEmpty(list)) {
var result = batchReloadCallback.apply(list);
result.forEach(it -> cache.put(it.getKey(), it.getValue()));
}
}
});
}
}, refreshDuration, TimeUnit.MILLISECONDS);
var simpleCache = new SimpleCache<K, V>();
simpleCache.cache = cache;
simpleCache.linkedQueue = linkedQueue;
simpleCache.expiredAccessDuration = expiredAccessDuration;
simpleCache.refreshDuration = refreshDuration;
simpleCache.batchReloadCallback = batchReloadCallback;
simpleCache.defaultValueBuilder = defaultValueBuilder;
return simpleCache;
}
/**
* 单个查找
*/
public V get(K k) {
try {
return cache.get(k);
} catch (Exception e) {
var defaultValue = defaultValueBuilder.apply(k);
cache.put(k, defaultValue);
return defaultValue;
}
}
/**
* 异步刷新缓存
*/
public void invalidate(K k) {
cache.invalidate(k);
}
public void put(K k, V v) {
cache.put(k, v);
}
/**
* 批量查找
*/
public Map<K, V> batchGet(Collection<K> list) {
if (CollectionUtils.isEmpty(list)) {
return Collections.emptyMap();
}
var result = new HashMap<K, V>();
var notPresentIdSet = new HashSet<K>();
for (var id : list) {
var value = cache.getIfPresent(id);
if (value != null) {
result.put(id, value);
} else {
notPresentIdSet.add(id);
}
}
if (CollectionUtils.isNotEmpty(notPresentIdSet)) {
batchReloadCallback.apply(new ArrayList<>(notPresentIdSet))
.forEach(it -> {
result.put(it.getKey(), it.getValue());
cache.put(it.getKey(), it.getValue());
notPresentIdSet.remove(it.getKey());
});
}
// 防止缓存穿透,将数据库查询不到的键存入到缓存,value给一个默认值
if (CollectionUtils.isNotEmpty(notPresentIdSet)) {
notPresentIdSet.forEach(it -> {
var defaultValue = defaultValueBuilder.apply(it);
result.put(it, defaultValue);
cache.put(it, defaultValue);
});
}
return result;
}
}
@@ -0,0 +1,78 @@
/*
* 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.util;
import com.zfoo.event.manager.EventBus;
import com.zfoo.scheduler.util.TimeUtils;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
/**
* 单值缓存,会隔一段时间在后台刷新一下缓存
*
* @author jaysunxiao
* @version 3.0
*/
public class SingleCache<V> {
private long refreshDuration;
private Supplier<V> supplier;
private volatile V cache;
private volatile long refreshTime;
private Lock lock = new ReentrantLock();
/**
* @param refreshDuration 刷新实际那,毫秒
* @param supplier 缓存提供者
* @return 简单的缓存
*/
public static <V> SingleCache<V> build(long refreshDuration, Supplier<V> supplier) {
var cache = new SingleCache<V>();
cache.refreshDuration = refreshDuration;
cache.supplier = supplier;
cache.cache = supplier.get();
cache.refreshTime = TimeUtils.now() + refreshDuration;
return cache;
}
public V get() {
var now = TimeUtils.now();
// 使用双重检测锁的方式
if (now > refreshTime) {
lock.lock();
try {
if (now > refreshTime) {
refreshTime = now + refreshDuration;
EventBus.asyncExecute().execute(new Runnable() {
@Override
public void run() {
cache = supplier.get();
}
});
}
} finally {
lock.unlock();
}
}
return cache;
}
}
@@ -0,0 +1 @@
http\://www.zfoo.com/schema/net=com.zfoo.net.schema.NamespaceHandler
Loaded 100 of 573 files, more files were not shown because too many files have changed in this diff. Show more