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 com.google.protobuf.MessageLite; |
||||||
|
import net.sopod.soim.core.handler.MessageHandler; |
||||||
import net.sopod.soim.core.session.Account; |
import net.sopod.soim.core.session.Account; |
||||||
import net.sopod.soim.core.session.NetUser; |
import net.sopod.soim.core.session.NetUser; |
||||||
|
import org.apache.dubbo.rpc.RpcContext; |
||||||
|
|
||||||
/** |
/** |
||||||
* AccountMessageHandler |
* AccountMessageHandler |
||||||
|
* 每次调用服务前 |
||||||
* |
* |
||||||
* @author tmy |
* @author tmy |
||||||
* @date 2022-04-10 23:41 |
* @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 com.google.protobuf.MessageLite; |
||||||
|
import net.sopod.soim.core.handler.MessageHandler; |
||||||
import net.sopod.soim.core.session.NetUser; |
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 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.Account; |
||||||
import net.sopod.soim.core.session.NetUser; |
|
||||||
import net.sopod.soim.data.msg.hello.HelloPB; |
import net.sopod.soim.data.msg.hello.HelloPB; |
||||||
import net.sopod.soim.entry.delay.NetUserDelayTaskManager; |
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.api.segmentid.core.SegmentIdGenerator; |
||||||
import net.sopod.soim.logic.user.service.UserService; |
import net.sopod.soim.logic.user.service.UserService; |
||||||
import org.apache.dubbo.config.annotation.DubboReference; |
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