32 changed files with 527 additions and 407 deletions
@ -0,0 +1,16 @@
|
||||
package net.sopod.soim.common.constant; |
||||
|
||||
/** |
||||
* DubboConstant |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 15:26 |
||||
*/ |
||||
public interface DubboConstant { |
||||
|
||||
/** |
||||
* 服务调用上下文用户id |
||||
*/ |
||||
String CTX_UID = "uid"; |
||||
|
||||
} |
||||
@ -0,0 +1,11 @@
|
||||
package net.sopod.soim.common.util; |
||||
|
||||
/** |
||||
* Func |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 15:28 |
||||
*/ |
||||
public class Func { |
||||
|
||||
} |
||||
@ -1,26 +0,0 @@
|
||||
package net.sopod.soim.core.handler; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import net.sopod.soim.core.session.NetUser; |
||||
|
||||
/** |
||||
* ProtoMessageHandler |
||||
* <T extends MessageLite> |
||||
* |
||||
* @author tmy |
||||
* @date 2022-04-10 19:19 |
||||
*/ |
||||
public abstract class ProtoMessageHandler<T> { |
||||
|
||||
public final void exec(NetUser netUser, T msg) { |
||||
MessageLite res = handle(msg); |
||||
if (res != null) { |
||||
netUser.write(res); |
||||
} |
||||
} |
||||
|
||||
public abstract Class<T> type(); |
||||
|
||||
public abstract MessageLite handle(T msg); |
||||
|
||||
} |
||||
@ -0,0 +1,31 @@
|
||||
package net.sopod.soim.entry.config; |
||||
|
||||
import net.sopod.soim.common.constant.DubboConstant; |
||||
import org.apache.dubbo.rpc.*; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
/** |
||||
* DubboPreInvokeFilter |
||||
* 每次调用服务前设置上下文属性 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 08:55 |
||||
*/ |
||||
public class DubboPreInvokeFilter implements Filter { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DubboPreInvokeFilter.class); |
||||
|
||||
@Override |
||||
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { |
||||
String uid = MessageHandlerContext.getAttribute(DubboConstant.CTX_UID); |
||||
if (uid != null) { |
||||
RpcContext.getServiceContext().setAttachment(DubboConstant.CTX_UID, uid); |
||||
if (logger.isDebugEnabled()) { |
||||
logger.debug("pre invoke filter set uid: {}", uid); |
||||
} |
||||
} |
||||
return invoker.invoke(invocation); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,53 @@
|
||||
package net.sopod.soim.entry.config; |
||||
|
||||
import io.netty.util.concurrent.FastThreadLocal; |
||||
|
||||
import java.util.Map; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
|
||||
/** |
||||
* MsgHandlerContext |
||||
* |
||||
* 线程上下文属性设置工具 |
||||
* 这里主要用于 {@link net.sopod.soim.core.handler.MessageHandler} 与 dubbo RpcContext 上下文参数传递 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 22:42 |
||||
*/ |
||||
public class MessageHandlerContext { |
||||
|
||||
private static final FastThreadLocal<Map<String, String>> HOLDER = new FastThreadLocal<>(); |
||||
|
||||
public static void setAttribute(String key, String value) { |
||||
Map<String, String> attrMap; |
||||
if ((attrMap = HOLDER.get()) == null) { |
||||
synchronized (Thread.currentThread()) { |
||||
if ((attrMap = HOLDER.get()) == null) { |
||||
attrMap = new ConcurrentHashMap<>(3); |
||||
HOLDER.set(attrMap); |
||||
} |
||||
} |
||||
} |
||||
attrMap.put(key, value); |
||||
} |
||||
|
||||
public static String getAttribute(String key) { |
||||
Map<String, String> attrMap; |
||||
if ((attrMap = HOLDER.get()) != null) { |
||||
return attrMap.get(key); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static void remove() { |
||||
HOLDER.remove(); |
||||
} |
||||
|
||||
public static void removeAttribute(String key) { |
||||
Map<String, String> attrMap; |
||||
if ((attrMap = HOLDER.get()) != null) { |
||||
attrMap.remove(key); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,11 +1,14 @@
|
||||
package net.sopod.soim.core.handler; |
||||
package net.sopod.soim.entry.handler; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import net.sopod.soim.core.handler.MessageHandler; |
||||
import net.sopod.soim.core.session.Account; |
||||
import net.sopod.soim.core.session.NetUser; |
||||
import org.apache.dubbo.rpc.RpcContext; |
||||
|
||||
/** |
||||
* AccountMessageHandler |
||||
* 每次调用服务前 |
||||
* |
||||
* @author tmy |
||||
* @date 2022-04-10 23:41 |
||||
@ -1,6 +1,7 @@
|
||||
package net.sopod.soim.core.handler; |
||||
package net.sopod.soim.entry.handler; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import net.sopod.soim.core.handler.MessageHandler; |
||||
import net.sopod.soim.core.session.NetUser; |
||||
|
||||
/** |
||||
@ -1,12 +1,10 @@
|
||||
package net.sopod.soim.entry.handler; |
||||
package net.sopod.soim.entry.handler.user; |
||||
|
||||
import com.google.protobuf.MessageLite; |
||||
import net.sopod.soim.core.handler.AccountMessageHandler; |
||||
import net.sopod.soim.core.handler.NetUserMessageHandler; |
||||
import net.sopod.soim.core.session.Account; |
||||
import net.sopod.soim.core.session.NetUser; |
||||
import net.sopod.soim.data.msg.hello.HelloPB; |
||||
import net.sopod.soim.entry.delay.NetUserDelayTaskManager; |
||||
import net.sopod.soim.entry.handler.AccountMessageHandler; |
||||
import net.sopod.soim.logic.api.segmentid.core.SegmentIdGenerator; |
||||
import net.sopod.soim.logic.user.service.UserService; |
||||
import org.apache.dubbo.config.annotation.DubboReference; |
||||
@ -0,0 +1 @@
|
||||
pre_invoke_filter=net.sopod.soim.entry.config.DubboPreInvokeFilter |
||||
@ -1,26 +0,0 @@
|
||||
package net.sopod.soim.router.api.route; |
||||
|
||||
import org.apache.dubbo.common.URL; |
||||
import org.apache.dubbo.rpc.Invocation; |
||||
import org.apache.dubbo.rpc.Invoker; |
||||
import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* ConsistentHashRoute |
||||
* 一致性 hash 服务路由 |
||||
* |
||||
* |
||||
* @author tmy |
||||
* @date 2022-04-29 14:41 |
||||
*/ |
||||
public class ConsistentHashRoute extends AbstractLoadBalance { |
||||
|
||||
@Override |
||||
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { |
||||
Invoker<T> invoker = invokers.get(0); |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,100 @@
|
||||
package net.sopod.soim.router.api.route; |
||||
|
||||
import net.sopod.soim.common.util.HashAlgorithms; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.TreeMap; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
/** |
||||
* ConsistentHashTest |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 15:33 |
||||
*/ |
||||
public class ConsistentHashTest { |
||||
|
||||
private static final int VIRTUAL_NODE_SIZE = 120; |
||||
|
||||
private TreeMap<Long, String> virtualInvokers = new TreeMap<>(); |
||||
|
||||
public void add(String key, String value) { |
||||
// 增加虚拟节点n个
|
||||
for (int i = 0; i < VIRTUAL_NODE_SIZE / 4; i++) { |
||||
for (int h = 0; h < 4; h++) { |
||||
long virHash = hash(key + i); |
||||
virtualInvokers.put(virHash, value); |
||||
} |
||||
// long virHash = hash("vir-" + key + i);
|
||||
// virtualInvokers.put(virHash, value);
|
||||
} |
||||
// virtualInvokers.put(hash(key), value);
|
||||
} |
||||
|
||||
public String getFirstNode(String value) { |
||||
long hash = hash(value); |
||||
Map.Entry<Long, String> entry = virtualInvokers.ceilingEntry(hash); |
||||
if (entry != null) { |
||||
return entry.getValue(); |
||||
} |
||||
if (virtualInvokers.size() == 0) { |
||||
throw new IllegalStateException("server is not available"); |
||||
} |
||||
return virtualInvokers.firstEntry().getValue(); |
||||
} |
||||
|
||||
/** |
||||
* hash 运算 |
||||
*/ |
||||
public static long hash(String value) { |
||||
return HashAlgorithms.md5Hash(value); |
||||
} |
||||
|
||||
public static long hash(String value, int number) { |
||||
return HashAlgorithms.md5Hash(value, number); |
||||
} |
||||
|
||||
private static void testConsistentHash() { |
||||
String[] nodes = { |
||||
"192.168.31.156:3032", |
||||
"192.168.31.156:3031", |
||||
// "192.168.1.101",
|
||||
// "192.168.1.102",
|
||||
// "192.168.1.103",
|
||||
// "192.168.1.104",
|
||||
// "192.168.1.105",
|
||||
}; |
||||
ConsistentHashTest route = new ConsistentHashTest(); |
||||
for (String node : nodes) { |
||||
route.add(node, node); |
||||
} |
||||
Map<String, AtomicInteger> counter = new HashMap<>(); |
||||
for (long i = 71000L; i < 72000L; i++) { |
||||
String node = route.getFirstNode(String.valueOf(i)); |
||||
AtomicInteger count = counter.computeIfAbsent(node, k -> new AtomicInteger()); |
||||
count.incrementAndGet(); |
||||
} |
||||
System.out.println(route.virtualInvokers); |
||||
System.out.println(counter); |
||||
} |
||||
|
||||
private static void testTreeMap() { |
||||
TreeMap<Integer, String> map = new TreeMap<>(); |
||||
map.put(10, "沧海1"); |
||||
map.put(20, "沧海2"); |
||||
map.put(30, "沧海3"); |
||||
map.put(40, "沧海4"); |
||||
map.put(50, "沧海5"); |
||||
System.out.println(map); |
||||
System.out.println(map.ceilingKey(30)); |
||||
System.out.println(map.higherEntry(30)); |
||||
} |
||||
|
||||
public static void main(String[] args) { |
||||
// {192.168.1.103=32, 192.168.1.100=26, 192.168.1.101=19, 192.168.1.102=23}
|
||||
testConsistentHash(); |
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,95 @@
|
||||
package net.sopod.soim.router.api.route; |
||||
|
||||
import net.sopod.soim.common.constant.DubboConstant; |
||||
import net.sopod.soim.common.util.HashAlgorithms; |
||||
import org.apache.dubbo.common.URL; |
||||
import org.apache.dubbo.rpc.Invocation; |
||||
import org.apache.dubbo.rpc.Invoker; |
||||
import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.TreeMap; |
||||
|
||||
/** |
||||
* ConsistentHashRoute |
||||
* 一致性 hash 服务路由 |
||||
* |
||||
* {@link org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance} |
||||
* |
||||
* @author tmy |
||||
* @date 2022-04-29 14:41 |
||||
*/ |
||||
public class ImRouterConsistentHashRoute extends AbstractLoadBalance { |
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImRouterConsistentHashRoute.class); |
||||
|
||||
/** |
||||
* 一致性 hash 每个节点的虚拟节点数量 |
||||
*/ |
||||
private static final int VIRTUAL_NODE_SIZE = 120; |
||||
|
||||
private volatile ConsistentHashSelector<?> selector; |
||||
|
||||
/** |
||||
* 后续如有接口版本号,构建 map 每个方法版本,对应一个 Selector |
||||
*/ |
||||
@Override |
||||
@SuppressWarnings("unchecked") |
||||
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) { |
||||
int invokersHash = getInvokersHash(invokers); |
||||
logger.info("invokers hash: {}", invokersHash); |
||||
if (selector == null || selector.identityHashCode != invokersHash) { |
||||
selector = new ConsistentHashSelector<>(invokers, invokersHash); |
||||
} |
||||
return ((ConsistentHashSelector<T>)selector).select(invocation); |
||||
} |
||||
|
||||
private <T> int getInvokersHash(List<Invoker<T>> invokers) { |
||||
return invokers.hashCode(); |
||||
} |
||||
|
||||
static class ConsistentHashSelector<T> { |
||||
|
||||
private final TreeMap<Long, Invoker<T>> virtualInvokers; |
||||
|
||||
private final int identityHashCode; |
||||
|
||||
ConsistentHashSelector(List<Invoker<T>> invokers, int identityHashCode) { |
||||
this.identityHashCode = identityHashCode; |
||||
this.virtualInvokers = new TreeMap<>(); |
||||
|
||||
for (Invoker<T> invoker : invokers) { |
||||
String address = invoker.getUrl().getAddress(); |
||||
for (int i = 0, len = VIRTUAL_NODE_SIZE / 4; i < len; i++) { |
||||
for (int h = 0; h < 4; h++) { |
||||
long hash = hash(address + i, h); |
||||
this.virtualInvokers.put(hash, invoker); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static long hash(String value, int number) { |
||||
return HashAlgorithms.md5Hash(value, number); |
||||
} |
||||
|
||||
public Invoker<T> select(Invocation invocation) { |
||||
// 获取上下文 uid, 同 RpcContext
|
||||
String uid = invocation.getAttachment(DubboConstant.CTX_UID); |
||||
if (uid == null) { |
||||
throw new IllegalStateException("im-router consistent hash route, ctx uid can not be null!"); |
||||
} |
||||
long hash = hash(uid, 0); |
||||
Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash); |
||||
if (entry == null) { |
||||
entry = virtualInvokers.firstEntry(); |
||||
} |
||||
return entry.getValue(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1 @@
|
||||
im_route_consistent_hash=net.sopod.soim.router.api.route.ImRouterConsistentHashRoute |
||||
@ -0,0 +1,25 @@
|
||||
package net.sopod.soim.router.listener; |
||||
|
||||
import org.apache.dubbo.rpc.Exporter; |
||||
import org.apache.dubbo.rpc.ExporterListener; |
||||
import org.apache.dubbo.rpc.RpcException; |
||||
|
||||
/** |
||||
* ImRouterExportListener |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 16:31 |
||||
*/ |
||||
public class ImRouterExportListener implements ExporterListener { |
||||
|
||||
@Override |
||||
public void exported(Exporter<?> exporter) throws RpcException { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void unexported(Exporter<?> exporter) { |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,39 @@
|
||||
package net.sopod.soim.router.listener; |
||||
|
||||
import java.rmi.*; |
||||
import java.rmi.registry.Registry; |
||||
|
||||
/** |
||||
* ImRouterServiceRegistry |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 16:34 |
||||
*/ |
||||
public class ImRouterServiceRegistry implements Registry { |
||||
|
||||
@Override |
||||
public Remote lookup(String s) throws RemoteException, NotBoundException, AccessException { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public void bind(String s, Remote remote) throws RemoteException, AlreadyBoundException, AccessException { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void unbind(String s) throws RemoteException, NotBoundException, AccessException { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void rebind(String s, Remote remote) throws RemoteException, AccessException { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public String[] list() throws RemoteException, AccessException { |
||||
return new String[0]; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,19 @@
|
||||
package net.sopod.soim.router.util; |
||||
|
||||
import net.sopod.soim.common.constant.DubboConstant; |
||||
import org.apache.dubbo.rpc.RpcContext; |
||||
|
||||
/** |
||||
* SessionContext |
||||
* |
||||
* @author tmy |
||||
* @date 2022-05-01 23:08 |
||||
*/ |
||||
public class ServerContext { |
||||
|
||||
public static Long getContextUid() { |
||||
String uidStr = RpcContext.getServerContext().getAttachment(DubboConstant.CTX_UID); |
||||
return Long.valueOf(uidStr); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue