├── Screen Shot 2016-07-16 at 12.20.51 AM.png ├── Screen Shot 2016-07-16 at 12.22.08 AM.png ├── dubbocopy-api ├── target │ └── classes │ │ └── qunar │ │ └── tc │ │ └── dubbocopy │ │ └── api │ │ ├── model │ │ ├── Group.class │ │ ├── Router.class │ │ └── Target.class │ │ └── service │ │ └── RouterService.class ├── src │ └── main │ │ └── java │ │ └── qunar │ │ └── tc │ │ └── dubbocopy │ │ └── api │ │ ├── service │ │ └── RouterService.java │ │ └── model │ │ ├── Target.java │ │ ├── Group.java │ │ └── Router.java ├── dubbocopy-api.iml └── pom.xml ├── dubbocopy-server ├── src │ ├── main │ │ ├── java │ │ │ └── qunar │ │ │ │ └── tc │ │ │ │ └── dubbocopy │ │ │ │ ├── util │ │ │ │ ├── KeyGenerator.java │ │ │ │ ├── Executors.java │ │ │ │ ├── Monitor.java │ │ │ │ ├── GlobalConfig.java │ │ │ │ └── PrefixMatcher.java │ │ │ │ ├── balance │ │ │ │ ├── LoadBalance.java │ │ │ │ └── RandomLoadBalance.java │ │ │ │ ├── request │ │ │ │ ├── RequestListener.java │ │ │ │ ├── Request.java │ │ │ │ ├── DubboRequestInfo.java │ │ │ │ └── RawByteBufRequest.java │ │ │ │ ├── conn │ │ │ │ ├── ConnectionPool.java │ │ │ │ ├── Connection.java │ │ │ │ ├── HttpConnectionPool.java │ │ │ │ ├── CachedConnectionPool.java │ │ │ │ └── NettyConnection.java │ │ │ │ ├── handler │ │ │ │ ├── Hessian2Constants.java │ │ │ │ ├── IdleCloseHandler.java │ │ │ │ ├── DiscardHandler.java │ │ │ │ ├── DispatchHandler.java │ │ │ │ ├── DubboDecodeHandler.java │ │ │ │ └── FullHttpRequestDecoder.java │ │ │ │ ├── router │ │ │ │ ├── RouterService.java │ │ │ │ ├── RouterServiceImpl.java │ │ │ │ ├── RouterSelector.java │ │ │ │ └── RouterUpdater.java │ │ │ │ └── server │ │ │ │ ├── DubboDispatchServer.java │ │ │ │ ├── ReuseNioEventLoopGroup.java │ │ │ │ ├── HttpDispatchServer.java │ │ │ │ └── AbstractDispatchServer.java │ │ ├── resources │ │ │ ├── qunar-app.properties │ │ │ ├── applicationContext.xml │ │ │ ├── applicationContext-task.xml │ │ │ ├── applicationContext-dubbo.xml │ │ │ ├── logback.xml │ │ │ ├── applicationContext-service.xml │ │ │ ├── applicationContext-qconfig.xml │ │ │ └── applicationContext-server.xml │ │ ├── resources.local │ │ │ ├── qconfig_test │ │ │ │ └── b_dubbocopy │ │ │ │ │ └── system.properties │ │ │ └── logback.xml │ │ ├── webapp │ │ │ └── WEB-INF │ │ │ │ ├── spring-servlet.xml │ │ │ │ └── web.xml │ │ └── resources.dev │ │ │ └── logback.xml │ └── test │ │ ├── resources │ │ └── applicationContext-consumer.xml │ │ └── java │ │ ├── DubboTest.java │ │ └── qunar │ │ └── tc │ │ └── dubbocopy │ │ └── test │ │ └── a │ │ └── RawByteBufClient.java ├── pom.xml └── dubbocopy-server.iml ├── dubbocopy.iml ├── README.md └── pom.xml /Screen Shot 2016-07-16 at 12.20.51 AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupeng330/dubbocopy/HEAD/Screen Shot 2016-07-16 at 12.20.51 AM.png -------------------------------------------------------------------------------- /Screen Shot 2016-07-16 at 12.22.08 AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupeng330/dubbocopy/HEAD/Screen Shot 2016-07-16 at 12.22.08 AM.png -------------------------------------------------------------------------------- /dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/model/Group.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupeng330/dubbocopy/HEAD/dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/model/Group.class -------------------------------------------------------------------------------- /dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/model/Router.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupeng330/dubbocopy/HEAD/dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/model/Router.class -------------------------------------------------------------------------------- /dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/model/Target.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupeng330/dubbocopy/HEAD/dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/model/Target.class -------------------------------------------------------------------------------- /dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/service/RouterService.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liupeng330/dubbocopy/HEAD/dubbocopy-api/target/classes/qunar/tc/dubbocopy/api/service/RouterService.class -------------------------------------------------------------------------------- /dubbocopy-api/src/main/java/qunar/tc/dubbocopy/api/service/RouterService.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.api.service; 2 | 3 | import qunar.tc.dubbocopy.api.model.Router; 4 | 5 | /** 6 | * @author song.xue created on 15/4/22 7 | * @version 1.0.0 8 | */ 9 | public interface RouterService { 10 | void setRouter(Router router); 11 | } 12 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/util/KeyGenerator.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.util; 2 | 3 | /** 4 | * 5 | * @author kelly.li 6 | * @date 2015-07-08 7 | */ 8 | public class KeyGenerator { 9 | 10 | public static String generateKey(String service, String method) { 11 | return service + "_" + method; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/balance/LoadBalance.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.balance; 2 | 3 | import qunar.tc.dubbocopy.api.model.Group; 4 | import qunar.tc.dubbocopy.api.model.Target; 5 | 6 | /** 7 | * 负载均衡 8 | * @author kelly.li 9 | * @date 2015-06-29 10 | */ 11 | public interface LoadBalance { 12 | 13 | Target select(Group group); 14 | } 15 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/qunar-app.properties: -------------------------------------------------------------------------------- 1 | #organization=tcdev 2 | #owner=[sen.chai, song.xue, zizhong.liu] 3 | #developer=[sen.chai, song.xue] 4 | #mailGroup=[tcdev@qunar.com] 5 | name=b_dubbocopy 6 | token=yTwIOisCH07zNu3JpxqRk10caoh0t88Gv/HEUgA2pULH8eBoCklopVadE2zbflaTQuS9sIMqKiN5sGwEU5MlMQuuk6Qrrl2ha3WFMVhY4N3VTGC2NVwSp9ShkdseU4XdsQfFR3y3KkC5tqWR1IPdramr2bYNoK8O8DbeJufc7ZM= 7 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/request/RequestListener.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.request; 2 | 3 | /** 4 | * @author kelly.li 5 | * @date 2015-06-30 6 | */ 7 | public interface RequestListener { 8 | 9 | void onConnected(); 10 | 11 | void onConnectFailed(Throwable e); 12 | 13 | void onUnWritable(); 14 | 15 | void onWriteSuccess(); 16 | 17 | void onWriteFailed(Throwable e); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/conn/ConnectionPool.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.conn; 2 | 3 | import qunar.tc.dubbocopy.api.model.Target; 4 | 5 | /** 6 | * @author kelly.li 7 | * @date 2015-06-30 8 | */ 9 | public interface ConnectionPool { 10 | 11 | Connection getConnection(Target target); 12 | 13 | void returnConnection(Connection connection); 14 | 15 | void removeConnection(Connection connection); 16 | } 17 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources.local/qconfig_test/b_dubbocopy/system.properties: -------------------------------------------------------------------------------- 1 | zk.address=zk.dev.corp.qunar.com:2181 2 | 3 | dubbocopy.server.writeBufferHighWaterMark=524288 4 | dubbocopy.server.writeBufferLowWaterMark=262144 5 | dubbocopy.server.readIdleTimeoutSeconds=60 6 | 7 | dubbocopy.client.connectTimeout=1000 8 | dubbocopy.client.idleTimeoutSeconds=60 9 | 10 | dubbocopy.cactusRouterUrl=http://l-cactus.tc.dev.cn6.qunar.com:8080/drainage/getAllInfo -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/conn/Connection.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.conn; 2 | 3 | import qunar.tc.dubbocopy.api.model.Target; 4 | import qunar.tc.dubbocopy.request.RawByteBufRequest; 5 | 6 | /** 7 | * @author kelly.li 8 | * @date 2015-06-30 9 | */ 10 | public interface Connection { 11 | 12 | public void send(final RawByteBufRequest request); 13 | 14 | void close(); 15 | 16 | String key(); 17 | 18 | Target target(); 19 | } 20 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/handler/Hessian2Constants.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.handler; 2 | 3 | /** 4 | * @author song.xue created on 15/4/23 5 | * @version 1.0.0 6 | */ 7 | public class Hessian2Constants { 8 | 9 | public static final int BC_STRING = 'S'; // final string 10 | public static final int BC_STRING_CHUNK = 'R'; // non-final string 11 | 12 | public static final int STRING_DIRECT_MAX = 0x1f; 13 | public static final int BC_STRING_SHORT = 0x30; 14 | public static final int STRING_SHORT_MAX = 0x3ff; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/balance/RandomLoadBalance.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.balance; 2 | 3 | import java.util.Random; 4 | 5 | import qunar.tc.dubbocopy.api.model.Group; 6 | import qunar.tc.dubbocopy.api.model.Target; 7 | 8 | /** 9 | * 负载均衡随机 10 | * @author kelly.li 11 | * @date 2015-06-29 12 | */ 13 | public class RandomLoadBalance implements LoadBalance { 14 | 15 | private final Random random = new Random(); 16 | 17 | public Target select(Group group) { 18 | int size = group.size(); 19 | int index = random.nextInt(size); 20 | return group.get(index); 21 | } 22 | } -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/router/RouterService.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.router; 2 | 3 | import qunar.tc.dubbocopy.api.model.Group; 4 | import qunar.tc.dubbocopy.api.model.Router; 5 | import qunar.tc.dubbocopy.request.DubboRequestInfo; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author song.xue created on 15/4/23 11 | * @version 1.0.0 12 | */ 13 | public interface RouterService extends qunar.tc.dubbocopy.api.service.RouterService { 14 | 15 | List selectGroups(DubboRequestInfo dubboRequestInfo); 16 | 17 | void refreshAllRouters(List routers); 18 | } 19 | -------------------------------------------------------------------------------- /dubbocopy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/server/DubboDispatchServer.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.server; 2 | 3 | import io.netty.channel.socket.SocketChannel; 4 | import qunar.tc.dubbocopy.handler.DubboDecodeHandler; 5 | 6 | 7 | 8 | /** 9 | * 10 | * @author kelly.li 11 | * @date 2015-07-16 12 | */ 13 | public class DubboDispatchServer extends AbstractDispatchServer { 14 | 15 | 16 | public DubboDispatchServer(int serverPort) { 17 | super(serverPort); 18 | } 19 | 20 | @Override 21 | public void initHandler(SocketChannel ch) throws Exception { 22 | ch.pipeline().addLast("dubboDecoder", new DubboDecodeHandler()); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/applicationContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/webapp/WEB-INF/spring-servlet.xml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/server/ReuseNioEventLoopGroup.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.server; 2 | 3 | import io.netty.channel.EventLoop; 4 | import io.netty.channel.nio.NioEventLoopGroup; 5 | import io.netty.util.concurrent.EventExecutor; 6 | 7 | import java.util.concurrent.ThreadFactory; 8 | 9 | /** 10 | * Created by zhaohui.yu 11 | * 4/27/15 12 | */ 13 | public class ReuseNioEventLoopGroup extends NioEventLoopGroup { 14 | private final EventLoop eventLoop; 15 | 16 | public ReuseNioEventLoopGroup(EventLoop eventLoop) { 17 | super(1); 18 | this.eventLoop = eventLoop; 19 | } 20 | 21 | @Override 22 | protected EventExecutor newChild(ThreadFactory threadFactory, Object... args) throws Exception { 23 | return this.eventLoop; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/applicationContext-task.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dubbocopy-api/dubbocopy-api.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### ***文中链接各位根据自己项目对应修改*** 2 | -------------------------------------------------------------------------------- 3 | #### **项目管理**: 4 | #### *sp*:(http://sp.corp.qunar.com/default.aspx)
5 | 6 | #### *jira*:(http://task.corp.qunar.com/browse/)
7 | -------------------------------------------------------------------------------- 8 | #### **发布相关**: 9 | #### *devbds*:(http://devbds.corp.qunar.com/jenkins/)
10 | #### *bds*:(http://bds.corp.qunar.com/jenkins/)
11 | -------------------------------------------------------------------------------- 12 | #### **质量管理**: 13 | #### *bugfree*:(http://svn.corp.qunar.com/bugfree)
14 | #### *case*:(http://bugfree.corp.qunar.com/bugfree/index.php/case)
15 | -------------------------------------------------------------------------------- 16 | #### **项目信息**: 17 | #### *wiki*:(http://wiki.corp.qunar.com/)
18 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/applicationContext-dubbo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/server/HttpDispatchServer.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.server; 2 | 3 | import io.netty.channel.socket.SocketChannel; 4 | import io.netty.handler.codec.http.HttpObjectAggregator; 5 | import io.netty.handler.codec.http.HttpRequestDecoder; 6 | import qunar.tc.dubbocopy.handler.FullHttpRequestDecoder; 7 | 8 | /** 9 | * @author kelly.li 10 | * @date 2015-07-16 11 | */ 12 | public class HttpDispatchServer extends AbstractDispatchServer { 13 | 14 | public HttpDispatchServer(int serverPort) { 15 | super(serverPort); 16 | } 17 | 18 | @Override 19 | public void initHandler(SocketChannel ch) throws Exception { 20 | ch.pipeline().addLast("httpDecoder", new HttpRequestDecoder()); 21 | ch.pipeline().addLast("httpObjectAggregator", new HttpObjectAggregator(65536)); 22 | ch.pipeline().addLast("fullHttpEncoder", new FullHttpRequestDecoder()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /dubbocopy-server/src/test/resources/applicationContext-consumer.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [%d{HH:mm:ss} [%thread] %-5level %logger{36}] - %msg%n 8 | 9 | 10 | 11 | 12 | 10000 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/request/Request.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.request; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.util.ReferenceCountUtil; 5 | 6 | /** 7 | * Created by zhaohui.yu 8 | * 4/28/15 9 | */ 10 | public class Request { 11 | private final String serviceName; 12 | 13 | private final String methodName; 14 | 15 | private final ByteBuf rawData; 16 | 17 | public Request(String serviceName, String methodName, ByteBuf rawData) { 18 | this.serviceName = serviceName; 19 | this.methodName = methodName; 20 | this.rawData = rawData; 21 | this.rawData.retain(); 22 | } 23 | 24 | public String getServiceName() { 25 | return serviceName; 26 | } 27 | 28 | public String getMethodName() { 29 | return methodName; 30 | } 31 | 32 | public ByteBuf getRawData() { 33 | return rawData; 34 | } 35 | 36 | public void release() { 37 | ReferenceCountUtil.safeRelease(this.rawData); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /dubbocopy-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | dubbocopy 6 | qunar.tc.dubbocopy 7 | 1.0.3 8 | 9 | 4.0.0 10 | 11 | dubbocopy-api 12 | 13 | 14 | false 15 | 16 | 17 | 1.8 18 | 1.8 19 | 20 | 21 | 22 | org.apache.commons 23 | commons-lang3 24 | 3.4 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/handler/IdleCloseHandler.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.handler; 2 | 3 | import io.netty.channel.ChannelDuplexHandler; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.timeout.IdleStateEvent; 7 | 8 | /** 9 | * @author song.xue created on 15/4/27 10 | * @version 1.0.0 11 | */ 12 | @ChannelHandler.Sharable 13 | public class IdleCloseHandler extends ChannelDuplexHandler { 14 | @Override 15 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 16 | if (evt instanceof IdleStateEvent) { 17 | ctx.channel().close(); 18 | } else { 19 | super.userEventTriggered(ctx, evt); 20 | } 21 | } 22 | 23 | @Override 24 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 25 | super.channelInactive(ctx); 26 | } 27 | 28 | @Override 29 | public boolean isSharable() { 30 | return true; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/applicationContext-service.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/util/Executors.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.util; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import io.netty.channel.nio.NioEventLoopGroup; 5 | 6 | /** 7 | * @author song.xue created on 15/4/21 8 | * @version 1.0.0 9 | */ 10 | public class Executors { 11 | 12 | public static final int DEFAULT_THREAD_NUM = Runtime.getRuntime().availableProcessors() * 2; 13 | 14 | public static final NioEventLoopGroup bossGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setNameFormat("dc-boss-%s").build()); 15 | 16 | public static final NioEventLoopGroup workerGroup = new NioEventLoopGroup(Executors.DEFAULT_THREAD_NUM, new ThreadFactoryBuilder().setNameFormat("dc-worker-%s").build()); 17 | 18 | public static void shutdownAll() { 19 | if (!bossGroup.isShutdown()) { 20 | bossGroup.shutdownGracefully().awaitUninterruptibly(); 21 | } 22 | if (!workerGroup.isShutdown()) { 23 | workerGroup.shutdownGracefully().awaitUninterruptibly(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/applicationContext-qconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | system.properties 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /dubbocopy-server/src/test/java/DubboTest.java: -------------------------------------------------------------------------------- 1 | import com.google.common.collect.Sets; 2 | import org.junit.Test; 3 | import org.junit.runner.RunWith; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 6 | import qunar.tc.dubbocopy.api.model.Router; 7 | import qunar.tc.dubbocopy.api.model.Target; 8 | import qunar.tc.dubbocopy.api.service.RouterService; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author song.xue created on 15/4/24 14 | * @version 1.0.0 15 | */ 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @ContextConfiguration({"classpath:applicationContext-consumer.xml", "classpath:applicationContext-qconfig.xml"}) 18 | public class DubboTest { 19 | 20 | @Resource 21 | private RouterService routerService; 22 | 23 | @Test 24 | public void test() throws Exception { 25 | while (true) { 26 | 27 | routerService.setRouter(new Router("qunar.tc.api.CalcService", "add", Sets.newHashSet(new Target("127.0.0.1", 20881)))); 28 | Thread.sleep(50); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources.dev/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [%d{HH:mm:ss} [%thread] %-5level %logger{36}] - %msg%n 8 | 9 | 10 | 11 | 12 | 10000 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources.local/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | [%d{HH:mm:ss} [%thread] %-5level %logger{36}] - %msg%n 8 | 9 | 10 | 11 | 12 | 10000 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /dubbocopy-api/src/main/java/qunar/tc/dubbocopy/api/model/Target.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.api.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @author song.xue created on 15/4/22 7 | * @version 1.0.0 8 | */ 9 | public class Target implements Serializable { 10 | private final String host; 11 | private final int port; 12 | 13 | public Target(String host, int port) { 14 | this.host = host; 15 | this.port = port; 16 | } 17 | 18 | public String getHost() { 19 | return host; 20 | } 21 | 22 | public int getPort() { 23 | return port; 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) return true; 29 | if (o == null || getClass() != o.getClass()) return false; 30 | 31 | Target target = (Target) o; 32 | 33 | if (port != target.port) return false; 34 | if (host != null ? !host.equals(target.host) : target.host != null) return false; 35 | 36 | return true; 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | int result = host != null ? host.hashCode() : 0; 42 | result = 31 * result + port; 43 | return result; 44 | } 45 | 46 | @Override 47 | public String toString() { 48 | return "Target{" + 49 | "host='" + host + '\'' + 50 | ", port=" + port + 51 | '}'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/resources/applicationContext-server.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | qunar.ServletWatcher 9 | 10 | 11 | 12 | contextConfigLocation 13 | classpath:applicationContext.xml 14 | 15 | 16 | 17 | org.springframework.web.context.ContextLoaderListener 18 | 19 | 20 | 21 | characterEncodingFilter 22 | org.springframework.web.filter.CharacterEncodingFilter 23 | 24 | encoding 25 | UTF-8 26 | 27 | 28 | 29 | 30 | characterEncodingFilter 31 | /* 32 | 33 | 34 | 35 | watcher 36 | qunar.ServletWatcher 37 | 38 | 39 | watcher 40 | /* 41 | 42 | 43 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/handler/DiscardHandler.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.handler; 2 | 3 | import io.netty.channel.ChannelHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import io.netty.util.ReferenceCountUtil; 7 | import qunar.tc.dubbocopy.conn.Connection; 8 | import qunar.tc.dubbocopy.conn.ConnectionPool; 9 | import qunar.tc.dubbocopy.conn.NettyConnection; 10 | 11 | @ChannelHandler.Sharable 12 | public class DiscardHandler extends ChannelInboundHandlerAdapter { 13 | 14 | private final ConnectionPool connectionPool; 15 | 16 | public DiscardHandler(ConnectionPool connectionPool) { 17 | this.connectionPool = connectionPool; 18 | } 19 | 20 | @Override 21 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 22 | Object conn = ctx.channel().attr(NettyConnection.connKey).get(); 23 | if (conn == null) return; 24 | connectionPool.removeConnection((Connection) conn); 25 | } 26 | 27 | // 安全释放消息 28 | @Override 29 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 30 | ReferenceCountUtil.safeRelease(msg); 31 | } 32 | 33 | @Override 34 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 35 | super.channelReadComplete(ctx); 36 | Object conn = ctx.channel().attr(NettyConnection.connKey).get(); 37 | if (conn == null) return; 38 | connectionPool.returnConnection((Connection) conn); 39 | } 40 | 41 | @Override 42 | public boolean isSharable() { 43 | return true; 44 | } 45 | } -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/request/DubboRequestInfo.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.request; 2 | 3 | 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | /** 7 | * @author song.xue created on 15/4/22 8 | * @version 1.0.0 9 | */ 10 | public class DubboRequestInfo { 11 | 12 | private final String serviceName; 13 | private final String methodName; 14 | 15 | public DubboRequestInfo(String serviceName, String methodName) { 16 | this.serviceName = serviceName; 17 | this.methodName = methodName; 18 | } 19 | 20 | public String getKey() { 21 | return serviceName + (StringUtils.isEmpty(methodName) ? "" : "_" + methodName); 22 | } 23 | 24 | public String getServiceName() { 25 | return serviceName; 26 | } 27 | 28 | public String getMethodName() { 29 | return methodName; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) 35 | return true; 36 | if (o == null || getClass() != o.getClass()) 37 | return false; 38 | 39 | DubboRequestInfo that = (DubboRequestInfo) o; 40 | 41 | if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) 42 | return false; 43 | if (serviceName != null ? !serviceName.equals(that.serviceName) : that.serviceName != null) 44 | return false; 45 | 46 | return true; 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int result = serviceName != null ? serviceName.hashCode() : 0; 52 | result = 31 * result + (methodName != null ? methodName.hashCode() : 0); 53 | return result; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "DubboRequestInfo{" + "serviceName='" + serviceName + '\'' + ", methodName='" + methodName + '\'' + '}'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/conn/HttpConnectionPool.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.conn; 2 | 3 | import com.google.common.collect.Maps; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import qunar.tc.dubbocopy.api.model.Target; 7 | import qunar.tc.dubbocopy.handler.DiscardHandler; 8 | import qunar.tc.dubbocopy.handler.IdleCloseHandler; 9 | 10 | import java.util.concurrent.ConcurrentMap; 11 | 12 | /** 13 | * Created by zhaohui.yu 14 | * 15/8/10 15 | */ 16 | public class HttpConnectionPool implements ConnectionPool { 17 | 18 | @Value("${dubbocopy.client.connectTimeout}") 19 | private int connectionTimeoutMs; 20 | 21 | @Value("${dubbocopy.client.idleTimeoutSeconds}") 22 | private int idleTimeoutSeconds; 23 | 24 | @Autowired 25 | private IdleCloseHandler idleCloseHandler; 26 | 27 | private final DiscardHandler discardHandler; 28 | 29 | private final ConcurrentMap used; 30 | 31 | public HttpConnectionPool() { 32 | this.used = Maps.newConcurrentMap(); 33 | this.discardHandler = new DiscardHandler(this); 34 | } 35 | 36 | public Connection getConnection(Target target) { 37 | NettyConnection connection = new NettyConnection(target, connectionTimeoutMs, idleTimeoutSeconds, discardHandler, idleCloseHandler); 38 | used.putIfAbsent(connection.key(), connection); 39 | return connection; 40 | } 41 | 42 | public void returnConnection(Connection connection) { 43 | Connection usedConn = used.remove(connection.key()); 44 | if (usedConn == null) return; 45 | usedConn.close(); 46 | } 47 | 48 | public void removeConnection(Connection connection) { 49 | returnConnection(connection); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/util/Monitor.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.util; 2 | 3 | import qunar.metrics.Counter; 4 | import qunar.metrics.Meter; 5 | import qunar.metrics.Metrics; 6 | 7 | /** 8 | * @author song.xue created on 15/4/25 9 | * @version 1.0.0 10 | */ 11 | public class Monitor { 12 | 13 | public final static Meter serverChannelRead = Metrics.meter("tc.dubbocopy.server.serverChannelRead").get(); 14 | public final static Meter requestDecoded = Metrics.meter("tc.dubbocopy.server.reqestDecoded").get(); 15 | public final static Meter notDubboRequest = Metrics.meter("tc.dubbocopy.server.notDubboRequest").tag("cause", "notRequest").get(); 16 | public final static Meter notDubboMagic = Metrics.meter("tc.dubbocopy.server.notDubboRequest").tag("cause", "notDubboMagic").get(); 17 | public final static Meter requestSent = Metrics.meter("tc.dubbocopy.server.requestSent").get(); 18 | public final static Meter requestSuccess = Metrics.meter("tc.dubbocopy.server.requestResult").tag("result", "success").get(); 19 | public final static Meter requestFail = Metrics.meter("tc.dubbocopy.server.requestResult").tag("result", "fail").get(); 20 | public final static Meter connectSuccess = Metrics.meter("tc.dubbocopy.server.connectResult").tag("result", "success").get(); 21 | public final static Meter connectFail = Metrics.meter("tc.dubbocopy.server.connecttResult").tag("result", "fail").get(); 22 | public final static Meter createNewConnection = Metrics.meter("tc.dubbocopy.server.connectUse").tag("type", "new").get(); 23 | public final static Meter useOldConnection = Metrics.meter("tc.dubbocopy.server.connectUse").tag("type", "cached").get(); 24 | public final static String REQUEST_NO_ROUTER = "tc.dubbocopy.server.requestNoRouter"; 25 | public final static Meter routerChanged = Metrics.meter("tc.dubbocopy.server.routerChanged").get(); 26 | public final static Counter sendBufferOverflow = Metrics.counter("tc.dubbocopy.server.sendBufferOverflow").get(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /dubbocopy-api/src/main/java/qunar/tc/dubbocopy/api/model/Group.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.api.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.Iterator; 5 | import java.util.Set; 6 | import java.util.concurrent.CopyOnWriteArraySet; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | /** 10 | * 11 | * @author kelly.li 12 | * @date 2015-06-29 13 | */ 14 | public class Group implements Iterable, Serializable { 15 | 16 | private final String name; 17 | 18 | private int n; 19 | 20 | private final AtomicInteger index = new AtomicInteger(0); 21 | 22 | private CopyOnWriteArraySet targets = new CopyOnWriteArraySet(); 23 | 24 | public Group(String name) { 25 | this.name = name; 26 | this.n = 1; 27 | } 28 | 29 | public Group(String name, int n) { 30 | this.name = name; 31 | this.n = n; 32 | } 33 | 34 | public void add(Target target) { 35 | targets.add(target); 36 | } 37 | 38 | public void addAll(Set targets) { 39 | this.targets.addAll(targets); 40 | } 41 | 42 | public void remove(Target target) { 43 | targets.remove(target); 44 | } 45 | 46 | public int size() { 47 | return targets.size(); 48 | } 49 | 50 | public void setN(int n) { 51 | this.n = n; 52 | } 53 | 54 | public int getN() { 55 | return n; 56 | } 57 | 58 | public void setTargets(CopyOnWriteArraySet targets) { 59 | this.targets = targets; 60 | } 61 | 62 | public CopyOnWriteArraySet getTargets() { 63 | return targets; 64 | } 65 | 66 | public Iterator iterator() { 67 | return targets.iterator(); 68 | } 69 | 70 | public Target get(int index) { 71 | int i = 0; 72 | for (Target target : targets) { 73 | if (i++ == index) 74 | return target; 75 | } 76 | return null; 77 | } 78 | 79 | public String getName() { 80 | return name; 81 | } 82 | 83 | public int nextAndGet() { 84 | return index.incrementAndGet(); 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return "Group{" + "name='" + name + '\'' + "n=" + n + ", targets=" + targets + '}'; 90 | 91 | } 92 | } -------------------------------------------------------------------------------- /dubbocopy-api/src/main/java/qunar/tc/dubbocopy/api/model/Router.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.api.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author song.xue created on 15/4/22 8 | * @version 1.0.0 9 | */ 10 | public class Router implements Serializable { 11 | private static final long serialVersionUID = 3162565165149998433L; 12 | private String serviceName; 13 | private String methodName; 14 | private Set targets; 15 | private int n = 1; 16 | private Set groups; 17 | 18 | public Router() { 19 | } 20 | 21 | public Router(String serviceName, String methodName, Set targets) { 22 | this.serviceName = serviceName; 23 | this.methodName = methodName; 24 | this.targets = targets; 25 | } 26 | 27 | public Router(String serviceName, String methodName, int n, Set groups) { 28 | this.serviceName = serviceName; 29 | this.methodName = methodName; 30 | this.n = n; 31 | this.groups = groups; 32 | for (Group group : groups) { 33 | group.setN(n); 34 | } 35 | } 36 | 37 | public String getKey() { 38 | return serviceName + (methodName == null || methodName.trim().length() == 0 ? "" : "_" + methodName); 39 | } 40 | 41 | public String getServiceName() { 42 | return serviceName; 43 | } 44 | 45 | public void setServiceName(String serviceName) { 46 | this.serviceName = serviceName; 47 | } 48 | 49 | public String getMethodName() { 50 | return methodName; 51 | } 52 | 53 | public void setMethodName(String methodName) { 54 | this.methodName = methodName; 55 | } 56 | 57 | public Set getTargets() { 58 | return targets; 59 | } 60 | 61 | public void setTargets(Set targets) { 62 | this.targets = targets; 63 | } 64 | 65 | public Set getGroups() { 66 | return groups; 67 | } 68 | 69 | public void setGroups(Set groups) { 70 | this.groups = groups; 71 | } 72 | 73 | public int getN() { 74 | return n; 75 | } 76 | 77 | public void setN(int n) { 78 | this.n = n; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "Router{" + "serviceName='" + serviceName + '\'' + ", methodName='" + methodName + '\'' + ", n=" + n + ", groups=" + groups + '\'' + ", targets=" + targets + '}'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/router/RouterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.router; 2 | 3 | import java.io.IOException; 4 | import java.util.Collection; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.ExecutionException; 8 | 9 | import javax.annotation.PostConstruct; 10 | import javax.annotation.Resource; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import qunar.metrics.Metrics; 16 | import qunar.tc.dubbocopy.api.model.Group; 17 | import qunar.tc.dubbocopy.api.model.Router; 18 | import qunar.tc.dubbocopy.request.DubboRequestInfo; 19 | import qunar.tc.dubbocopy.util.Monitor; 20 | 21 | import com.google.common.collect.Lists; 22 | 23 | /** 24 | * @author song.xue created on 15/4/23 25 | * @version 1.0.0 26 | */ 27 | public class RouterServiceImpl implements RouterService { 28 | private static final Logger LOGGER = LoggerFactory.getLogger(RouterServiceImpl.class); 29 | 30 | @Resource 31 | private RouterUpdater routerUpdater; 32 | @Resource 33 | private RouterSelector reouterSelector; 34 | 35 | @Override 36 | public void refreshAllRouters(List routers) { 37 | LOGGER.info("refreshAllRouters"); 38 | if (routers == null || routers.isEmpty()) { 39 | return; 40 | } 41 | for (Router router : routers) { 42 | setRouter(router); 43 | } 44 | } 45 | 46 | public void setRouter(Router router) { 47 | String key = router.getKey(); 48 | if (router.getGroups() == null || router.getGroups().isEmpty()) { 49 | reouterSelector.removeService(key); 50 | LOGGER.info("remove router {}", router); 51 | } else { 52 | reouterSelector.addRouter(router); 53 | LOGGER.info("add router {}", router); 54 | } 55 | Monitor.routerChanged.mark(); 56 | } 57 | 58 | @PostConstruct 59 | public void queryAllRoutersFromCactus() { 60 | routerUpdater.updateAll(); 61 | } 62 | 63 | public List selectGroups(DubboRequestInfo requestInfo) { 64 | Map> groupMap = reouterSelector.select(requestInfo.getServiceName(), requestInfo.getMethodName()); 65 | List list = Lists.newArrayList(); 66 | for (Map.Entry> entry : groupMap.entrySet()) { 67 | for (Group group : entry.getValue()) { 68 | if (group.size() == 0) { 69 | LOGGER.info("没有router,不发送请求 {}", requestInfo); 70 | Metrics.meter(Monitor.REQUEST_NO_ROUTER).tag("service", requestInfo.getServiceName()).get().mark(); 71 | } else { 72 | list.add(group); 73 | } 74 | } 75 | } 76 | return list; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/router/RouterSelector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Qunar.com. All Rights Reserved. 3 | */ 4 | package qunar.tc.dubbocopy.router; 5 | 6 | import java.util.Collection; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.ConcurrentMap; 10 | 11 | import org.apache.commons.lang3.ObjectUtils; 12 | 13 | import qunar.tc.dubbocopy.api.model.Group; 14 | import qunar.tc.dubbocopy.api.model.Router; 15 | import qunar.tc.dubbocopy.api.model.Target; 16 | import qunar.tc.dubbocopy.util.KeyGenerator; 17 | import qunar.tc.dubbocopy.util.PrefixMatcher; 18 | 19 | import com.google.common.collect.Maps; 20 | import com.googlecode.concurrenttrees.common.KeyValuePair; 21 | import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory; 22 | 23 | /** 24 | * @author kelly.li 25 | * @date 2015-06-29 26 | */ 27 | public class RouterSelector { 28 | 29 | private final PrefixMatcher> tree = new PrefixMatcher>(new DefaultCharArrayNodeFactory()); 30 | 31 | public void addRouter(Router router) { 32 | String requestKey = router.getKey(); 33 | for (Group group : router.getGroups()) { 34 | addGroup(router.getKey(), group); 35 | for (Target target : group) { 36 | addTarget(requestKey, group, target); 37 | } 38 | } 39 | } 40 | 41 | private Group addGroup(String service, Group group) { 42 | String groupName = group.getName(); 43 | ConcurrentMap map = tree.getValueForExactKey(service); 44 | if (map == null) { 45 | map = new ConcurrentHashMap(); 46 | map = ObjectUtils.defaultIfNull(tree.putIfAbsent(service, map), map); 47 | } 48 | 49 | Group cachedGroup = map.get(groupName); 50 | if (cachedGroup == null) { 51 | cachedGroup = ObjectUtils.defaultIfNull(map.putIfAbsent(groupName, group), group); 52 | } 53 | return cachedGroup; 54 | } 55 | 56 | private void addTarget(String service, Group group, Target target) { 57 | group = addGroup(service, group); 58 | group.add(target); 59 | } 60 | 61 | public void removeService(String service) { 62 | tree.remove(service); 63 | } 64 | 65 | 66 | public Map> select(String service, String method) { 67 | Map> map = Maps.newHashMap(); 68 | String key = KeyGenerator.generateKey(service, method); 69 | for (KeyValuePair> entry : tree.getKeyValuePairsForKeysPrefixIn(key)) { 70 | String entryKey = entry.getKey().toString(); 71 | if (entryKey.equals(service) || entryKey.equals(key)) { 72 | map.put(entryKey, entry.getValue().values()); 73 | } 74 | } 75 | return map; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | qunar.tc.dubbocopy 7 | dubbocopy 8 | 1.0.3 9 | 10 | pom 11 | 12 | 13 | dubbocopy-server 14 | dubbocopy-api 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 8.2.3 25 | 26 | 27 | 28 | 29 | 30 | 31 | qunar.tc.dubbocopy 32 | dubbocopy-api 33 | ${project.version} 34 | 35 | 36 | 37 | io.netty 38 | netty-all 39 | 4.0.24.Final 40 | 41 | 42 | 43 | io.netty 44 | netty 45 | 3.9.5.Final 46 | 47 | 48 | 49 | qunar.tc.qconfig 50 | qconfig-client 51 | 1.1.2 52 | 53 | 54 | 55 | com.google.code.gson 56 | gson 57 | 2.2.4 58 | 59 | 60 | zookeeper 61 | org.apache.zookeeper 62 | 3.4.5 63 | runtime 64 | 65 | 66 | slf4j-log4j12 67 | org.slf4j 68 | 69 | 70 | log4j 71 | log4j 72 | 73 | 74 | netty 75 | org.jboss.netty 76 | 77 | 78 | junit 79 | junit 80 | 81 | 82 | 83 | 84 | 85 | com.googlecode.concurrent-trees 86 | concurrent-trees 87 | 1.0.0 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.apache.maven.plugins 97 | maven-compiler-plugin 98 | 99 | 1.8 100 | 1.8 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/router/RouterUpdater.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.router; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.gson.Gson; 5 | import com.ning.http.client.AsyncHttpClient; 6 | import com.ning.http.client.RequestBuilder; 7 | import com.ning.http.client.Response; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import qunar.tc.dubbocopy.api.model.Router; 11 | import qunar.tc.dubbocopy.util.GlobalConfig; 12 | 13 | import javax.annotation.Resource; 14 | import java.io.IOException; 15 | import java.util.List; 16 | 17 | /** 18 | * @author song.xue created on 15/4/28 19 | * @version 1.0.0 20 | */ 21 | public class RouterUpdater { 22 | 23 | private static final Logger LOGGER = LoggerFactory.getLogger(RouterUpdater.class); 24 | 25 | @Resource 26 | private AsyncHttpClient asyncHttpClient; 27 | 28 | @Resource 29 | private RouterService routerService; 30 | 31 | public void updateAll() { 32 | update(GlobalConfig.get("dubbocopy.cactusRouterUrl")); 33 | update(GlobalConfig.get("dubbocopy.appcenterRouterUrl")); 34 | } 35 | 36 | public void update(String url) { 37 | try { 38 | LOGGER.info("开始从 {} 拉取路由", url); 39 | RequestBuilder builder = new RequestBuilder("GET"); 40 | builder.setUrl(url); 41 | Response response = asyncHttpClient.executeRequest(builder.build()).get(); 42 | if (response.getStatusCode() != 200) { 43 | throw new IOException("从" + url + "抓取router失败"); 44 | } 45 | String responseBody = response.getResponseBody(); 46 | List routers = parseRouter(responseBody); 47 | routerService.refreshAllRouters(routers); 48 | LOGGER.info("拉取路由完成,共 {} 条路由", routers.size()); 49 | } catch (Exception e) { 50 | LOGGER.error("拉取路由失败", e); 51 | } 52 | } 53 | 54 | private List parseRouter(String responseBody) throws IOException { 55 | Gson gson = new Gson(); 56 | FetchRouterResult fetchRouterResult = gson.fromJson(responseBody, FetchRouterResult.class); 57 | if (fetchRouterResult.getStatus() != 0) { 58 | throw new IOException("路由接口返回错误,status = " + fetchRouterResult.getStatus() + ", message = " + fetchRouterResult.getMessage()); 59 | } 60 | if (fetchRouterResult.getData() == null) { 61 | return Lists.newArrayList(); 62 | } 63 | for (Router router : fetchRouterResult.getData()) { 64 | if (router.getMethodName() != null && router.getMethodName().equals("")) { 65 | router.setMethodName(null); 66 | } 67 | } 68 | return fetchRouterResult.getData(); 69 | } 70 | 71 | private static class FetchRouterResult { 72 | private int status; 73 | private String message; 74 | private List data; 75 | 76 | public int getStatus() { 77 | return status; 78 | } 79 | 80 | public void setStatus(int status) { 81 | this.status = status; 82 | } 83 | 84 | public String getMessage() { 85 | return message; 86 | } 87 | 88 | public void setMessage(String message) { 89 | this.message = message; 90 | } 91 | 92 | public List getData() { 93 | return data; 94 | } 95 | 96 | public void setData(List data) { 97 | this.data = data; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/util/GlobalConfig.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.util; 2 | 3 | import com.google.common.collect.Maps; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import qunar.tc.qconfig.client.Configuration; 7 | import qunar.tc.qconfig.client.MapConfig; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import java.util.concurrent.ConcurrentMap; 13 | 14 | /** 15 | * @author sen.chai 14-12-2 上午11:51 16 | */ 17 | public class GlobalConfig { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(GlobalConfig.class); 20 | 21 | private List configFiles; 22 | 23 | private boolean isFirstLoadConfig; 24 | 25 | public void setConfigFiles(List configFiles) { 26 | this.configFiles = configFiles; 27 | } 28 | 29 | private static final ConcurrentMap allConfig = Maps.newConcurrentMap(); 30 | 31 | public void init() { 32 | isFirstLoadConfig = true; 33 | logger.info("global config loading begin"); 34 | loadConfig(); 35 | logger.info("global config loading finish"); 36 | isFirstLoadConfig = false; 37 | } 38 | 39 | private void loadConfig() { 40 | Configuration.ConfigListener> mapConfigListener = new Configuration.ConfigListener>() { 41 | public void onLoad(Map conf) { 42 | logger.info("detected config change..."); 43 | // 应用启动时不执行 44 | if (!isFirstLoadConfig) { 45 | addConfig(conf); 46 | } 47 | } 48 | }; 49 | for (String fileName : configFiles) { 50 | MapConfig mapConfig = MapConfig.get(fileName); 51 | mapConfig.addListener(mapConfigListener); 52 | Map configMap = mapConfig.asMap(); 53 | addConfig(configMap); 54 | } 55 | } 56 | 57 | /** 58 | * Add global config 59 | * 60 | * @param configMap 61 | */ 62 | private void addConfig(Map configMap) { 63 | if (configMap == null) { 64 | return; 65 | } 66 | // 应用启动时进行检查 67 | if (isFirstLoadConfig) { 68 | checkConfig(configMap); 69 | } 70 | allConfig.putAll(configMap); 71 | } 72 | 73 | /** 74 | * 启动时进行检查 Duplicate key check 75 | * 76 | * @param configMap 77 | */ 78 | private void checkConfig(Map configMap) { 79 | Set configKeys = configMap.keySet(); 80 | for (String configKey : configKeys) { 81 | if (allConfig.containsKey(configKey)) { 82 | logger.error("配置中包含重复的key:{}", configKey); 83 | throw new RuntimeException("配置中包含重复的key:" + configKey); 84 | } 85 | } 86 | } 87 | 88 | public static String get(String key) { 89 | String value = allConfig.get(key); 90 | if (value != null) { 91 | return value; 92 | } 93 | logger.error("没有找到配置,key:{}", key); 94 | throw new RuntimeException("没有找到配置,key:" + key); 95 | } 96 | 97 | public static int getInt(String key) { 98 | return Integer.parseInt(get(key).trim()); 99 | } 100 | 101 | public static long getLong(String key) { 102 | return Long.parseLong(get(key).trim()); 103 | } 104 | 105 | public static boolean getBoolean(String key) { 106 | return Boolean.parseBoolean(get(key).trim()); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/conn/CachedConnectionPool.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.conn; 2 | 3 | import com.google.common.cache.*; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.DisposableBean; 7 | import org.springframework.beans.factory.InitializingBean; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import qunar.metrics.Gauge; 12 | import qunar.metrics.Metrics; 13 | import qunar.tc.dubbocopy.api.model.Target; 14 | import qunar.tc.dubbocopy.handler.DiscardHandler; 15 | import qunar.tc.dubbocopy.handler.IdleCloseHandler; 16 | 17 | import java.util.concurrent.ExecutionException; 18 | 19 | /** 20 | * @author kelly.li 21 | * @date 2015-06-30 22 | */ 23 | public class CachedConnectionPool implements ConnectionPool, InitializingBean, DisposableBean { 24 | private static final Logger LOGGER = LoggerFactory.getLogger(CachedConnectionPool.class); 25 | 26 | private LoadingCache POOL; 27 | 28 | @Value("${dubbocopy.client.connectTimeout}") 29 | private int connectionTimeoutMs; 30 | 31 | @Value("${dubbocopy.client.idleTimeoutSeconds}") 32 | private int idleTimeoutSeconds; 33 | 34 | @Autowired 35 | private IdleCloseHandler idleCloseHandler; 36 | 37 | private final DiscardHandler discardHandler; 38 | 39 | public CachedConnectionPool() { 40 | this.discardHandler = new DiscardHandler(this); 41 | } 42 | 43 | public void afterPropertiesSet() { 44 | POOL = CacheBuilder.newBuilder().removalListener(new RemovalListener() { 45 | public void onRemoval(RemovalNotification removedItem) { 46 | LOGGER.info("Remove connection {} from cache", removedItem.getKey()); 47 | Connection conn = removedItem.getValue(); 48 | if (conn != null) { 49 | conn.close(); 50 | } 51 | } 52 | }).build(new CacheLoader() { 53 | @Override 54 | public Connection load(Target target) { 55 | LOGGER.info("Build connection {} to cache", target); 56 | return new NettyConnection(target, connectionTimeoutMs, idleTimeoutSeconds, discardHandler, idleCloseHandler); 57 | } 58 | }); 59 | 60 | Metrics.gauge("target.client.connections").call(new Gauge() { 61 | public double getValue() { 62 | return POOL.size(); 63 | } 64 | }); 65 | 66 | } 67 | 68 | public Connection getConnection(Target target) { 69 | Connection connection = null; 70 | try { 71 | connection = POOL.get(target); 72 | } catch (ExecutionException e) { 73 | String errorMessage = "Get client connection failed"; 74 | LOGGER.error(errorMessage, e); 75 | throw new RuntimeException(errorMessage, e); 76 | } 77 | return connection; 78 | } 79 | 80 | public void returnConnection(Connection connection) { 81 | 82 | } 83 | 84 | public void removeConnection(Connection connection) { 85 | POOL.invalidate(connection.target()); 86 | } 87 | 88 | public void destroy() { 89 | POOL.invalidateAll(); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/request/RawByteBufRequest.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.request; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.util.ReferenceCountUtil; 8 | import qunar.tc.dubbocopy.api.model.Target; 9 | import qunar.tc.dubbocopy.util.Monitor; 10 | 11 | /** 12 | * @author song.xue created on 15/4/23 13 | * @version 1.0.0 14 | */ 15 | public class RawByteBufRequest implements RequestListener { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(RawByteBufRequest.class); 18 | 19 | public static class Builder { 20 | private String serviceName; 21 | private String methodName; 22 | private String groupName; 23 | private int n; 24 | private Target target; 25 | private ByteBuf byteBuf; 26 | 27 | public Builder(String serviceName, String methodName, String groupName, int n, Target target, ByteBuf byteBuf) { 28 | this.serviceName = serviceName; 29 | this.methodName = methodName; 30 | this.groupName = groupName; 31 | this.n = n; 32 | this.target = target; 33 | this.byteBuf = byteBuf; 34 | } 35 | 36 | 37 | 38 | public RawByteBufRequest build() { 39 | return new RawByteBufRequest(serviceName, methodName, groupName, n, target, byteBuf); 40 | } 41 | } 42 | 43 | private String serviceName; 44 | private String methodName; 45 | private String groupName; 46 | private int n; 47 | private Target target; 48 | private ByteBuf byteBuf; 49 | 50 | private RawByteBufRequest(String serviceName, String methodName, String groupName, int n, Target target, ByteBuf byteBuf) { 51 | this.serviceName = serviceName; 52 | this.methodName = methodName; 53 | this.groupName = groupName; 54 | this.n = n; 55 | this.target = target; 56 | this.byteBuf = byteBuf; 57 | } 58 | 59 | public Target getTarget() { 60 | return target; 61 | } 62 | 63 | public ByteBuf getByteBuf() { 64 | return byteBuf; 65 | } 66 | 67 | public String getServiceName() { 68 | return serviceName; 69 | } 70 | 71 | public String getMethodName() { 72 | return methodName; 73 | } 74 | 75 | public String getGroupName() { 76 | return groupName; 77 | } 78 | 79 | public int getN() { 80 | return n; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return "RawByteBufRequest{" + "serviceName=" + serviceName + "methodName=" + methodName + "groupName=" + groupName + "n=" + n + ", byteBuf=" + byteBuf + '}'; 86 | } 87 | 88 | @Override 89 | public void onConnected() { 90 | LOGGER.info("连接成功 {}.{} ->{}->{}:{}", serviceName, methodName, groupName, target.getHost(), target.getPort()); 91 | Monitor.connectSuccess.mark(); 92 | } 93 | 94 | @Override 95 | public void onConnectFailed(Throwable e) { 96 | release(); 97 | LOGGER.error("连接失败 {}.{} ->{}->{}:{}", serviceName, methodName, groupName, target.getHost(), target.getPort()); 98 | Monitor.connectFail.mark(); 99 | 100 | } 101 | 102 | @Override 103 | public void onUnWritable() { 104 | LOGGER.info("不可写 {}.{} ->{}->{}:{}", serviceName, methodName, groupName, target.getHost(), target.getPort()); 105 | release(); 106 | } 107 | 108 | @Override 109 | public void onWriteSuccess() { 110 | LOGGER.info("请求成功 {}.{} ->{}->{}:{}", serviceName, methodName, groupName, target.getHost(), target.getPort()); 111 | Monitor.requestSuccess.mark(); 112 | } 113 | 114 | @Override 115 | public void onWriteFailed(Throwable e) { 116 | LOGGER.error("请求失败 {}.{} ->{}->{}:{}", serviceName, methodName, groupName, target.getHost(), target.getPort(), e); 117 | Monitor.requestFail.mark(); 118 | } 119 | 120 | public void release() { 121 | ReferenceCountUtil.safeRelease(byteBuf); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/handler/DispatchHandler.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.handler; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandler; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import qunar.metrics.Metrics; 12 | import qunar.tc.dubbocopy.api.model.Group; 13 | import qunar.tc.dubbocopy.api.model.Target; 14 | import qunar.tc.dubbocopy.balance.LoadBalance; 15 | import qunar.tc.dubbocopy.conn.Connection; 16 | import qunar.tc.dubbocopy.conn.ConnectionPool; 17 | import qunar.tc.dubbocopy.request.DubboRequestInfo; 18 | import qunar.tc.dubbocopy.request.RawByteBufRequest; 19 | import qunar.tc.dubbocopy.request.Request; 20 | import qunar.tc.dubbocopy.router.RouterService; 21 | import qunar.tc.dubbocopy.util.Monitor; 22 | 23 | import java.util.List; 24 | 25 | /** 26 | * @author song.xue created on 15/4/23 27 | * @version 1.0.0 28 | */ 29 | 30 | /** 31 | * @author kelly.li 32 | */ 33 | @ChannelHandler.Sharable 34 | public class DispatchHandler extends ChannelInboundHandlerAdapter { 35 | private static final Logger LOGGER = LoggerFactory.getLogger(DispatchHandler.class); 36 | 37 | private static final ByteBuf BUF = Unpooled.unreleasableBuffer(Unpooled.directBuffer(1).writeByte(1)); 38 | 39 | @Autowired 40 | private RouterService routerService; 41 | 42 | @Autowired 43 | private LoadBalance loadBalance; 44 | 45 | private ConnectionPool connectionPool; 46 | 47 | @Override 48 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 49 | if (!(msg instanceof Request)) 50 | return; 51 | final Request request = (Request) msg; 52 | LOGGER.info("接收 {}.{} 的数据", request.getServiceName(), request.getMethodName()); 53 | final DubboRequestInfo requestInfo = new DubboRequestInfo(request.getServiceName(), request.getMethodName()); 54 | List groups = routerService.selectGroups(requestInfo); 55 | if (groups == null || groups.isEmpty()) { 56 | LOGGER.info("没有router,不发送请求 {}", requestInfo); 57 | Metrics.meter(Monitor.REQUEST_NO_ROUTER).tag("service", requestInfo.getServiceName()).get().mark(); 58 | request.release(); 59 | return; 60 | } 61 | LOGGER.info("接收 {}.{} 的数据,数据匹配{}个组", request.getServiceName(), request.getMethodName(), groups.size()); 62 | dispatch(groups, request); 63 | } 64 | 65 | private void dispatch(List groups, Request request) { 66 | for (Group group : groups) { 67 | for (int i = 0; i < group.getN(); i++) { 68 | Target target = loadBalance.select(group); 69 | RawByteBufRequest rawRequest = new RawByteBufRequest.Builder(request.getServiceName(), request.getMethodName(), group.getName(), group.getN(), target, 70 | request.getRawData().duplicate().retain()).build(); 71 | send(rawRequest); 72 | } 73 | } 74 | 75 | } 76 | 77 | private void send(final RawByteBufRequest rawRequest) { 78 | Monitor.requestSent.mark(); 79 | Connection conn = connectionPool.getConnection(rawRequest.getTarget()); 80 | conn.send(rawRequest); 81 | LOGGER.info("向{}发送数据", rawRequest.getTarget()); 82 | } 83 | 84 | @Override 85 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 86 | if (ctx.channel().isActive()) 87 | ctx.writeAndFlush(BUF.duplicate()); 88 | } 89 | 90 | @Override 91 | public boolean isSharable() { 92 | return true; 93 | } 94 | 95 | public void setConnectionPool(ConnectionPool connectionPool) { 96 | this.connectionPool = connectionPool; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /dubbocopy-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | dubbocopy 6 | qunar.tc.dubbocopy 7 | 1.0.3 8 | 9 | 4.0.0 10 | 11 | dubbocopy-server 12 | war 13 | 14 | 15 | true 16 | 17 | 18 | 1.8 19 | 1.8 20 | 21 | 22 | 23 | 24 | com.ning 25 | async-http-client 26 | 1.8.14 27 | 28 | 29 | 30 | com.googlecode.concurrent-trees 31 | concurrent-trees 32 | 33 | 34 | org.apache.commons 35 | commons-lang3 36 | 3.4 37 | 38 | 39 | 40 | qunar.tc.dubbocopy 41 | dubbocopy-api 42 | 1.0.3 43 | 44 | 45 | 46 | 47 | org.springframework 48 | spring-webmvc 49 | 3.2.4.RELEASE 50 | 51 | 52 | 53 | 54 | qunar.common 55 | common-core 56 | 8.2.3 57 | 58 | 59 | 60 | qunar.common 61 | common-rpc 62 | 8.2.3 63 | 64 | 65 | 66 | 67 | qunar.tc.qconfig 68 | qconfig-client 69 | 1.1.2 70 | 71 | 72 | 73 | qunar.tc.qtracer 74 | qtracer-common 75 | 1.0.7 76 | 77 | 78 | 79 | 80 | org.slf4j 81 | slf4j-api 82 | 1.7.21 83 | 84 | 85 | ch.qos.logback 86 | logback-classic 87 | 1.1.7 88 | 89 | 90 | 91 | org.slf4j 92 | jcl-over-slf4j 93 | 1.7.21 94 | 95 | 96 | 97 | 98 | io.netty 99 | netty-all 100 | 101 | 102 | 103 | com.google.guava 104 | guava 105 | 18.0 106 | 107 | 108 | 109 | 110 | com.google.code.gson 111 | gson 112 | 113 | 114 | 115 | 116 | junit 117 | junit-dep 118 | 4.10 119 | 120 | 121 | 122 | org.springframework 123 | spring-test 124 | 3.2.4.RELEASE 125 | 126 | 127 | 128 | 129 | 130 | ROOT 131 | 132 | 133 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/server/AbstractDispatchServer.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.server; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.buffer.PooledByteBufAllocator; 5 | import io.netty.channel.AdaptiveRecvByteBufAllocator; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelOption; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.timeout.IdleStateHandler; 12 | 13 | import javax.annotation.Resource; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.beans.factory.DisposableBean; 18 | import org.springframework.beans.factory.InitializingBean; 19 | import org.springframework.beans.factory.annotation.Value; 20 | 21 | import qunar.tc.dubbocopy.handler.DispatchHandler; 22 | import qunar.tc.dubbocopy.handler.IdleCloseHandler; 23 | import qunar.tc.dubbocopy.util.Executors; 24 | 25 | /** 26 | * @author song.xue created on 15/4/21 27 | * @version 1.0.0 28 | */ 29 | public abstract class AbstractDispatchServer implements InitializingBean, DisposableBean { 30 | private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDispatchServer.class); 31 | 32 | private Channel ch; 33 | 34 | private final int serverPort; 35 | 36 | private DispatchHandler dispatchHandler; 37 | 38 | @Value("${dubbocopy.server.writeBufferHighWaterMark}") 39 | private int writeBufferHighWaterMark; 40 | 41 | @Value("${dubbocopy.server.writeBufferLowWaterMark}") 42 | private int writeBufferLowWaterMark; 43 | 44 | @Value("${dubbocopy.server.readIdleTimeoutSeconds}") 45 | private int readerIdleTimeoutSeconds; 46 | 47 | public AbstractDispatchServer(int serverPort) { 48 | this.serverPort = serverPort; 49 | } 50 | 51 | public void afterPropertiesSet() throws Exception { 52 | final IdleCloseHandler idleCloseHandler = new IdleCloseHandler(); 53 | 54 | ServerBootstrap b = new ServerBootstrap(); 55 | //端口重启可重用 56 | b.option(ChannelOption.SO_REUSEADDR, true); 57 | b.option(ChannelOption.AUTO_READ, true); 58 | b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); 59 | b.childOption(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT); 60 | b.childOption(ChannelOption.TCP_NODELAY, true); 61 | b.childOption(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, writeBufferHighWaterMark); 62 | b.childOption(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, writeBufferLowWaterMark); 63 | b.group(Executors.bossGroup, Executors.workerGroup) 64 | .channel(NioServerSocketChannel.class) 65 | .childHandler(new ChannelInitializer() { 66 | @Override 67 | protected void initChannel(SocketChannel ch) throws Exception { 68 | ch.pipeline().addLast("idle", new IdleStateHandler(readerIdleTimeoutSeconds, 0, 0)); 69 | ch.pipeline().addLast("idleClose", idleCloseHandler); 70 | initHandler(ch); 71 | ch.pipeline().addLast("dispatch", dispatchHandler); 72 | 73 | } 74 | }); 75 | //绑定到一个30880监听连接 76 | ch = b.bind(serverPort).await().channel(); 77 | } 78 | 79 | public abstract void initHandler(SocketChannel ch) throws Exception; 80 | 81 | public void destroy() throws Exception { 82 | try { 83 | LOGGER.info("准备关闭Netty服务器, 端口 {}", serverPort); 84 | // 阻塞, 等待断开连接和关闭操作完成 85 | if (ch != null && ch.isActive()) { 86 | ch.close().awaitUninterruptibly(); 87 | } 88 | Executors.shutdownAll(); 89 | LOGGER.info("成功关闭Netty服务器, 端口 {}", serverPort); 90 | } catch (Exception e) { 91 | LOGGER.warn("关闭Netty服务器时出错, 端口 {}", serverPort, e); 92 | } 93 | } 94 | 95 | public void setDispatchHandler(DispatchHandler dispatchHandler) { 96 | this.dispatchHandler = dispatchHandler; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /dubbocopy-server/dubbocopy-server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | file://$MODULE_DIR$/src/main/webapp/WEB-INF/spring-servlet.xml 22 | file://$MODULE_DIR$/src/main/resources/applicationContext.xml 23 | file://$MODULE_DIR$/src/main/resources/applicationContext-task.xml 24 | file://$MODULE_DIR$/src/main/resources/applicationContext-service.xml 25 | file://$MODULE_DIR$/src/main/resources/applicationContext-server.xml 26 | file://$MODULE_DIR$/src/main/resources/applicationContext-dubbo.xml 27 | file://$MODULE_DIR$/src/main/resources/applicationContext-qconfig.xml 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/handler/DubboDecodeHandler.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.handler; 2 | 3 | import com.google.common.primitives.Ints; 4 | import com.google.common.primitives.Shorts; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.ByteToMessageDecoder; 8 | import qunar.tc.dubbocopy.request.Request; 9 | import qunar.tc.dubbocopy.util.Monitor; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @author song.xue created on 15/4/22 15 | * @version 1.0.0 16 | */ 17 | /* 18 | Dubbo字节码结构 19 | header 16 bytes 20 | magic 2bytes 固定是0xdabb 21 | 一些位标记 1byte 22 | 最高位 request为1,response为0 23 | 第2高位 貌似是保留位 24 | 第3高位 FLAG_TWOWAY 25 | 第4高位 EVENT为1,非EVENT为0 26 | 后4位 content type id,对于默认的Hessian2Serialization是2 27 | 1byte 再看是啥 28 | request id 8bytes 29 | datalength 4bytes 30 | data datalength bytes 31 | Hessian2String dubbo版本 32 | Hessian2String 服务名 33 | Hessian2String 服务版本 34 | Hessian2String 方法名 35 | 后面的暂时不用管 36 | */ 37 | public class DubboDecodeHandler extends ByteToMessageDecoder { 38 | 39 | private static final int DUBBO_HEADER_LEN = 16; 40 | 41 | protected static final byte FLAG_REQUEST = (byte) 0x80; 42 | 43 | protected static final byte FLAG_EVENT = (byte) 0x20; 44 | 45 | protected static final byte HESSIAN_SERILIZATION_ID = 2; 46 | 47 | @Override 48 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 49 | Monitor.serverChannelRead.mark(); 50 | if (in.readableBytes() < DUBBO_HEADER_LEN) { 51 | return; 52 | } 53 | in.markReaderIndex(); 54 | byte[] header = new byte[DUBBO_HEADER_LEN]; 55 | in.readBytes(header, 0, DUBBO_HEADER_LEN); 56 | int dataLen = getDataLenFromHeader(header); 57 | if (in.readableBytes() < dataLen) { 58 | in.resetReaderIndex(); 59 | return; 60 | } 61 | if (!isDubboMagicValid(header)) { 62 | ctx.channel().close(); 63 | Monitor.notDubboMagic.mark(); 64 | return; 65 | } 66 | if (!isHessian2RequestFlag(header[2])) { 67 | Monitor.notDubboRequest.mark(); 68 | in.skipBytes(dataLen); 69 | return; 70 | } 71 | Request request = parseRequestInfo(in, dataLen); 72 | out.add(request); 73 | Monitor.requestDecoded.mark(); 74 | } 75 | 76 | private Request parseRequestInfo(ByteBuf data, int dataLen) { 77 | readString(data, true);//skip dubbo version 78 | String serviceName = readString(data, false); 79 | readString(data, true);//skip service version 80 | String methodName = readString(data, false); 81 | data.resetReaderIndex(); 82 | ByteBuf rawData = data.readSlice(DUBBO_HEADER_LEN + dataLen); 83 | return new Request(serviceName, methodName, rawData); 84 | } 85 | 86 | private String readString(ByteBuf buf, boolean skip) { 87 | byte firstByte = buf.readByte(); 88 | return readStringByFirstByte(firstByte, buf, skip); 89 | } 90 | 91 | private String readStringByFirstByte(byte firstByte, ByteBuf buf, boolean skip) { 92 | if (Hessian2Constants.BC_STRING_CHUNK == firstByte) { 93 | return readStringChunks(buf, skip); 94 | } 95 | if (Hessian2Constants.BC_STRING == firstByte) { 96 | int len = unsignedShort(buf.readShort()); 97 | return readStringByLength(buf, len, skip); 98 | } 99 | if (firstByte > Hessian2Constants.STRING_DIRECT_MAX) { 100 | short len = Shorts.fromBytes((byte) (firstByte - Hessian2Constants.BC_STRING_SHORT), buf.readByte()); 101 | return readStringByLength(buf, len, skip); 102 | } 103 | return readStringByLength(buf, firstByte, skip); 104 | } 105 | 106 | private String readStringByLength(ByteBuf buf, int length, boolean skip) { 107 | if (skip) { 108 | buf.skipBytes(length); 109 | return null; 110 | } 111 | byte[] stringBuf = new byte[length]; 112 | buf.readBytes(stringBuf, 0, length); 113 | return new String(stringBuf); 114 | } 115 | 116 | private String readStringChunks(ByteBuf buf, boolean skip) { 117 | StringBuilder builder = skip ? null : new StringBuilder(); 118 | byte nextChunkFirstByte; 119 | do { 120 | int len = unsignedShort(buf.readShort()); 121 | if (skip) { 122 | readStringByLength(buf, len, true); 123 | } else { 124 | builder.append(readStringByLength(buf, len, false)); 125 | } 126 | nextChunkFirstByte = buf.readByte(); 127 | } while (nextChunkFirstByte == Hessian2Constants.BC_STRING_CHUNK); 128 | if (skip) { 129 | readStringByFirstByte(nextChunkFirstByte, buf, true); 130 | return null; 131 | } 132 | builder.append(readStringByFirstByte(nextChunkFirstByte, buf, false)); 133 | return builder.toString(); 134 | } 135 | 136 | private int unsignedShort(short val) { 137 | return val & 0xffff; 138 | } 139 | 140 | private boolean isHessian2RequestFlag(byte flag) { 141 | return ((flag & FLAG_REQUEST) != 0) && ((flag & FLAG_EVENT) == 0) && ((flag & (byte) 0x0f) == HESSIAN_SERILIZATION_ID); 142 | } 143 | 144 | private boolean isDubboMagicValid(byte[] header) { 145 | return header[0] == (byte) 0xda && header[1] == (byte) 0xbb; 146 | } 147 | 148 | private int getDataLenFromHeader(byte[] header) { 149 | return Ints.fromBytes(header[12], header[13], header[14], header[15]); 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/handler/FullHttpRequestDecoder.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.handler; 2 | 3 | import static io.netty.handler.codec.http.HttpConstants.COLON; 4 | import static io.netty.handler.codec.http.HttpConstants.CR; 5 | import static io.netty.handler.codec.http.HttpConstants.LF; 6 | import static io.netty.handler.codec.http.HttpConstants.SP; 7 | 8 | import java.net.InetSocketAddress; 9 | import java.util.List; 10 | import java.util.Map.Entry; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import qunar.tc.dubbocopy.request.Request; 16 | 17 | import io.netty.buffer.ByteBuf; 18 | import io.netty.channel.ChannelHandlerContext; 19 | import io.netty.handler.codec.MessageToMessageDecoder; 20 | import io.netty.handler.codec.http.FullHttpRequest; 21 | import io.netty.handler.codec.http.HttpContent; 22 | import io.netty.handler.codec.http.HttpHeaders; 23 | import io.netty.handler.codec.http.HttpRequest; 24 | import io.netty.util.CharsetUtil; 25 | 26 | /** 27 | * @author kelly.li 28 | * @date 2015-07-12 29 | */ 30 | public class FullHttpRequestDecoder extends MessageToMessageDecoder { 31 | private static final Logger logger = LoggerFactory.getLogger(FullHttpRequestDecoder.class); 32 | 33 | private static final char SLASH = '/'; 34 | private static final byte[] CRLF = {CR, LF}; 35 | private static final char QUESTION_MARK = '?'; 36 | private static final byte[] HEADER_SEPERATOR = {COLON, SP}; 37 | 38 | private static final Logger LOGGER = LoggerFactory.getLogger(FullHttpRequestDecoder.class); 39 | 40 | @Override 41 | protected void decode(ChannelHandlerContext ctx, Object msg, List out) throws Exception { 42 | ByteBuf buf = null; 43 | if (msg instanceof FullHttpRequest) { 44 | FullHttpRequest fullHttpRequest = (FullHttpRequest) msg; 45 | dispaly(fullHttpRequest); 46 | String host = extractHost(ctx, fullHttpRequest); 47 | buf = ctx.alloc().buffer(); 48 | encodeInitialLine(fullHttpRequest, buf); 49 | encodeHttpHeaders(fullHttpRequest, buf); 50 | encodeContent(fullHttpRequest, buf); 51 | Request request = new Request(host, "", buf); 52 | out.add(request); 53 | } 54 | 55 | } 56 | 57 | private String extractHost(ChannelHandlerContext ctx, FullHttpRequest request) { 58 | try { 59 | String host = ((InetSocketAddress) ctx.channel().remoteAddress()).getHostName(); 60 | if (!host.endsWith(".qunar.com")) { 61 | host = host + ".qunar.com"; 62 | } 63 | return host; 64 | } catch (Throwable e) { 65 | logger.error("extract host failed", e); 66 | return HttpHeaders.getHost(request); 67 | } 68 | } 69 | 70 | private void dispaly(FullHttpRequest fullHttpRequest) { 71 | LOGGER.info("****decode full http request**********"); 72 | LOGGER.info(fullHttpRequest.getMethod().name() + " " + fullHttpRequest.getUri() + " " + fullHttpRequest.getProtocolVersion().text()); 73 | for (String headerName : fullHttpRequest.headers().names()) { 74 | LOGGER.info(headerName + " " + HttpHeaders.getHeader(fullHttpRequest, headerName)); 75 | } 76 | LOGGER.info("*************************************"); 77 | 78 | } 79 | 80 | private void encodeContent(HttpContent content, ByteBuf buf) { 81 | buf.writeBytes(content.content()); 82 | } 83 | 84 | private void encodeHttpHeaders(HttpRequest request, ByteBuf buf) { 85 | HttpHeaders httpHeaders = request.headers(); 86 | for (Entry header : httpHeaders) { 87 | encodeAscii0(header.getKey(), buf); 88 | buf.writeBytes(HEADER_SEPERATOR); 89 | encodeAscii0(header.getValue(), buf); 90 | buf.writeBytes(CRLF); 91 | } 92 | buf.writeBytes(CRLF); 93 | } 94 | 95 | private void encodeInitialLine(HttpRequest request, ByteBuf buf) throws Exception { 96 | encodeMethod(request.getMethod().name(), buf); 97 | buf.writeByte(SP); 98 | 99 | // Add / as absolute path if no is present. 100 | // See http://tools.ietf.org/html/rfc2616#section-5.1.2 101 | String uri = request.getUri(); 102 | 103 | if (uri.length() == 0) { 104 | uri += SLASH; 105 | } else { 106 | int start = uri.indexOf("://"); 107 | if (start != -1 && uri.charAt(0) != SLASH) { 108 | int startIndex = start + 3; 109 | // Correctly handle query params. 110 | // See https://github.com/netty/netty/issues/2732 111 | int index = uri.indexOf(QUESTION_MARK, startIndex); 112 | if (index == -1) { 113 | if (uri.lastIndexOf(SLASH) <= startIndex) { 114 | uri += SLASH; 115 | } 116 | } else { 117 | if (uri.lastIndexOf(SLASH, index) <= startIndex) { 118 | int len = uri.length(); 119 | StringBuilder sb = new StringBuilder(len + 1); 120 | sb.append(uri, 0, index); 121 | sb.append(SLASH); 122 | sb.append(uri, index, len); 123 | uri = sb.toString(); 124 | } 125 | } 126 | } 127 | } 128 | 129 | buf.writeBytes(uri.getBytes(CharsetUtil.UTF_8)); 130 | 131 | buf.writeByte(SP); 132 | encodeProtocolVersion(request.getProtocolVersion().text(), buf); 133 | buf.writeBytes(CRLF); 134 | } 135 | 136 | private void encodeMethod(String method, ByteBuf buf) { 137 | encodeAscii0(method, buf); 138 | } 139 | 140 | private void encodeProtocolVersion(String protocolVersion, ByteBuf buf) { 141 | encodeAscii0(protocolVersion, buf); 142 | } 143 | 144 | private void encodeAscii0(CharSequence seq, ByteBuf buf) { 145 | int length = seq.length(); 146 | for (int i = 0; i < length; i++) { 147 | buf.writeByte((byte) seq.charAt(i)); 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/conn/NettyConnection.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.conn; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.PooledByteBufAllocator; 5 | import io.netty.channel.AdaptiveRecvByteBufAllocator; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.ChannelFutureListener; 9 | import io.netty.channel.ChannelInitializer; 10 | import io.netty.channel.ChannelOption; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import io.netty.handler.timeout.IdleStateHandler; 13 | 14 | import io.netty.util.AttributeKey; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.beans.factory.annotation.Value; 19 | 20 | import qunar.tc.dubbocopy.api.model.Target; 21 | import qunar.tc.dubbocopy.handler.DiscardHandler; 22 | import qunar.tc.dubbocopy.handler.IdleCloseHandler; 23 | import qunar.tc.dubbocopy.request.RawByteBufRequest; 24 | import qunar.tc.dubbocopy.util.Executors; 25 | import qunar.tc.dubbocopy.util.Monitor; 26 | 27 | import java.net.SocketAddress; 28 | 29 | /** 30 | * @author kelly.li 31 | * @date 2015-06-30 32 | */ 33 | public class NettyConnection implements Connection { 34 | private static final Logger LOGGER = LoggerFactory.getLogger(NettyConnection.class); 35 | 36 | @Value("${dubbocopy.client.connectTimeout}") 37 | private int connectionTimeoutMs; 38 | 39 | @Value("${dubbocopy.client.idleTimeoutSeconds}") 40 | private int idleTimeoutSeconds; 41 | 42 | private DiscardHandler discardHandler; 43 | 44 | private IdleCloseHandler idleCloseHandler; 45 | 46 | private String strVal = ""; 47 | 48 | private final Target target; 49 | private final String host; 50 | private final int port; 51 | 52 | private volatile boolean closed = false; 53 | 54 | private volatile Channel channel; 55 | 56 | private Bootstrap bootstrap; 57 | 58 | public static final AttributeKey connKey = AttributeKey.valueOf("conn"); 59 | 60 | public NettyConnection(Target target, int connectionTimeoutMs, int idleTimeoutSeconds, DiscardHandler discardHandler, IdleCloseHandler idleCloseHandler) { 61 | this.connectionTimeoutMs = connectionTimeoutMs; 62 | this.idleTimeoutSeconds = idleTimeoutSeconds; 63 | this.discardHandler = discardHandler; 64 | this.idleCloseHandler = idleCloseHandler; 65 | this.host = target.getHost(); 66 | this.port = target.getPort(); 67 | this.target = target; 68 | 69 | open(); 70 | try { 71 | ChannelFuture future = connect(); 72 | if (future == null) 73 | return; 74 | this.channel = future.await().channel(); 75 | this.channel.attr(connKey).set(this); 76 | } catch (Exception e) { 77 | LOGGER.error("build consumer connection error", e); 78 | } 79 | } 80 | 81 | private void open() { 82 | bootstrap = new Bootstrap() 83 | .group(Executors.workerGroup) 84 | .channel(NioSocketChannel.class) 85 | .option(ChannelOption.TCP_NODELAY, false) 86 | .option(ChannelOption.SO_REUSEADDR, true) 87 | .option(ChannelOption.AUTO_READ, true) 88 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutMs) 89 | .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 90 | .option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT) 91 | .handler(new ChannelInitializer() { 92 | @Override 93 | protected void initChannel(NioSocketChannel ch) throws Exception { 94 | ch.pipeline().addLast(new IdleStateHandler(0, 0, idleTimeoutSeconds)); 95 | ch.pipeline().addLast(idleCloseHandler); 96 | ch.pipeline().addLast(discardHandler); 97 | } 98 | }); 99 | } 100 | 101 | private ChannelFuture connect() { 102 | LOGGER.info("connect to {}:{}", host, port); 103 | return bootstrap.connect(host, port); 104 | } 105 | 106 | public void close() { 107 | LOGGER.warn("close connection: {} for {}:{}", this.channel, host, port); 108 | closed = true; 109 | if (this.channel == null) 110 | return; 111 | this.channel.close(); 112 | } 113 | 114 | public String key() { 115 | if (this.channel == null) return strVal; 116 | if (strVal.length() > 0) return strVal; 117 | 118 | SocketAddress remoteAddr = this.channel.remoteAddress(); 119 | SocketAddress localAddr = this.channel.localAddress(); 120 | if (remoteAddr != null) { 121 | SocketAddress srcAddr; 122 | SocketAddress dstAddr; 123 | 124 | srcAddr = localAddr; 125 | dstAddr = remoteAddr; 126 | 127 | strVal = String.format("[%s => %s]", srcAddr, dstAddr); 128 | } else if (localAddr != null) { 129 | strVal = String.format("[%s]", localAddr); 130 | } 131 | return strVal; 132 | } 133 | 134 | public Target target() { 135 | return this.target; 136 | } 137 | 138 | public void send(final RawByteBufRequest request) { 139 | if (channel == null || !channel.isActive()) 140 | return; 141 | if (!channel.isWritable()) { 142 | request.onUnWritable(); 143 | Monitor.sendBufferOverflow.inc(); 144 | return; 145 | } 146 | this.channel.writeAndFlush(request.getByteBuf()).addListener(new ChannelFutureListener() { 147 | public void operationComplete(ChannelFuture future) throws Exception { 148 | if (future.isSuccess()) { 149 | request.onWriteSuccess(); 150 | } else { 151 | request.onWriteFailed(future.cause()); 152 | } 153 | } 154 | }); 155 | } 156 | 157 | boolean isClosed() { 158 | return closed; 159 | } 160 | 161 | @Override 162 | public String toString() { 163 | return "client[" + host + ":" + port + "]"; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /dubbocopy-server/src/main/java/qunar/tc/dubbocopy/util/PrefixMatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012 Qunar.com. All Rights Reserved. 3 | */ 4 | package qunar.tc.dubbocopy.util; 5 | 6 | import com.googlecode.concurrenttrees.common.CharSequenceUtil; 7 | import com.googlecode.concurrenttrees.common.KeyValuePair; 8 | import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree; 9 | import com.googlecode.concurrenttrees.radix.node.Node; 10 | import com.googlecode.concurrenttrees.radix.node.NodeFactory; 11 | import com.googlecode.concurrenttrees.radixinverted.ConcurrentInvertedRadixTree; 12 | 13 | import java.util.LinkedHashSet; 14 | import java.util.Set; 15 | 16 | /** 17 | * @author miao.yang susing@gmail.com 18 | * @date 2012-12-27 19 | */ 20 | public class PrefixMatcher{ 21 | 22 | static class PrefixMatcherImpl extends ConcurrentRadixTree { 23 | 24 | public PrefixMatcherImpl(NodeFactory nodeFactory) { 25 | super(nodeFactory); 26 | } 27 | 28 | public PrefixMatcherImpl(NodeFactory nodeFactory, boolean restrictConcurrency) { 29 | super(nodeFactory, restrictConcurrency); 30 | } 31 | 32 | /** 33 | * Traverses the tree based on characters in the given input, and for 34 | * each node traversed which encodes a key in the tree, invokes the 35 | * given {@link KeyValueHandler} supplying it the key which matched that 36 | * node and the value from the node. 37 | * 38 | * @param input 39 | * A sequence of characters which controls traversal of the 40 | * tree 41 | * @param keyValueHandler 42 | * An object which will be notified of every key and value 43 | * encountered in the input 44 | */ 45 | protected void scanForKeysAtStartOfInput(CharSequence input, KeyValueHandler keyValueHandler) { 46 | Node currentNode = super.root; 47 | int charsMatched = 0; 48 | 49 | final int documentLength = input.length(); 50 | outer_loop: while (charsMatched < documentLength) { 51 | Node nextNode = currentNode.getOutgoingEdge(input.charAt(charsMatched)); 52 | if (nextNode == null) { 53 | // Next node is a dead end... 54 | // noinspection UnnecessaryLabelOnBreakStatement 55 | break outer_loop; 56 | } 57 | 58 | currentNode = nextNode; 59 | CharSequence currentNodeEdgeCharacters = currentNode.getIncomingEdge(); 60 | int charsMatchedThisEdge = 0; 61 | for (int i = 0, j = Math.min(currentNodeEdgeCharacters.length(), documentLength - charsMatched); i < j; i++) { 62 | if (currentNodeEdgeCharacters.charAt(i) != input.charAt(charsMatched + i)) { 63 | // Found a difference in chars between character in key 64 | // and a character in current node. 65 | // Current node is the deepest match (inexact match).... 66 | break outer_loop; 67 | } 68 | charsMatchedThisEdge++; 69 | } 70 | if (charsMatchedThisEdge == currentNodeEdgeCharacters.length()) { 71 | // All characters in the current edge matched, add this 72 | // number to total chars matched... 73 | charsMatched += charsMatchedThisEdge; 74 | } 75 | if (currentNode.getValue() != null) { 76 | CharSequence key = input.subSequence(0, charsMatched); 77 | O value = getValueForExactKey(key); 78 | if (value != null) { 79 | keyValueHandler.handle(key, currentNode.getValue()); 80 | } 81 | } 82 | } 83 | } 84 | 85 | interface KeyValueHandler { 86 | void handle(CharSequence key, Object value); 87 | } 88 | } 89 | 90 | private final PrefixMatcherImpl radixTree; 91 | 92 | /** 93 | * Creates a new {@link ConcurrentInvertedRadixTree} which will use the 94 | * given {@link NodeFactory} to create nodes. 95 | * 96 | * @param nodeFactory 97 | * An object which creates {@link Node} objects on-demand, and 98 | * which might return node implementations optimized for storing 99 | * the values supplied to it for the creation of each node 100 | */ 101 | public PrefixMatcher(NodeFactory nodeFactory) { 102 | this.radixTree = new PrefixMatcherImpl(nodeFactory); 103 | } 104 | 105 | /** 106 | * Creates a new {@link ConcurrentInvertedRadixTree} which will use the 107 | * given {@link NodeFactory} to create nodes. 108 | * 109 | * @param nodeFactory 110 | * An object which creates {@link Node} objects on-demand, and 111 | * which might return node implementations optimized for storing 112 | * the values supplied to it for the creation of each node 113 | * @param restrictConcurrency 114 | * If true, configures use of a 115 | * {@link java.util.concurrent.locks.ReadWriteLock} allowing 116 | * concurrent reads, except when writes are being performed by 117 | * other threads, in which case writes block all reads; if false, 118 | * configures lock-free reads; allows concurrent non-blocking 119 | * reads, even if writes are being performed by other threads 120 | */ 121 | public PrefixMatcher(NodeFactory nodeFactory, boolean restrictConcurrency) { 122 | this.radixTree = new PrefixMatcherImpl(nodeFactory, restrictConcurrency); 123 | } 124 | 125 | 126 | public O put(CharSequence key, O value) { 127 | return radixTree.put(key, value); 128 | } 129 | 130 | 131 | public O putIfAbsent(CharSequence key, O value) { 132 | return radixTree.putIfAbsent(key, value); 133 | } 134 | 135 | 136 | public boolean remove(CharSequence key) { 137 | return radixTree.remove(key); 138 | } 139 | 140 | public O getValueForExactKey(CharSequence key) { 141 | return radixTree.getValueForExactKey(key); 142 | } 143 | 144 | 145 | public Set getKeysPrefixIn(CharSequence document) { 146 | final Set results = new LinkedHashSet(); 147 | 148 | radixTree.scanForKeysAtStartOfInput(document, new PrefixMatcherImpl.KeyValueHandler() { 149 | public void handle(CharSequence key, Object value) { 150 | String keyString = CharSequenceUtil.toString(key); 151 | results.add(keyString); 152 | } 153 | }); 154 | 155 | return results; 156 | } 157 | 158 | 159 | public Set getValuesForKeysPrefixIn(CharSequence document) { 160 | final Set results = new LinkedHashSet(); 161 | radixTree.scanForKeysAtStartOfInput(document, new PrefixMatcherImpl.KeyValueHandler() { 162 | public void handle(CharSequence key, Object value) { 163 | @SuppressWarnings({ "unchecked" }) 164 | O valueTyped = (O) value; 165 | results.add(valueTyped); 166 | } 167 | }); 168 | return results; 169 | } 170 | 171 | 172 | public Set> getKeyValuePairsForKeysPrefixIn(CharSequence document) { 173 | final Set> results = new LinkedHashSet>(); 174 | radixTree.scanForKeysAtStartOfInput(document, new PrefixMatcherImpl.KeyValueHandler() { 175 | public void handle(CharSequence key, Object value) { 176 | @SuppressWarnings({ "unchecked" }) 177 | O valueTyped = (O) value; 178 | String keyString = CharSequenceUtil.toString(key); 179 | results.add(new ConcurrentRadixTree.KeyValuePairImpl(keyString, valueTyped)); 180 | } 181 | }); 182 | return results; 183 | } 184 | 185 | public Node getNode() { 186 | return radixTree.getNode(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /dubbocopy-server/src/test/java/qunar/tc/dubbocopy/test/a/RawByteBufClient.java: -------------------------------------------------------------------------------- 1 | package qunar.tc.dubbocopy.test.a; 2 | //package qunar.tc.dubbocopy.client; 3 | // 4 | //import com.google.common.collect.Maps; 5 | //import io.netty.bootstrap.Bootstrap; 6 | //import io.netty.buffer.PooledByteBufAllocator; 7 | //import io.netty.channel.*; 8 | //import io.netty.channel.socket.nio.NioSocketChannel; 9 | //import io.netty.handler.timeout.IdleStateHandler; 10 | //import io.netty.util.ReferenceCountUtil; 11 | //import org.slf4j.Logger; 12 | //import org.slf4j.LoggerFactory; 13 | //import org.springframework.beans.factory.annotation.Value; 14 | //import qunar.metrics.Gauge; 15 | //import qunar.metrics.Metrics; 16 | //import qunar.tc.dubbocopy.api.model.Target; 17 | //import qunar.tc.dubbocopy.server.handler.IdleCloseHandler; 18 | //import qunar.tc.dubbocopy.util.Executors; 19 | //import qunar.tc.dubbocopy.util.Monitor; 20 | // 21 | //import javax.annotation.PostConstruct; 22 | //import java.net.InetSocketAddress; 23 | //import java.net.SocketAddress; 24 | //import java.util.Map; 25 | // 26 | ///** 27 | // * @author song.xue created on 15/4/23 28 | // * @version 1.0.0 29 | // */ 30 | //public class RawByteBufClient { 31 | // private static final Logger LOGGER = LoggerFactory.getLogger(RawByteBufClient.class); 32 | // 33 | // @Value("${dubbocopy.client.connectTimeout}") 34 | // private int connectionTimeoutMs; 35 | // 36 | // @Value("${dubbocopy.client.idleTimeoutSeconds}") 37 | // private int idleTimeoutSeconds; 38 | // 39 | // private final Map establishedChannels; 40 | // 41 | // private final DiscardHandler discardHandler; 42 | // 43 | // private final IdleCloseHandler idleCloseHandler; 44 | // 45 | // public RawByteBufClient() { 46 | // Metrics.gauge("tc.dubbocopy.server.establishedChannels").call(new Gauge() { 47 | // @Override 48 | // public double getValue() { 49 | // return establishedChannels.size(); 50 | // } 51 | // }); 52 | // this.establishedChannels = Maps.newConcurrentMap(); 53 | // this.discardHandler = new DiscardHandler(establishedChannels); 54 | // this.idleCloseHandler = new IdleCloseHandler(); 55 | // } 56 | // 57 | // public void executeRequest(RawByteBufRequest request) { 58 | // connect(request); 59 | // } 60 | // 61 | // private void connect(final RawByteBufRequest request) { 62 | // if (tryToUseEstablishedChannel(request)) { 63 | // Monitor.useOldConnection.mark(); 64 | // LOGGER.debug("使用已有连接 {}", request); 65 | // return; 66 | // } 67 | // LOGGER.debug("使用新连接 {}", request); 68 | // Monitor.createNewConnection.mark(); 69 | // Bootstrap bootstrap = new Bootstrap(); 70 | // bootstrap.group(Executors.workerGroup) 71 | // .channel(NioSocketChannel.class) 72 | // .option(ChannelOption.TCP_NODELAY, false) 73 | // .option(ChannelOption.SO_REUSEADDR, true) 74 | // .option(ChannelOption.AUTO_READ, true) 75 | // .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutMs) 76 | // .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 77 | // .option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT) 78 | // .handler(new ChannelInitializer() { 79 | // @Override 80 | // protected void initChannel(NioSocketChannel ch) throws Exception { 81 | // ch.pipeline().addLast(new IdleStateHandler(0, 0, idleTimeoutSeconds)); 82 | // ch.pipeline().addLast(idleCloseHandler); 83 | // ch.pipeline().addLast(discardHandler); 84 | // } 85 | // }); 86 | // bootstrap.connect(request.getTarget().getHost(), request.getTarget().getPort()).addListener(new ChannelFutureListener() { 87 | // 88 | // @Override 89 | // public void operationComplete(ChannelFuture future) throws Exception { 90 | // if (!future.isSuccess()) { 91 | // LOGGER.error("request {} connect failed", request); 92 | // request.getListener().onConnectFailed(future.cause()); 93 | // return; 94 | // } 95 | // establishedChannels.put(request.getTarget(), future.channel()); 96 | // request.getListener().onConnected(); 97 | // sendData(request, future.channel()); 98 | // } 99 | // }); 100 | // } 101 | // 102 | // private boolean tryToUseEstablishedChannel(RawByteBufRequest request) { 103 | // Channel channel = establishedChannels.get(request.getTarget()); 104 | // if (channel == null) { 105 | // return false; 106 | // } 107 | // if (!channel.isActive()) { 108 | // establishedChannels.remove(request.getTarget()); 109 | // return false; 110 | // } 111 | // sendData(request, channel); 112 | // return true; 113 | // } 114 | // 115 | // private void sendData(final RawByteBufRequest request, Channel channel) { 116 | // if (!channel.isWritable()) { 117 | // request.getListener().onUnWritable(); 118 | // Monitor.sendBufferOverflow.inc(); 119 | // return; 120 | // } 121 | // channel.writeAndFlush(request.getByteBuf()).addListener(new ChannelFutureListener() { 122 | // @Override 123 | // public void operationComplete(ChannelFuture future) throws Exception { 124 | // if (future.isSuccess()) { 125 | // request.getListener().onWriteSuccess(); 126 | // } else { 127 | // request.getListener().onWriteFailed(future.cause()); 128 | // } 129 | // } 130 | // }); 131 | // } 132 | // 133 | // private static final int CLEAR_CHANNEL_INTERVAL_MILLI = 30000; 134 | // 135 | // 136 | // @PostConstruct 137 | // public void startClearThread() { 138 | // Thread clearThread = new Thread(new Runnable() { 139 | // @Override 140 | // public void run() { 141 | // while (true) { 142 | // try { 143 | // Thread.sleep(CLEAR_CHANNEL_INTERVAL_MILLI); 144 | // } catch (InterruptedException e) { 145 | // LOGGER.error("清除线程被打断{}", e); 146 | // Thread.interrupted(); 147 | // } 148 | // RawByteBufClient.this.clearClosedChannels(); 149 | // } 150 | // } 151 | // }); 152 | // clearThread.setDaemon(true); 153 | // clearThread.setName("clear-dead-channel-thread"); 154 | // clearThread.start(); 155 | // } 156 | // 157 | // private void clearClosedChannels() { 158 | // //如果channel 不活动清楚 159 | // LOGGER.info("当前共{}个连接", establishedChannels.size()); 160 | // for (Map.Entry entry : establishedChannels.entrySet()) { 161 | // if (!entry.getValue().isActive()) { 162 | // establishedChannels.remove(entry.getKey()); 163 | // } 164 | // } 165 | // LOGGER.info("清除后共{}个连接", establishedChannels.size()); 166 | // } 167 | // 168 | // @ChannelHandler.Sharable 169 | // private static class DiscardHandler extends ChannelInboundHandlerAdapter { 170 | // 171 | // private final Map channels; 172 | // 173 | // public DiscardHandler(Map channels) { 174 | // this.channels = channels; 175 | // } 176 | // 177 | // @Override 178 | // public void channelInactive(ChannelHandlerContext ctx) throws Exception { 179 | // //如果channel 不活动清楚 180 | // SocketAddress address = ctx.channel().remoteAddress(); 181 | // if (address == null) return; 182 | // if (!(address instanceof InetSocketAddress)) return; 183 | // InetSocketAddress socketAddress = (InetSocketAddress) address; 184 | // channels.remove(new Target(socketAddress.getHostName(), socketAddress.getPort())); 185 | // } 186 | // 187 | // @Override 188 | // public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 189 | // ReferenceCountUtil.safeRelease(msg); 190 | // } 191 | // 192 | // @Override 193 | // public boolean isSharable() { 194 | // return true; 195 | // } 196 | // } 197 | // 198 | //} 199 | --------------------------------------------------------------------------------