├── doc ├── 主流程.png ├── 吞吐量.png ├── 系统时序.png ├── 系统架构.png ├── 数据包结构.png ├── 平均耗时&随机取样.png └── benchmark.xlsx ├── src ├── test │ ├── java │ │ └── cn │ │ │ └── ziav │ │ │ └── rpc │ │ │ ├── bean │ │ │ ├── HelloReq.java │ │ │ ├── HelloResp.java │ │ │ ├── Page.java │ │ │ └── User.java │ │ │ ├── Constant.java │ │ │ ├── handler │ │ │ ├── MsgId.java │ │ │ ├── MultiThreadMsgHandler.java │ │ │ ├── ExceptionMsgHandler.java │ │ │ ├── TimeoutMsgHandler.java │ │ │ ├── CreateUserMsgHandler.java │ │ │ ├── HelloMsgHandler.java │ │ │ ├── ExistUserMsgHandler.java │ │ │ ├── GetUserMsgHandler.java │ │ │ └── ListUserMsgHandler.java │ │ │ ├── DemoServer.java │ │ │ ├── BenchmarkTest.java │ │ │ └── InvokeTest.java │ └── resources │ │ └── log4j.properties └── main │ └── java │ └── cn │ └── ziav │ └── rpc │ ├── client │ ├── EasyRpcCallback.java │ ├── ServerNode.java │ ├── RpcClientHandler.java │ ├── RpcFuture.java │ └── RpcClient.java │ ├── exception │ ├── ExceptionCode.java │ └── RemotingException.java │ ├── common │ ├── MsgRequest.java │ ├── Constants.java │ └── MsgResponse.java │ ├── codec │ ├── Wrapper.java │ ├── RpcDecoder.java │ └── RpcEncoder.java │ ├── server │ ├── IMsgHandler.java │ ├── RpcDispatcher.java │ └── RpcServer.java │ ├── timer │ ├── TimerTask.java │ ├── Timeout.java │ ├── Timer.java │ └── HashedWheelTimer.java │ └── utils │ ├── NamedThreadFactory.java │ ├── Predicates.java │ ├── Streams.java │ ├── Bytes.java │ ├── CollectionUtils.java │ └── ClassUtils.java ├── .github └── workflows │ └── maven.yml ├── .gitignore ├── README.md ├── pom.xml └── LICENSE /doc/主流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matrix6677/EasyRpc/HEAD/doc/主流程.png -------------------------------------------------------------------------------- /doc/吞吐量.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matrix6677/EasyRpc/HEAD/doc/吞吐量.png -------------------------------------------------------------------------------- /doc/系统时序.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matrix6677/EasyRpc/HEAD/doc/系统时序.png -------------------------------------------------------------------------------- /doc/系统架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matrix6677/EasyRpc/HEAD/doc/系统架构.png -------------------------------------------------------------------------------- /doc/数据包结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matrix6677/EasyRpc/HEAD/doc/数据包结构.png -------------------------------------------------------------------------------- /doc/平均耗时&随机取样.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matrix6677/EasyRpc/HEAD/doc/平均耗时&随机取样.png -------------------------------------------------------------------------------- /doc/benchmark.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Matrix6677/EasyRpc/HEAD/doc/benchmark.xlsx -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/bean/HelloReq.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.bean; 2 | 3 | /** @author Zavi */ 4 | public class HelloReq { 5 | public String msg; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/bean/HelloResp.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.bean; 2 | 3 | /** @author Zavi */ 4 | public class HelloResp { 5 | public String msg; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/Constant.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc; 2 | 3 | public interface Constant { 4 | 5 | String zkAddr = "127.0.0.1:2181"; 6 | String topic = "testService"; 7 | String localIp = "127.0.0.1"; 8 | int port = 8812; 9 | } 10 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info,stdout 2 | #console 3 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} --> [%t] %l: %m %x %n -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/MsgId.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | /** @author Zavi */ 4 | public interface MsgId { 5 | int HELLO = 1; 6 | int TIMEOUT = 2; 7 | int EXCEPTION = 3; 8 | int MULTI_THREAD = 4; 9 | int GET_USER = 5; 10 | int LIST_USER = 6; 11 | int CREATE_USER = 7; 12 | int EXIST_USER = 8; 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up JDK 1.8 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 1.8 16 | - name: Build with Maven 17 | run: mvn -B package -Dmaven.test.skip=true --file pom.xml 18 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/client/EasyRpcCallback.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.client; 2 | 3 | /** 4 | * 回调接口 5 | * 6 | * @author Zavi 7 | */ 8 | public interface EasyRpcCallback { 9 | /** 10 | * 成功 11 | * 12 | * @param result 13 | */ 14 | void success(R result); 15 | 16 | /** 17 | * 失败 18 | * 19 | * @param throwable 20 | */ 21 | void fail(Throwable throwable); 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/MultiThreadMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.server.IMsgHandler; 4 | 5 | /** @author Zavi */ 6 | public class MultiThreadMsgHandler implements IMsgHandler { 7 | 8 | @Override 9 | public String process(String o) throws Throwable { 10 | return "thread-" + o; 11 | } 12 | 13 | @Override 14 | public int msgId() { 15 | return MsgId.MULTI_THREAD; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/ExceptionMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.server.IMsgHandler; 4 | 5 | /** @author Zavi */ 6 | public class ExceptionMsgHandler implements IMsgHandler { 7 | 8 | @Override 9 | public String process(String s) throws Throwable { 10 | throw new IllegalStateException("fdsew"); 11 | } 12 | 13 | @Override 14 | public int msgId() { 15 | return MsgId.EXCEPTION; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/TimeoutMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.server.IMsgHandler; 4 | 5 | /** @author Zavi */ 6 | public class TimeoutMsgHandler implements IMsgHandler { 7 | 8 | @Override 9 | public String process(String s) throws Throwable { 10 | Thread.sleep(2000L); 11 | return "pong"; 12 | } 13 | 14 | @Override 15 | public int msgId() { 16 | return MsgId.TIMEOUT; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/exception/ExceptionCode.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.exception; 2 | 3 | /** @author Zavi */ 4 | public interface ExceptionCode { 5 | /** 客户端连接失败 */ 6 | int CLIENT_CONNECTED_FAILED = -1; 7 | /** 超时 */ 8 | int TIME_OUT = -2; 9 | /** 请求格式错误 */ 10 | int BAD_REQUEST = -3; 11 | /** 响应格式错误 */ 12 | int BAD_RESPONSE = -4; 13 | /** 客户端已关闭 */ 14 | int CLIENT_HAS_CLOSED = -5; 15 | /** 没有对应的handler */ 16 | int NO_SUCH_HANDLER = -6; 17 | /** 未知错误 */ 18 | int UNKNOWN_ERROR = -255; 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/CreateUserMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.bean.User; 4 | import cn.ziav.rpc.server.IMsgHandler; 5 | 6 | /** @author Zavi */ 7 | public class CreateUserMsgHandler implements IMsgHandler { 8 | 9 | @Override 10 | public Boolean process(User user) throws Throwable { 11 | if (user == null) { 12 | return false; 13 | } 14 | 15 | return true; 16 | } 17 | 18 | @Override 19 | public int msgId() { 20 | return MsgId.CREATE_USER; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/HelloMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.bean.HelloReq; 4 | import cn.ziav.rpc.bean.HelloResp; 5 | import cn.ziav.rpc.server.IMsgHandler; 6 | 7 | /** @author Zavi */ 8 | public class HelloMsgHandler implements IMsgHandler { 9 | 10 | @Override 11 | public HelloResp process(HelloReq helloReq) throws InterruptedException { 12 | HelloResp helloResp = new HelloResp(); 13 | helloResp.msg = "pong"; 14 | return helloResp; 15 | } 16 | 17 | @Override 18 | public int msgId() { 19 | return MsgId.HELLO; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/ExistUserMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.server.IMsgHandler; 4 | 5 | /** @author Zavi */ 6 | public class ExistUserMsgHandler implements IMsgHandler { 7 | 8 | @Override 9 | public Boolean process(String email) throws Throwable { 10 | if (email == null || email.isEmpty()) { 11 | return true; 12 | } 13 | 14 | if (email.charAt(email.length() - 1) < '5') { 15 | return false; 16 | } 17 | 18 | return true; 19 | } 20 | 21 | @Override 22 | public int msgId() { 23 | return MsgId.EXIST_USER; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/common/MsgRequest.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.common; 2 | 3 | import java.util.concurrent.atomic.AtomicLong; 4 | 5 | /** @author Zavi */ 6 | public class MsgRequest { 7 | private static final AtomicLong INVOKE_ID = new AtomicLong(0); 8 | 9 | public long id; 10 | /** 消息id */ 11 | public int mId; 12 | 13 | /** 是否为双向请求 */ 14 | public boolean mTwoWay = true; 15 | 16 | public T mData; 17 | 18 | public MsgRequest(long id, int mId) { 19 | this.id = id; 20 | this.mId = mId; 21 | } 22 | 23 | public MsgRequest() { 24 | this.id = newId(); 25 | } 26 | 27 | private static long newId() { 28 | return INVOKE_ID.getAndIncrement(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/bean/Page.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.bean; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | /** @author Zavi */ 7 | public class Page implements Serializable { 8 | 9 | private int pageNo; 10 | private int total; 11 | private List result; 12 | 13 | public int getPageNo() { 14 | return pageNo; 15 | } 16 | 17 | public void setPageNo(int pageNo) { 18 | this.pageNo = pageNo; 19 | } 20 | 21 | public int getTotal() { 22 | return total; 23 | } 24 | 25 | public void setTotal(int total) { 26 | this.total = total; 27 | } 28 | 29 | public List getResult() { 30 | return result; 31 | } 32 | 33 | public void setResult(List result) { 34 | this.result = result; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "Page [pageNo=" + pageNo + ", total=" + total + ", result=" + result + "]"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/common/Constants.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.common; 2 | 3 | import cn.ziav.rpc.utils.Bytes; 4 | 5 | /** @author Zavi */ 6 | public interface Constants { 7 | /** 默认IO线程池数 */ 8 | int DEFAULT_IO_THREADS = Math.min(Runtime.getRuntime().availableProcessors() + 1, 32); 9 | 10 | /** 默认心跳时间 */ 11 | int DEFAULT_HEARTBEAT = 60 * 1000; 12 | /** 8M */ 13 | int DEFAULT_PAYLOAD = 8 * 1024 * 1024; 14 | 15 | /** 协议头长度 */ 16 | int HEADER_LENGTH = 20; 17 | /** 魔数 */ 18 | short MAGIC = (short) 0xdabb; 19 | 20 | byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0]; 21 | byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1]; 22 | /** 请求标识 */ 23 | byte FLAG_REQUEST = (byte) 0x80; 24 | /** 往返请求标识 */ 25 | byte FLAG_TWOWAY = (byte) 0x40; 26 | 27 | /** 默认客户端连接超时 */ 28 | int DEFAULT_CONNECT_TIMEOUT = 3000; 29 | 30 | /** ZK默认连接超时 */ 31 | int ZK_SESSION_TIMEOUT = 5000; 32 | 33 | String ZK_REGISTRY_PATH = "/easy-rpc"; 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/exception/RemotingException.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.exception; 2 | 3 | /** 4 | * 统一异常 5 | * 6 | * @author Zavi 7 | */ 8 | public class RemotingException extends Exception { 9 | /** 异常码 */ 10 | public final int code; 11 | 12 | public RemotingException(int code) { 13 | this.code = code; 14 | } 15 | 16 | public RemotingException(int code, String message) { 17 | super(message); 18 | this.code = code; 19 | } 20 | 21 | public RemotingException(int code, String message, Throwable cause) { 22 | super(message, cause); 23 | this.code = code; 24 | } 25 | 26 | public RemotingException(int code, Throwable cause) { 27 | super(cause); 28 | this.code = code; 29 | } 30 | 31 | protected RemotingException( 32 | int code, 33 | String message, 34 | Throwable cause, 35 | boolean enableSuppression, 36 | boolean writableStackTrace) { 37 | super(message, cause, enableSuppression, writableStackTrace); 38 | this.code = code; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/client/ServerNode.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.client; 2 | 3 | import java.util.concurrent.atomic.LongAdder; 4 | 5 | /** @author Zavi */ 6 | public class ServerNode { 7 | public final String addr; 8 | /** 总响应时间(毫秒) */ 9 | private LongAdder totalRespTime = new LongAdder(); 10 | 11 | /** 总请求次数 */ 12 | private LongAdder totalReqTimes = new LongAdder(); 13 | 14 | public ServerNode(String addr) { 15 | this.addr = addr; 16 | } 17 | 18 | /** 19 | * 累计总响应时间 20 | * 21 | * @param time 22 | */ 23 | public void addCost(long time) { 24 | totalRespTime.add(time); 25 | totalReqTimes.increment(); 26 | } 27 | 28 | /** 29 | * 平均耗时 30 | * 31 | * @return 32 | */ 33 | public double calAvgRespTime() { 34 | long sumRespTime = totalRespTime.sum(); 35 | if (sumRespTime == 0) { 36 | return 0; 37 | } 38 | long sumReqTimes = totalReqTimes.sum(); 39 | if (sumReqTimes == 0) { 40 | return 0; 41 | } 42 | 43 | return sumRespTime * 1.0f / sumReqTimes; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/DemoServer.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc; 2 | 3 | import static cn.ziav.rpc.Constant.localIp; 4 | import static cn.ziav.rpc.Constant.port; 5 | import static cn.ziav.rpc.Constant.topic; 6 | import static cn.ziav.rpc.Constant.zkAddr; 7 | 8 | import cn.ziav.rpc.handler.CreateUserMsgHandler; 9 | import cn.ziav.rpc.handler.ExistUserMsgHandler; 10 | import cn.ziav.rpc.handler.GetUserMsgHandler; 11 | import cn.ziav.rpc.handler.ListUserMsgHandler; 12 | import cn.ziav.rpc.server.RpcServer; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * @author Zavi 18 | */ 19 | public class DemoServer { 20 | 21 | static Logger logger = LoggerFactory.getLogger(DemoServer.class); 22 | 23 | public static void main(String[] args) throws Throwable { 24 | RpcServer server = new RpcServer(zkAddr, topic, localIp, port); 25 | server.register(new ExistUserMsgHandler()); 26 | server.register(new GetUserMsgHandler()); 27 | server.register(new ListUserMsgHandler()); 28 | server.register(new CreateUserMsgHandler()); 29 | logger.info("服务器启动成功!"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/codec/Wrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package cn.ziav.rpc.codec; 19 | 20 | /** Protostuff对象包装类 */ 21 | public class Wrapper { 22 | private T data; 23 | 24 | Wrapper(T data) { 25 | this.data = data; 26 | } 27 | 28 | T getData() { 29 | return data; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/server/IMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.server; 2 | 3 | import cn.ziav.rpc.utils.NamedThreadFactory; 4 | import java.util.concurrent.ArrayBlockingQueue; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * 消息处理接口 11 | * 12 | * @author Zavi 13 | */ 14 | public interface IMsgHandler { 15 | ExecutorService DEFAULT_THREAD_EXEC = 16 | new ThreadPoolExecutor( 17 | 200, 18 | 200, 19 | 1, 20 | TimeUnit.MINUTES, 21 | new ArrayBlockingQueue<>(200), 22 | new NamedThreadFactory("EasyRpcMsgHandler", true)); 23 | 24 | /** 25 | * 处理业务 26 | * 27 | * @param t 请求参数 28 | * @return 返回参数 29 | * @throws Throwable 异常 30 | */ 31 | R process(T t) throws Throwable; 32 | 33 | /** 34 | * 业务消息id 35 | * 36 | * @return 37 | */ 38 | int msgId(); 39 | 40 | /** 41 | * 自定义业务线程池,默认{@code DEFAULT_THREAD_EXEC} 42 | * 43 | * @return 44 | */ 45 | default ExecutorService threadPool() { 46 | return DEFAULT_THREAD_EXEC; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/timer/TimerTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package cn.ziav.rpc.timer; 18 | 19 | import java.util.concurrent.TimeUnit; 20 | 21 | /** 22 | * A task which is executed after the delay specified with {@link Timer#newTimeout(TimerTask, long, 23 | * TimeUnit)} (TimerTask, long, TimeUnit)}. 24 | */ 25 | public interface TimerTask { 26 | 27 | /** 28 | * Executed after the delay specified with {@link Timer#newTimeout(TimerTask, long, TimeUnit)}. 29 | * 30 | * @param timeout a handle which is associated with this task 31 | */ 32 | void run(Timeout timeout) throws Exception; 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/GetUserMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.bean.User; 4 | import cn.ziav.rpc.server.IMsgHandler; 5 | import java.time.LocalDate; 6 | import java.time.LocalDateTime; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | /** @author Zavi */ 12 | public class GetUserMsgHandler implements IMsgHandler { 13 | 14 | @Override 15 | public User process(Integer id) throws Throwable { 16 | User user = new User(); 17 | 18 | user.setId(id); 19 | user.setName("Doug Lea"); 20 | user.setSex(1); 21 | user.setBirthday(LocalDate.of(1968, 12, 8)); 22 | user.setEmail("dong.lea@gmail.com"); 23 | user.setMobile("18612345678"); 24 | user.setAddress("北京市 中关村 中关村大街1号 鼎好大厦 1605"); 25 | user.setIcon("https://www.baidu.com/img/bd_logo1.png"); 26 | user.setStatus(1); 27 | user.setCreateTime(LocalDateTime.now()); 28 | user.setUpdateTime(user.getCreateTime()); 29 | 30 | List permissions = 31 | new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 19, 88, 86, 89, 90, 91, 92)); 32 | 33 | user.setPermissions(permissions); 34 | 35 | return user; 36 | } 37 | 38 | @Override 39 | public int msgId() { 40 | return MsgId.GET_USER; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/common/MsgResponse.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.common; 2 | 3 | /** @author Zavi */ 4 | public class MsgResponse { 5 | /** ok. */ 6 | public static final byte OK = 20; 7 | /** client side timeout. */ 8 | public static final byte CLIENT_TIMEOUT = 30; 9 | 10 | /** server side timeout. */ 11 | public static final byte SERVER_TIMEOUT = 31; 12 | 13 | /** channel inactive, directly return the unfinished requests. */ 14 | public static final byte CHANNEL_INACTIVE = 35; 15 | 16 | /** request format error. */ 17 | public static final byte BAD_REQUEST = 40; 18 | 19 | /** response format error. */ 20 | public static final byte BAD_RESPONSE = 50; 21 | 22 | /** service not found. */ 23 | public static final byte SERVICE_NOT_FOUND = 60; 24 | 25 | /** service error. */ 26 | public static final byte SERVICE_ERROR = 70; 27 | 28 | /** internal server error. */ 29 | public static final byte SERVER_ERROR = 80; 30 | 31 | /** internal server error. */ 32 | public static final byte CLIENT_ERROR = 90; 33 | 34 | /** server side threadpool exhausted and quick return. */ 35 | public static final byte SERVER_THREADPOOL_EXHAUSTED_ERROR = 100; 36 | 37 | public long id; 38 | /** 消息id */ 39 | public int mId; 40 | 41 | public R mData; 42 | 43 | public byte mStatus = OK; 44 | 45 | public MsgResponse(long id, int mId) { 46 | this.id = id; 47 | this.mId = mId; 48 | } 49 | 50 | public MsgResponse(long id, int mId, R mData) { 51 | this.id = id; 52 | this.mId = mId; 53 | this.mData = mData; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/handler/ListUserMsgHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.handler; 2 | 3 | import cn.ziav.rpc.bean.Page; 4 | import cn.ziav.rpc.bean.User; 5 | import cn.ziav.rpc.server.IMsgHandler; 6 | import java.time.LocalDate; 7 | import java.time.LocalDateTime; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | /** @author Zavi */ 13 | public class ListUserMsgHandler implements IMsgHandler> { 14 | 15 | @Override 16 | public Page process(Integer pageNo) throws Throwable { 17 | List userList = new ArrayList<>(15); 18 | 19 | for (int i = 0; i < 15; i++) { 20 | User user = new User(); 21 | 22 | user.setId(i); 23 | user.setName("Doug Lea" + i); 24 | user.setSex(1); 25 | user.setBirthday(LocalDate.of(1968, 12, 8)); 26 | user.setEmail("dong.lea@gmail.com" + i); 27 | user.setMobile("18612345678" + i); 28 | user.setAddress("北京市 中关村 中关村大街1号 鼎好大厦 1605" + i); 29 | user.setIcon("https://www.baidu.com/img/bd_logo1.png" + i); 30 | user.setStatus(1); 31 | user.setCreateTime(LocalDateTime.now()); 32 | user.setUpdateTime(user.getCreateTime()); 33 | 34 | List permissions = 35 | new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 19, 88, 86, 89, 90, 91, 92)); 36 | user.setPermissions(permissions); 37 | 38 | userList.add(user); 39 | } 40 | 41 | Page page = new Page<>(); 42 | page.setPageNo(pageNo); 43 | page.setTotal(1000); 44 | page.setResult(userList); 45 | 46 | return page; 47 | } 48 | 49 | @Override 50 | public int msgId() { 51 | return MsgId.LIST_USER; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/timer/Timeout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package cn.ziav.rpc.timer; 18 | 19 | /** A handle associated with a {@link TimerTask} that is returned by a {@link Timer}. */ 20 | public interface Timeout { 21 | 22 | /** Returns the {@link Timer} that created this handle. */ 23 | Timer timer(); 24 | 25 | /** Returns the {@link TimerTask} which is associated with this handle. */ 26 | TimerTask task(); 27 | 28 | /** 29 | * Returns {@code true} if and only if the {@link TimerTask} associated with this handle has been 30 | * expired. 31 | */ 32 | boolean isExpired(); 33 | 34 | /** 35 | * Returns {@code true} if and only if the {@link TimerTask} associated with this handle has been 36 | * cancelled. 37 | */ 38 | boolean isCancelled(); 39 | 40 | /** 41 | * Attempts to cancel the {@link TimerTask} associated with this handle. If the task has been 42 | * executed or cancelled already, it will return with no side effect. 43 | * 44 | * @return True if the cancellation completed successfully, otherwise false 45 | */ 46 | boolean cancel(); 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/timer/Timer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package cn.ziav.rpc.timer; 18 | 19 | import java.util.Set; 20 | import java.util.concurrent.RejectedExecutionException; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | /** Schedules {@link TimerTask}s for one-time future execution in a background thread. */ 24 | public interface Timer { 25 | 26 | /** 27 | * Schedules the specified {@link TimerTask} for one-time execution after the specified delay. 28 | * 29 | * @return a handle which is associated with the specified task 30 | * @throws IllegalStateException if this timer has been {@linkplain #stop() stopped} already 31 | * @throws RejectedExecutionException if the pending timeouts are too many and creating new 32 | * timeout can cause instability in the system. 33 | */ 34 | Timeout newTimeout(TimerTask task, long delay, TimeUnit unit); 35 | 36 | /** 37 | * Releases all resources acquired by this {@link Timer} and cancels all tasks which were 38 | * scheduled but not executed yet. 39 | * 40 | * @return the handles associated with the tasks which were canceled by this method 41 | */ 42 | Set stop(); 43 | 44 | /** 45 | * the timer is stop 46 | * 47 | * @return true for stop 48 | */ 49 | boolean isStop(); 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyRpc 2 | 3 | [![Build Status](https://github.com/Matrix6677/EasyRpc/workflows/Java%20CI/badge.svg)](https://github.com/Matrix6677/EasyRpc/actions) ![license](https://img.shields.io/github/license/alibaba/dubbo.svg) 4 | 5 | EasyRpc是基于Netty、ZooKeeper和ProtoStuff开发的一个简单易用,便于学习的RPC框架。 6 | 7 | ------ 8 | 9 | ## 1 特性 10 | 11 | - 简单易用; 12 | - 注释完善,方便学习; 13 | - 低延迟,基于Netty 4; 14 | - 解决TCP粘包/拆包问题; 15 | - 支持非阻塞的同步/异步调用; 16 | - 基于ProtoStuff的对象序列化; 17 | - 完整的单元测试和JMH性能压测; 18 | - 基于ZooKeeper实现的服务注册和发现; 19 | - 仿Dubbo数据包结构,优化协议头仅20字节; 20 | - 支持4种负载均衡策略:随机、轮询、哈希、最佳响应; 21 | 22 | ## 2 总体设计 23 | 24 | ### 2.1 架构图 25 | 26 | ![系统架构](https://raw.githubusercontent.com/Matrix6677/EasyRpc/master/doc/%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84.png) 27 | 28 | ### 2.2 系统时序 29 | 30 | ![系统时序](https://raw.githubusercontent.com/Matrix6677/EasyRpc/master/doc/%E7%B3%BB%E7%BB%9F%E6%97%B6%E5%BA%8F.png) 31 | 32 | ### 2.3 主流程图 33 | 34 | 主流程 35 | 36 | ### 2.4 数据包结构 37 | 38 | ![数据包结构](https://raw.githubusercontent.com/Matrix6677/EasyRpc/master/doc/%E6%95%B0%E6%8D%AE%E5%8C%85%E7%BB%93%E6%9E%84.png) 39 | 40 | ## 3 性能测试 41 | 42 | ![吞吐量](https://github.com/Matrix6677/EasyRpc/blob/master/doc/%E5%90%9E%E5%90%90%E9%87%8F.png?raw=true) 43 | 44 | ![平均耗时&随机取样](https://github.com/Matrix6677/EasyRpc/blob/master/doc/%E5%B9%B3%E5%9D%87%E8%80%97%E6%97%B6&%E9%9A%8F%E6%9C%BA%E5%8F%96%E6%A0%B7.png?raw=true) 45 | 46 | ## 4 参考文献 47 | 48 | [1]阮一峰.理解字节序[J/OL].阮一峰的网络日志,2016-11-22. 49 | 50 | [2]猿码道.聊聊Linux 五种IO模型[J/OL].简书,2016.05.18. 51 | 52 | [3]猿码道.聊聊同步、异步、阻塞与非阻塞[J/OL].简书,2016.05.18. 53 | 54 | [4]科来网络.网络通讯协议[J/OL].科来网络,2019-01.01. 55 | 56 | [5]此鱼不得水.Dubbo编码解码[J/OL].简书,2017-12-20. 57 | 58 | [6]Newland.谈谈如何使用Netty开发实现高性能的RPC服务器[J/OL].博客园,2016-06-25. 59 | 60 | [7]加多.谈谈Netty的线程模型[J/OL].并发编程网,2019-08-23. 61 | 62 | [8]鲁小憨.怎样对 RPC 进行有效的性能测试[J/OL].简书,2018-02-02. 63 | 64 | ## 5 License 65 | 66 | EasyRpc is under the Apache 2.0 license. See the [LICENSE](https://github.com/Matrix6677/EasyRpc/blob/master/LICENSE) file for details. -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/utils/NamedThreadFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cn.ziav.rpc.utils; 18 | 19 | import java.util.concurrent.ThreadFactory; 20 | import java.util.concurrent.atomic.AtomicInteger; 21 | 22 | /** InternalThreadFactory. */ 23 | public class NamedThreadFactory implements ThreadFactory { 24 | 25 | protected static final AtomicInteger POOL_SEQ = new AtomicInteger(1); 26 | 27 | protected final AtomicInteger mThreadNum = new AtomicInteger(1); 28 | 29 | protected final String mPrefix; 30 | 31 | protected final boolean mDaemon; 32 | 33 | protected final ThreadGroup mGroup; 34 | 35 | public NamedThreadFactory() { 36 | this("pool-" + POOL_SEQ.getAndIncrement(), false); 37 | } 38 | 39 | public NamedThreadFactory(String prefix) { 40 | this(prefix, false); 41 | } 42 | 43 | public NamedThreadFactory(String prefix, boolean daemon) { 44 | mPrefix = prefix + "-thread-"; 45 | mDaemon = daemon; 46 | SecurityManager s = System.getSecurityManager(); 47 | mGroup = (s == null) ? Thread.currentThread().getThreadGroup() : s.getThreadGroup(); 48 | } 49 | 50 | @Override 51 | public Thread newThread(Runnable runnable) { 52 | String name = mPrefix + mThreadNum.getAndIncrement(); 53 | Thread ret = new Thread(mGroup, runnable, name, 0); 54 | ret.setDaemon(mDaemon); 55 | return ret; 56 | } 57 | 58 | public ThreadGroup getThreadGroup() { 59 | return mGroup; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/utils/Predicates.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cn.ziav.rpc.utils; 18 | 19 | import static java.util.stream.Stream.of; 20 | 21 | import java.util.function.Predicate; 22 | 23 | /** 24 | * The utilities class for Java {@link Predicate} 25 | * 26 | * @since 2.7.5 27 | */ 28 | public interface Predicates { 29 | 30 | Predicate[] EMPTY_ARRAY = new Predicate[0]; 31 | 32 | /** 33 | * {@link Predicate} always return true 34 | * 35 | * @param the type to test 36 | * @return true 37 | */ 38 | static Predicate alwaysTrue() { 39 | return e -> true; 40 | } 41 | 42 | /** 43 | * {@link Predicate} always return false 44 | * 45 | * @param the type to test 46 | * @return false 47 | */ 48 | static Predicate alwaysFalse() { 49 | return e -> false; 50 | } 51 | 52 | /** 53 | * a composed predicate that represents a short-circuiting logical AND of {@link Predicate 54 | * predicates} 55 | * 56 | * @param predicates {@link Predicate predicates} 57 | * @param the type to test 58 | * @return non-null 59 | */ 60 | static Predicate and(Predicate... predicates) { 61 | return of(predicates).reduce((a, b) -> a.and(b)).orElseGet(Predicates::alwaysTrue); 62 | } 63 | 64 | /** 65 | * a composed predicate that represents a short-circuiting logical OR of {@link Predicate 66 | * predicates} 67 | * 68 | * @param predicates {@link Predicate predicates} 69 | * @param the detected type 70 | * @return non-null 71 | */ 72 | static Predicate or(Predicate... predicates) { 73 | return of(predicates).reduce((a, b) -> a.or(b)).orElse(e -> true); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/utils/Streams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cn.ziav.rpc.utils; 18 | 19 | import static cn.ziav.rpc.utils.Predicates.and; 20 | import static cn.ziav.rpc.utils.Predicates.or; 21 | import static java.util.stream.Collectors.toList; 22 | import static java.util.stream.StreamSupport.stream; 23 | 24 | import java.util.LinkedHashSet; 25 | import java.util.List; 26 | import java.util.Set; 27 | import java.util.function.Predicate; 28 | import java.util.stream.Stream; 29 | 30 | /** 31 | * The utilities class for {@link Stream} 32 | * 33 | * @since 2.7.5 34 | */ 35 | public interface Streams { 36 | 37 | static > Stream filterStream(S values, Predicate predicate) { 38 | return stream(values.spliterator(), false).filter(predicate); 39 | } 40 | 41 | static > List filterList(S values, Predicate predicate) { 42 | return filterStream(values, predicate).collect(toList()); 43 | } 44 | 45 | static > Set filterSet(S values, Predicate predicate) { 46 | // new Set with insertion order 47 | return filterStream(values, predicate).collect(LinkedHashSet::new, Set::add, Set::addAll); 48 | } 49 | 50 | static > S filter(S values, Predicate predicate) { 51 | final boolean isSet = Set.class.isAssignableFrom(values.getClass()); 52 | return (S) (isSet ? filterSet(values, predicate) : filterList(values, predicate)); 53 | } 54 | 55 | static > S filterAll(S values, Predicate... predicates) { 56 | return filter(values, and(predicates)); 57 | } 58 | 59 | static > S filterAny(S values, Predicate... predicates) { 60 | return filter(values, or(predicates)); 61 | } 62 | 63 | static T filterFirst(Iterable values, Predicate... predicates) { 64 | return stream(values.spliterator(), false).filter(and(predicates)).findFirst().orElse(null); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/utils/Bytes.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.utils; 2 | 3 | /** @author Zavi */ 4 | public class Bytes { 5 | /** 6 | * to byte array. 7 | * 8 | * @param v value. 9 | * @param b byte array. 10 | * @param off array offset. 11 | */ 12 | public static void int2bytes(int v, byte[] b, int off) { 13 | b[off + 3] = (byte) v; 14 | b[off + 2] = (byte) (v >>> 8); 15 | b[off + 1] = (byte) (v >>> 16); 16 | b[off] = (byte) (v >>> 24); 17 | } 18 | 19 | /** 20 | * to byte array. 21 | * 22 | * @param v value. 23 | * @param b byte array. 24 | * @param off array offset. 25 | */ 26 | public static void long2bytes(long v, byte[] b, int off) { 27 | b[off + 7] = (byte) v; 28 | b[off + 6] = (byte) (v >>> 8); 29 | b[off + 5] = (byte) (v >>> 16); 30 | b[off + 4] = (byte) (v >>> 24); 31 | b[off + 3] = (byte) (v >>> 32); 32 | b[off + 2] = (byte) (v >>> 40); 33 | b[off + 1] = (byte) (v >>> 48); 34 | b[off] = (byte) (v >>> 56); 35 | } 36 | 37 | /** 38 | * to long. 39 | * 40 | * @param b byte array. 41 | * @param off offset. 42 | * @return long. 43 | */ 44 | public static long bytes2long(byte[] b, int off) { 45 | return ((b[off + 7] & 0xFFL)) 46 | + ((b[off + 6] & 0xFFL) << 8) 47 | + ((b[off + 5] & 0xFFL) << 16) 48 | + ((b[off + 4] & 0xFFL) << 24) 49 | + ((b[off + 3] & 0xFFL) << 32) 50 | + ((b[off + 2] & 0xFFL) << 40) 51 | + ((b[off + 1] & 0xFFL) << 48) 52 | + (((long) b[off]) << 56); 53 | } 54 | 55 | /** 56 | * byte array copy. 57 | * 58 | * @param src src. 59 | * @param length new length. 60 | * @return new byte array. 61 | */ 62 | public static byte[] copyOf(byte[] src, int length) { 63 | byte[] dest = new byte[length]; 64 | System.arraycopy(src, 0, dest, 0, Math.min(src.length, length)); 65 | return dest; 66 | } 67 | 68 | /** 69 | * to byte array. 70 | * 71 | * @param v value. 72 | * @return byte[]. 73 | */ 74 | public static byte[] short2bytes(short v) { 75 | byte[] ret = {0, 0}; 76 | short2bytes(v, ret); 77 | return ret; 78 | } 79 | 80 | /** 81 | * to byte array. 82 | * 83 | * @param v value. 84 | * @param b byte array. 85 | */ 86 | public static void short2bytes(short v, byte[] b) { 87 | short2bytes(v, b, 0); 88 | } 89 | 90 | /** 91 | * to byte array. 92 | * 93 | * @param v value. 94 | * @param b byte array. 95 | */ 96 | public static void short2bytes(short v, byte[] b, int off) { 97 | b[off + 1] = (byte) v; 98 | b[off + 0] = (byte) (v >>> 8); 99 | } 100 | 101 | /** 102 | * to int. 103 | * 104 | * @param b byte array. 105 | * @param off offset. 106 | * @return int. 107 | */ 108 | public static int bytes2int(byte[] b, int off) { 109 | return ((b[off + 3] & 0xFF)) 110 | + ((b[off + 2] & 0xFF) << 8) 111 | + ((b[off + 1] & 0xFF) << 16) 112 | + ((b[off]) << 24); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/bean/User.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.bean; 2 | 3 | import java.io.Serializable; 4 | import java.time.LocalDate; 5 | import java.time.LocalDateTime; 6 | import java.util.List; 7 | 8 | /** @author Zavi */ 9 | public class User implements Serializable { 10 | 11 | private long id; 12 | private String name; 13 | private int sex; 14 | private LocalDate birthday; 15 | private String email; 16 | private String mobile; 17 | private String address; 18 | private String icon; 19 | private List permissions; 20 | private int status; 21 | private LocalDateTime createTime; 22 | private LocalDateTime updateTime; 23 | 24 | public long getId() { 25 | return id; 26 | } 27 | 28 | public void setId(long id) { 29 | this.id = id; 30 | } 31 | 32 | public String getName() { 33 | return name; 34 | } 35 | 36 | public void setName(String name) { 37 | this.name = name; 38 | } 39 | 40 | public int getSex() { 41 | return sex; 42 | } 43 | 44 | public void setSex(int sex) { 45 | this.sex = sex; 46 | } 47 | 48 | public LocalDate getBirthday() { 49 | return birthday; 50 | } 51 | 52 | public void setBirthday(LocalDate birthday) { 53 | this.birthday = birthday; 54 | } 55 | 56 | public String getEmail() { 57 | return email; 58 | } 59 | 60 | public void setEmail(String email) { 61 | this.email = email; 62 | } 63 | 64 | public String getMobile() { 65 | return mobile; 66 | } 67 | 68 | public void setMobile(String mobile) { 69 | this.mobile = mobile; 70 | } 71 | 72 | public String getAddress() { 73 | return address; 74 | } 75 | 76 | public void setAddress(String address) { 77 | this.address = address; 78 | } 79 | 80 | public String getIcon() { 81 | return icon; 82 | } 83 | 84 | public void setIcon(String icon) { 85 | this.icon = icon; 86 | } 87 | 88 | public List getPermissions() { 89 | return permissions; 90 | } 91 | 92 | public void setPermissions(List permissions) { 93 | this.permissions = permissions; 94 | } 95 | 96 | public int getStatus() { 97 | return status; 98 | } 99 | 100 | public void setStatus(int status) { 101 | this.status = status; 102 | } 103 | 104 | public LocalDateTime getCreateTime() { 105 | return createTime; 106 | } 107 | 108 | public void setCreateTime(LocalDateTime createTime) { 109 | this.createTime = createTime; 110 | } 111 | 112 | public LocalDateTime getUpdateTime() { 113 | return updateTime; 114 | } 115 | 116 | public void setUpdateTime(LocalDateTime updateTime) { 117 | this.updateTime = updateTime; 118 | } 119 | 120 | @Override 121 | public String toString() { 122 | return "User [id=" 123 | + id 124 | + ", name=" 125 | + name 126 | + ", sex=" 127 | + sex 128 | + ", birthday=" 129 | + birthday 130 | + ", email=" 131 | + email 132 | + ", mobile=" 133 | + mobile 134 | + ", address=" 135 | + address 136 | + ", icon=" 137 | + icon 138 | + ", permissions=" 139 | + permissions 140 | + ", status=" 141 | + status 142 | + ", createTime=" 143 | + createTime 144 | + ", updateTime=" 145 | + updateTime 146 | + "]"; 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | cn.ziav 6 | easy-rpc 7 | 1.0.0-RELEASE 8 | easy-rpc 9 | a simple rpc framework based on netty 4. 10 | 11 | 12 | 13 | 14 | org.apache.zookeeper 15 | zookeeper 16 | 3.4.14 17 | 18 | 19 | 20 | com.google.guava 21 | guava 22 | 28.1-jre 23 | 24 | 25 | io.netty 26 | netty-all 27 | 4.1.43.Final 28 | 29 | 30 | io.protostuff 31 | protostuff-core 32 | 1.6.2 33 | 34 | 35 | io.protostuff 36 | protostuff-runtime 37 | 1.6.2 38 | 39 | 40 | org.openjdk.jmh 41 | jmh-core 42 | 1.23 43 | 44 | 45 | org.openjdk.jmh 46 | jmh-generator-annprocess 47 | 1.23 48 | provided 49 | 50 | 51 | org.apache.commons 52 | commons-lang3 53 | 3.9 54 | 55 | 56 | org.junit.jupiter 57 | junit-jupiter-api 58 | 5.6.0 59 | test 60 | 61 | 62 | org.junit.jupiter 63 | junit-jupiter-engine 64 | 5.6.0 65 | test 66 | 67 | 68 | 69 | ch.qos.logback 70 | logback-classic 71 | 1.2.3 72 | test 73 | 74 | 75 | ch.qos.logback 76 | logback-core 77 | 1.2.3 78 | 79 | 80 | org.slf4j 81 | slf4j-api 82 | 1.7.30 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.apache.maven.plugins 90 | maven-source-plugin 91 | 3.2.0 92 | 93 | 94 | attach-sources 95 | verify 96 | 97 | jar-no-fork 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-compiler-plugin 106 | 3.8.1 107 | 108 | 1.8 109 | 1.8 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/BenchmarkTest.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc; 2 | 3 | import static cn.ziav.rpc.Constant.topic; 4 | import static cn.ziav.rpc.Constant.zkAddr; 5 | 6 | import cn.ziav.rpc.bean.User; 7 | import cn.ziav.rpc.client.RpcClient; 8 | import cn.ziav.rpc.handler.MsgId; 9 | import java.time.LocalDate; 10 | import java.time.LocalDateTime; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicInteger; 16 | import org.openjdk.jmh.annotations.Benchmark; 17 | import org.openjdk.jmh.annotations.BenchmarkMode; 18 | import org.openjdk.jmh.annotations.Mode; 19 | import org.openjdk.jmh.annotations.Scope; 20 | import org.openjdk.jmh.annotations.Setup; 21 | import org.openjdk.jmh.annotations.State; 22 | import org.openjdk.jmh.annotations.TearDown; 23 | import org.openjdk.jmh.runner.Runner; 24 | import org.openjdk.jmh.runner.options.Options; 25 | import org.openjdk.jmh.runner.options.OptionsBuilder; 26 | import org.openjdk.jmh.runner.options.TimeValue; 27 | 28 | /** @author Zavi */ 29 | @State(Scope.Benchmark) 30 | public class BenchmarkTest { 31 | public static final int CONCURRENCY = 32; 32 | 33 | private RpcClient client; 34 | 35 | private final AtomicInteger counter = new AtomicInteger(0); 36 | 37 | @Setup 38 | public void initClient() throws Throwable { 39 | client = new RpcClient(zkAddr, topic); 40 | } 41 | 42 | @TearDown 43 | public void close() { 44 | client.doClose(); 45 | } 46 | 47 | @Benchmark 48 | @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime}) 49 | public void existUser() throws Throwable { 50 | String email = String.valueOf(counter.getAndIncrement()); 51 | client.send(client.randomNode(), MsgId.EXIST_USER, email, 5000); 52 | } 53 | 54 | @Benchmark 55 | @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime}) 56 | public void createUser() throws Throwable { 57 | int id = counter.getAndIncrement(); 58 | User user = new User(); 59 | 60 | user.setId(id); 61 | user.setName("Doug Lea"); 62 | user.setSex(1); 63 | user.setBirthday(LocalDate.of(1968, 12, 8)); 64 | user.setEmail("dong.lea@gmail.com"); 65 | user.setMobile("18612345678"); 66 | user.setAddress("北京市 中关村 中关村大街1号 鼎好大厦 1605"); 67 | user.setIcon("https://www.baidu.com/img/bd_logo1.png"); 68 | user.setStatus(1); 69 | user.setCreateTime(LocalDateTime.now()); 70 | user.setUpdateTime(user.getCreateTime()); 71 | 72 | List permissions = 73 | new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 19, 88, 86, 89, 90, 91, 92)); 74 | 75 | user.setPermissions(permissions); 76 | client.send(client.randomNode(), MsgId.CREATE_USER, user, 5000); 77 | } 78 | 79 | @Benchmark 80 | @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime}) 81 | public void getUser() throws Throwable { 82 | int id = counter.getAndIncrement(); 83 | client.send(client.randomNode(), MsgId.GET_USER, id, 5000); 84 | } 85 | 86 | @Benchmark 87 | @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime}) 88 | public void listUser() throws Throwable { 89 | int pageNo = counter.getAndIncrement(); 90 | client.send(client.randomNode(), MsgId.LIST_USER, pageNo, 5000); 91 | } 92 | 93 | public static void main(String[] args) throws Throwable { 94 | Options opt = 95 | new OptionsBuilder() 96 | .timeUnit(TimeUnit.MILLISECONDS) 97 | .include(BenchmarkTest.class.getSimpleName()) 98 | .warmupIterations(3) // 99 | .warmupTime(TimeValue.seconds(3)) // 100 | .measurementIterations(5) // 101 | .measurementTime(TimeValue.seconds(5)) // 102 | .threads(CONCURRENCY) 103 | .forks(1) 104 | .build(); 105 | new Runner(opt).run(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/codec/RpcDecoder.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.codec; 2 | 3 | import static cn.ziav.rpc.common.Constants.FLAG_REQUEST; 4 | import static cn.ziav.rpc.common.Constants.FLAG_TWOWAY; 5 | import static cn.ziav.rpc.common.Constants.HEADER_LENGTH; 6 | import static cn.ziav.rpc.common.Constants.MAGIC_HIGH; 7 | import static cn.ziav.rpc.common.Constants.MAGIC_LOW; 8 | import static java.nio.ByteOrder.LITTLE_ENDIAN; 9 | 10 | import cn.ziav.rpc.common.Constants; 11 | import cn.ziav.rpc.common.MsgRequest; 12 | import cn.ziav.rpc.common.MsgResponse; 13 | import cn.ziav.rpc.utils.Bytes; 14 | import com.google.common.base.Preconditions; 15 | import io.netty.buffer.ByteBuf; 16 | import io.netty.channel.ChannelHandlerContext; 17 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 18 | import io.protostuff.GraphIOUtil; 19 | import io.protostuff.Schema; 20 | import io.protostuff.runtime.RuntimeSchema; 21 | 22 | /** 23 | * 协议解码器 24 | * 25 | * @author Zavi 26 | */ 27 | public class RpcDecoder extends LengthFieldBasedFrameDecoder { 28 | 29 | public RpcDecoder() { 30 | // 这里使用小端字节序,效率更高 31 | super(LITTLE_ENDIAN, Constants.DEFAULT_PAYLOAD, 0, 4, 0, 4, true); 32 | } 33 | 34 | @Override 35 | public Object decode(ChannelHandlerContext ctx, ByteBuf input) throws Exception { 36 | try { 37 | input = (ByteBuf) super.decode(ctx, input); 38 | if (input == null) { 39 | return null; 40 | } 41 | 42 | input.markReaderIndex(); 43 | int readable = input.readableBytes(); 44 | 45 | // 创建消息头字节数组 46 | byte[] header = new byte[Math.min(readable, HEADER_LENGTH)]; 47 | // 读取消息头数据 48 | input.readBytes(header); 49 | 50 | // 检查魔数是否相等,不相等说明请求非法,直接断联 51 | if (readable > 0 && header[0] != MAGIC_HIGH || readable > 1 && header[1] != MAGIC_LOW) { 52 | ctx.disconnect(); 53 | return null; 54 | } 55 | 56 | // 检测可读数据量是否少于消息头长度,若小于则立即返回 57 | if (readable < HEADER_LENGTH) { 58 | input.resetReaderIndex(); 59 | return null; 60 | } 61 | 62 | // 从消息头中获取消息体长度 63 | int len = Bytes.bytes2int(header, 16); 64 | int tt = len + HEADER_LENGTH; 65 | // 检查数据大小是否超限 66 | Preconditions.checkArgument( 67 | tt <= Constants.DEFAULT_PAYLOAD, "数据大小超过上限%sM", Constants.DEFAULT_PAYLOAD / 1024 / 1024); 68 | 69 | // 获取协议头的数据包类型、请求编号、业务编号 70 | byte flag = header[2]; 71 | long id = Bytes.bytes2long(header, 4); 72 | int mId = Bytes.bytes2int(header, 12); 73 | // 通过逻辑与运算判断是否为请求消息 74 | if ((flag & FLAG_REQUEST) != 0) { 75 | // 创建MsgRequest对象 76 | MsgRequest req = new MsgRequest(id, mId); 77 | // 通过逻辑与运算得到通信方式,并设置到 Request 对象中 78 | req.mTwoWay = (flag & FLAG_TWOWAY) != 0; 79 | 80 | // 通过ProtoStuff反序列化请求体 81 | Schema schema = RuntimeSchema.getSchema(Wrapper.class); 82 | Wrapper wrapper = schema.newMessage(); 83 | 84 | byte[] bytes = new byte[len]; 85 | input.readBytes(bytes); 86 | GraphIOUtil.mergeFrom(bytes, wrapper, schema); 87 | req.mData = wrapper.getData(); 88 | return req; 89 | } else { 90 | // 创建MsgResponse对象 91 | MsgResponse resp = new MsgResponse(id, mId); 92 | // 设置对象状态 93 | resp.mStatus = header[3]; 94 | // 只要消息体长度>1就进行反序列化 95 | if (len > 0) { 96 | // 反序列化响应对象的具体内容 97 | Schema schema = RuntimeSchema.getSchema(Wrapper.class); 98 | byte[] bytes = new byte[len]; 99 | input.readBytes(bytes); 100 | Wrapper wrapper = schema.newMessage(); 101 | GraphIOUtil.mergeFrom(bytes, wrapper, schema); 102 | resp.mData = wrapper.getData(); 103 | } 104 | return resp; 105 | } 106 | } finally { 107 | if (input != null) { 108 | input.release(); 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/codec/RpcEncoder.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.codec; 2 | 3 | import static cn.ziav.rpc.common.Constants.FLAG_REQUEST; 4 | import static cn.ziav.rpc.common.Constants.FLAG_TWOWAY; 5 | import static cn.ziav.rpc.common.Constants.HEADER_LENGTH; 6 | import static cn.ziav.rpc.common.Constants.MAGIC; 7 | import static com.google.common.base.Preconditions.checkArgument; 8 | 9 | import cn.ziav.rpc.common.Constants; 10 | import cn.ziav.rpc.common.MsgRequest; 11 | import cn.ziav.rpc.common.MsgResponse; 12 | import cn.ziav.rpc.utils.Bytes; 13 | import com.google.common.base.Preconditions; 14 | import io.netty.buffer.ByteBuf; 15 | import io.netty.channel.ChannelHandler.Sharable; 16 | import io.netty.channel.ChannelHandlerContext; 17 | import io.netty.handler.codec.MessageToByteEncoder; 18 | import io.protostuff.GraphIOUtil; 19 | import io.protostuff.LinkedBuffer; 20 | import io.protostuff.Schema; 21 | import io.protostuff.runtime.RuntimeSchema; 22 | 23 | /** 24 | * 协议编码 25 | * 26 | * @author Zavi 27 | */ 28 | @Sharable 29 | public class RpcEncoder extends MessageToByteEncoder { 30 | 31 | @Override 32 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 33 | if (msg instanceof MsgRequest) { 34 | // 创建消息头字节数组,长度20个字节 35 | byte[] header = new byte[HEADER_LENGTH]; 36 | // 设置魔数 37 | Bytes.short2bytes(MAGIC, header); 38 | 39 | // 设置数据包类型(Request/Response) 40 | header[2] = FLAG_REQUEST; 41 | 42 | // 设置通信方式(单向/双向) 43 | if (((MsgRequest) msg).mTwoWay) { 44 | header[2] |= FLAG_TWOWAY; 45 | } 46 | 47 | // 设置请求编号,8个字节,从第4个字节开始设置 48 | Bytes.long2bytes(((MsgRequest) msg).id, header, 4); 49 | // 设置业务消息编号,4个字节,从第12个字节开始设置 50 | Bytes.int2bytes(((MsgRequest) msg).mId, header, 12); 51 | 52 | // 分配序列化所需的Buffer,默认大小512 53 | LinkedBuffer buffer = LinkedBuffer.allocate(); 54 | Schema schema = RuntimeSchema.getSchema(Wrapper.class); 55 | // 将请求体用Wrapper包装 56 | Wrapper wrapper = new Wrapper<>(((MsgRequest) msg).mData); 57 | try { 58 | // 通过ProtoStuff序列化wrapper对象 59 | byte[] bytes = GraphIOUtil.toByteArray(wrapper, schema, buffer); 60 | // 检查最终发起请求的数据包大小是否超过上限 61 | int tt = bytes.length + HEADER_LENGTH; 62 | Preconditions.checkArgument( 63 | tt <= Constants.DEFAULT_PAYLOAD, 64 | "数据大小超过上限%sM", 65 | Constants.DEFAULT_PAYLOAD / 1024 / 1024); 66 | // 设置请求体长度,4个字节,从第16个字节开始设置 67 | Bytes.int2bytes(bytes.length, header, 16); 68 | 69 | // 写入数据缓冲区 70 | out.writeIntLE(tt); 71 | out.writeBytes(header); 72 | out.writeBytes(bytes); 73 | } finally { 74 | // 清除序列化buffer 75 | buffer.clear(); 76 | } 77 | } 78 | 79 | if (msg instanceof MsgResponse) { 80 | // 创建消息头字节数组,长度20个字节 81 | byte[] header = new byte[HEADER_LENGTH]; 82 | // 设置魔数 83 | Bytes.short2bytes(MAGIC, header); 84 | // 设置响应状态码,长度1个字节,从第3个字节开始设置 85 | byte status = ((MsgResponse) msg).mStatus; 86 | header[3] = status; 87 | // 设置请求编号,8个字节,从第4个字节开始设置 88 | Bytes.long2bytes(((MsgResponse) msg).id, header, 4); 89 | // 设置业务消息编号,4个字节,从第12个字节开始设置 90 | Bytes.int2bytes(((MsgResponse) msg).mId, header, 12); 91 | 92 | // 分配序列化所需的Buffer,默认大小512 93 | LinkedBuffer buffer = LinkedBuffer.allocate(); 94 | Schema schema = RuntimeSchema.getSchema(Wrapper.class); 95 | // 将请求体用Wrapper包装 96 | Wrapper wrapper = new Wrapper<>(((MsgResponse) msg).mData); 97 | try { 98 | // 通过ProtoStuff序列化wrapper对象 99 | byte[] bytes = GraphIOUtil.toByteArray(wrapper, schema, buffer); 100 | // 检查最终发起请求的数据包大小是否超过上限 101 | int tt = bytes.length + HEADER_LENGTH; 102 | checkArgument( 103 | tt <= Constants.DEFAULT_PAYLOAD, 104 | "数据大小超过上限%sM", 105 | Constants.DEFAULT_PAYLOAD / 1024 / 1024); 106 | // 设置请求体长度,4个字节,从第16个字节开始设置 107 | Bytes.int2bytes(bytes.length, header, 16); 108 | // 写入数据缓冲区 109 | out.writeIntLE(tt); 110 | out.writeBytes(header); 111 | out.writeBytes(bytes); 112 | } finally { 113 | // 清除序列化buffer 114 | buffer.clear(); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/cn/ziav/rpc/InvokeTest.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc; 2 | 3 | import static cn.ziav.rpc.Constant.localIp; 4 | import static cn.ziav.rpc.Constant.port; 5 | import static cn.ziav.rpc.Constant.topic; 6 | import static cn.ziav.rpc.Constant.zkAddr; 7 | import static cn.ziav.rpc.exception.ExceptionCode.TIME_OUT; 8 | 9 | import cn.ziav.rpc.bean.HelloReq; 10 | import cn.ziav.rpc.bean.HelloResp; 11 | import cn.ziav.rpc.client.EasyRpcCallback; 12 | import cn.ziav.rpc.client.RpcClient; 13 | import cn.ziav.rpc.exception.RemotingException; 14 | import cn.ziav.rpc.handler.ExceptionMsgHandler; 15 | import cn.ziav.rpc.handler.HelloMsgHandler; 16 | import cn.ziav.rpc.handler.MsgId; 17 | import cn.ziav.rpc.handler.MultiThreadMsgHandler; 18 | import cn.ziav.rpc.handler.TimeoutMsgHandler; 19 | import cn.ziav.rpc.server.RpcServer; 20 | import java.util.concurrent.CountDownLatch; 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.BeforeAll; 23 | import org.junit.jupiter.api.Test; 24 | 25 | public class InvokeTest { 26 | 27 | @BeforeAll 28 | static void initServer() throws Throwable { 29 | RpcServer server = new RpcServer(zkAddr, topic, localIp, port); 30 | server.register(new HelloMsgHandler()); 31 | server.register(new TimeoutMsgHandler()); 32 | server.register(new ExceptionMsgHandler()); 33 | server.register(new MultiThreadMsgHandler()); 34 | } 35 | 36 | @Test 37 | void testSync() throws Throwable { 38 | RpcClient client = new RpcClient(zkAddr, topic); 39 | HelloReq helloReq = new HelloReq(); 40 | helloReq.msg = "ping"; 41 | HelloResp resp = client.send(client.randomNode(), MsgId.HELLO, helloReq, 3000); 42 | Assertions.assertEquals(resp.msg, "pong"); 43 | } 44 | 45 | @Test 46 | void testAsync() throws Throwable { 47 | RpcClient client = new RpcClient(zkAddr, topic); 48 | HelloReq helloReq = new HelloReq(); 49 | helloReq.msg = "ping"; 50 | CountDownLatch latch = new CountDownLatch(1); 51 | client.sendAsync( 52 | client.randomNode(), 53 | MsgId.HELLO, 54 | helloReq, 55 | 3000, 56 | new EasyRpcCallback() { 57 | @Override 58 | public void success(HelloResp result) { 59 | Assertions.assertEquals(result.msg, "pong"); 60 | latch.countDown(); 61 | } 62 | 63 | @Override 64 | public void fail(Throwable throwable) {} 65 | }); 66 | latch.await(); 67 | } 68 | 69 | @Test 70 | void testTimeout() throws Throwable { 71 | RpcClient client = new RpcClient(zkAddr, topic); 72 | RemotingException remotingException = 73 | Assertions.assertThrows( 74 | RemotingException.class, 75 | () -> client.send(client.randomNode(), MsgId.TIMEOUT, "ping", 1)); 76 | Assertions.assertEquals(TIME_OUT, remotingException.code); 77 | } 78 | 79 | @Test 80 | void testException() throws Throwable { 81 | RpcClient client = new RpcClient(zkAddr, topic); 82 | CountDownLatch latch = new CountDownLatch(1); 83 | client.sendAsync( 84 | client.randomNode(), 85 | MsgId.EXCEPTION, 86 | "", 87 | 3000, 88 | new EasyRpcCallback() { 89 | @Override 90 | public void success(HelloResp result) {} 91 | 92 | @Override 93 | public void fail(Throwable throwable) { 94 | Assertions.assertTrue(throwable instanceof IllegalStateException); 95 | latch.countDown(); 96 | } 97 | }); 98 | latch.await(); 99 | } 100 | 101 | @Test 102 | void multiThreadTest() throws Throwable { 103 | CountDownLatch latch = new CountDownLatch(5); 104 | RpcClient client = new RpcClient(zkAddr, topic); 105 | 106 | for (int i = 0; i < 5; i++) { 107 | int threadId = i; 108 | new Thread( 109 | () -> { 110 | try { 111 | String result = 112 | client.send(client.lowLatencyNode(), MsgId.MULTI_THREAD, threadId + "", 3000); 113 | Assertions.assertEquals(result, "thread-" + threadId); 114 | latch.countDown(); 115 | } catch (Throwable throwable) { 116 | System.err.println(throwable); 117 | } 118 | }) 119 | .start(); 120 | } 121 | latch.await(); 122 | } 123 | 124 | @Test 125 | public void testDiscovery() throws Throwable { 126 | RpcClient client = new RpcClient(zkAddr, topic); 127 | String node = client.randomNode(); 128 | Assertions.assertEquals("127.0.0.1:8812", node); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/server/RpcDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cn.ziav.rpc.server; 18 | 19 | import static cn.ziav.rpc.common.MsgResponse.SERVER_ERROR; 20 | import static com.google.common.base.Preconditions.checkArgument; 21 | 22 | import cn.ziav.rpc.common.MsgRequest; 23 | import cn.ziav.rpc.common.MsgResponse; 24 | import cn.ziav.rpc.exception.ExceptionCode; 25 | import cn.ziav.rpc.exception.RemotingException; 26 | import io.netty.channel.Channel; 27 | import io.netty.channel.ChannelDuplexHandler; 28 | import io.netty.channel.ChannelHandlerContext; 29 | import io.netty.handler.timeout.IdleStateEvent; 30 | import java.net.InetSocketAddress; 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | import java.util.concurrent.ConcurrentHashMap; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | 37 | /** 消息分发器 */ 38 | @io.netty.channel.ChannelHandler.Sharable 39 | public class RpcDispatcher extends ChannelDuplexHandler { 40 | private static final Logger logger = LoggerFactory.getLogger(RpcDispatcher.class); 41 | /** 消息处理器: */ 42 | private final Map msgHandlerMap = new HashMap<>(); 43 | 44 | /** */ 45 | private final Map channelMap = new ConcurrentHashMap<>(); 46 | 47 | public void register(IMsgHandler handler) { 48 | IMsgHandler pre = msgHandlerMap.putIfAbsent(handler.msgId(), handler); 49 | checkArgument(pre == null, "重复的消息处理器,msgId=%s", handler.msgId()); 50 | logger.info("消息处理器{}注册成功", handler.msgId()); 51 | } 52 | 53 | @Override 54 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 55 | super.channelActive(ctx); 56 | InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); 57 | String key = socketAddress.getAddress().getHostAddress() + ":" + socketAddress.getPort(); 58 | channelMap.put(key, ctx.channel()); 59 | } 60 | 61 | @Override 62 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 63 | InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); 64 | String key = socketAddress.getAddress().getHostAddress() + ":" + socketAddress.getPort(); 65 | channelMap.remove(key); 66 | } 67 | 68 | @Override 69 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 70 | if (msg instanceof MsgRequest) { 71 | MsgRequest msgReq = (MsgRequest) msg; 72 | // 根据mId获取Handler 73 | IMsgHandler msgHandler = msgHandlerMap.get(msgReq.mId); 74 | MsgResponse msgResponse = new MsgResponse<>(msgReq.id, msgReq.mId); 75 | if (msgHandler == null) { 76 | logger.warn("no msg handler found, mid={}", msgReq.mId); 77 | msgResponse.mStatus = SERVER_ERROR; 78 | msgResponse.mData = 79 | new RemotingException(ExceptionCode.NO_SUCH_HANDLER, "mid=" + msgReq.mId); 80 | ctx.writeAndFlush(msgResponse); 81 | return; 82 | } 83 | 84 | // 在Handler的线程池中,执行process方法 85 | msgHandler 86 | .threadPool() 87 | .submit( 88 | () -> { 89 | try { 90 | msgResponse.mData = msgHandler.process(msgReq.mData); 91 | } catch (Throwable throwable) { 92 | logger.error("", throwable); 93 | msgResponse.mStatus = SERVER_ERROR; 94 | msgResponse.mData = throwable; 95 | } 96 | // 写入缓冲区 97 | ctx.writeAndFlush(msgResponse); 98 | }); 99 | } 100 | } 101 | 102 | @Override 103 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 104 | if (evt instanceof IdleStateEvent) { 105 | InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress(); 106 | String key = socketAddress.getAddress().getHostAddress() + ":" + socketAddress.getPort(); 107 | Channel channel = channelMap.getOrDefault(key, ctx.channel()); 108 | channel.close(); 109 | channelMap.remove(key); 110 | } 111 | super.userEventTriggered(ctx, evt); 112 | } 113 | 114 | @Override 115 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 116 | logger.error("", cause); 117 | ctx.close(); 118 | } 119 | 120 | public Map getChannels() { 121 | return channelMap; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/client/RpcClientHandler.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.client; 2 | 3 | import cn.ziav.rpc.common.MsgRequest; 4 | import cn.ziav.rpc.common.MsgResponse; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelDuplexHandler; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelOutboundInvoker; 9 | import io.netty.channel.ChannelPromise; 10 | import io.netty.handler.timeout.IdleStateEvent; 11 | import java.net.InetSocketAddress; 12 | import java.util.Map; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** @author Zavi */ 18 | @io.netty.channel.ChannelHandler.Sharable 19 | public class RpcClientHandler extends ChannelDuplexHandler { 20 | private static final Logger logger = LoggerFactory.getLogger(RpcClientHandler.class); 21 | /** */ 22 | private final Map CHANNEL_MAP = new ConcurrentHashMap<>(); 23 | 24 | @Override 25 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 26 | String addr = toRemoteAddrString(ctx.channel()); 27 | getOrAddChannel(addr, ctx.channel()); 28 | } 29 | 30 | @Override 31 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 32 | String addr = toRemoteAddrString(ctx.channel()); 33 | CHANNEL_MAP.remove(addr); 34 | } 35 | 36 | /** 37 | * 将Channel转换成host:port字符串 38 | * 39 | * @param channel 40 | * @return 41 | */ 42 | private String toRemoteAddrString(Channel channel) { 43 | InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress(); 44 | String host = socketAddress.getHostString(); 45 | int port = socketAddress.getPort(); 46 | return host + ":" + port; 47 | } 48 | 49 | @Override 50 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) 51 | throws Exception { 52 | super.write(ctx, msg, promise); 53 | final boolean isRequest = msg instanceof MsgRequest; 54 | promise.addListener( 55 | future -> { 56 | if (future.isSuccess()) { 57 | return; 58 | } 59 | 60 | Throwable t = future.cause(); 61 | if (t != null && isRequest) { 62 | MsgRequest request = (MsgRequest) msg; 63 | MsgResponse response = buildErrorResponse(request, t); 64 | RpcFuture rpcFuture = RpcFuture.getFuture(response.id); 65 | if (rpcFuture.executor == null) { 66 | RpcFuture.received((response), false); 67 | } else { 68 | rpcFuture.executor.submit(() -> RpcFuture.received(response, false)); 69 | } 70 | } 71 | }); 72 | } 73 | 74 | /** 75 | * 构建一个请求异常的响应 76 | * 77 | * @param request 请求体 78 | * @param t 大多都是序列化的异常 79 | * @return 80 | */ 81 | private static MsgResponse buildErrorResponse(MsgRequest request, Throwable t) { 82 | MsgResponse response = new MsgResponse<>(request.id, request.mId, t); 83 | response.mStatus = MsgResponse.BAD_REQUEST; 84 | return response; 85 | } 86 | 87 | @Override 88 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 89 | if (msg instanceof MsgResponse) { 90 | RpcFuture future = RpcFuture.getFuture(((MsgResponse) msg).id); 91 | if (future == null) { 92 | logger.warn("future has been removed, mId={}", ((MsgResponse) msg).mId); 93 | return; 94 | } 95 | if (future.executor == null) { 96 | RpcFuture.received(((MsgResponse) msg), false); 97 | } else { 98 | future.executor.submit(() -> RpcFuture.received(((MsgResponse) msg), false)); 99 | } 100 | } 101 | } 102 | 103 | @Override 104 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 105 | Channel channel = ctx.channel(); 106 | removeChannelIfDisconnected(channel); 107 | logger.error("", cause); 108 | } 109 | 110 | @Override 111 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 112 | // send heartbeat when read idle. 113 | if (evt instanceof IdleStateEvent) { 114 | Channel channel = ctx.channel(); 115 | try { 116 | MsgRequest req = new MsgRequest(); 117 | req.mId = -1; 118 | req.mTwoWay = true; 119 | // 心跳处理 120 | channel.writeAndFlush(req); 121 | } finally { 122 | removeChannelIfDisconnected(channel); 123 | } 124 | return; 125 | } 126 | super.userEventTriggered(ctx, evt); 127 | } 128 | 129 | /** 130 | * 如果失联了则移除Channel 131 | * 132 | * @param channel 133 | */ 134 | private void removeChannelIfDisconnected(Channel channel) { 135 | if (channel != null && !channel.isActive()) { 136 | String address = toRemoteAddrString(channel); 137 | CHANNEL_MAP.remove(address); 138 | } 139 | } 140 | 141 | /** 142 | * 获取Channel 143 | * 144 | * @param addr 145 | * @return 146 | */ 147 | public Channel getChannel(String addr) { 148 | return CHANNEL_MAP.get(addr); 149 | } 150 | 151 | /** 152 | * 获取或添加Channel 153 | * 154 | * @param addr 155 | * @param channel 156 | * @return 157 | */ 158 | public Channel getOrAddChannel(String addr, Channel channel) { 159 | Channel pre = CHANNEL_MAP.putIfAbsent(addr, channel); 160 | return pre != null ? pre : channel; 161 | } 162 | 163 | public void closeChannel() { 164 | CHANNEL_MAP.values().forEach(ChannelOutboundInvoker::close); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/server/RpcServer.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.server; 2 | 3 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 4 | 5 | import cn.ziav.rpc.codec.RpcDecoder; 6 | import cn.ziav.rpc.codec.RpcEncoder; 7 | import cn.ziav.rpc.common.Constants; 8 | import io.netty.bootstrap.ServerBootstrap; 9 | import io.netty.buffer.PooledByteBufAllocator; 10 | import io.netty.channel.Channel; 11 | import io.netty.channel.ChannelFuture; 12 | import io.netty.channel.ChannelInitializer; 13 | import io.netty.channel.ChannelOption; 14 | import io.netty.channel.EventLoopGroup; 15 | import io.netty.channel.nio.NioEventLoopGroup; 16 | import io.netty.channel.socket.nio.NioServerSocketChannel; 17 | import io.netty.channel.socket.nio.NioSocketChannel; 18 | import io.netty.handler.timeout.IdleStateHandler; 19 | import io.netty.util.concurrent.DefaultThreadFactory; 20 | import java.util.Map; 21 | import java.util.concurrent.CountDownLatch; 22 | import org.apache.zookeeper.CreateMode; 23 | import org.apache.zookeeper.Watcher.Event.KeeperState; 24 | import org.apache.zookeeper.ZooDefs; 25 | import org.apache.zookeeper.ZooKeeper; 26 | import org.apache.zookeeper.data.Stat; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | /** @author Zavi */ 31 | public class RpcServer { 32 | 33 | private static final Logger logger = LoggerFactory.getLogger(RpcServer.class); 34 | /** 主机地址 */ 35 | private final String host; 36 | /** 端口 */ 37 | private final int port; 38 | /** zookeeper连接地址 */ 39 | private final String zkAddr; 40 | 41 | private final String topic; 42 | 43 | /** worker通道 */ 44 | private Map channels; 45 | /** Server启动器 */ 46 | private ServerBootstrap bootstrap; 47 | /** boss通道:用于接收连接和分发给worker通道 */ 48 | private Channel channel; 49 | /** boss线程池 */ 50 | private EventLoopGroup bossGroup; 51 | /** worker线程池 */ 52 | private EventLoopGroup workerGroup; 53 | /** Netty Server处理器 */ 54 | private RpcDispatcher rpcDispatcher; 55 | 56 | /** 57 | * 初始化一个Server 58 | * 59 | * @param zkAddr 注册中心地址 60 | * @param topic 需要注册的topic 61 | * @param host 主机地址 62 | * @param port 端口 63 | * @throws Throwable 64 | */ 65 | public RpcServer(String zkAddr, String topic, String host, int port) throws Throwable { 66 | this.zkAddr = zkAddr; 67 | this.host = host; 68 | this.port = port; 69 | this.topic = topic; 70 | this.bootstrap = new ServerBootstrap(); 71 | 72 | this.bossGroup = new NioEventLoopGroup(1, new DefaultThreadFactory("NettyServerBoss", false)); 73 | this.workerGroup = 74 | new NioEventLoopGroup( 75 | Constants.DEFAULT_IO_THREADS, new DefaultThreadFactory("NettyServerWorker", true)); 76 | 77 | this.rpcDispatcher = new RpcDispatcher(); 78 | this.channels = rpcDispatcher.getChannels(); 79 | this.start(); 80 | } 81 | 82 | /** 启动服务器 */ 83 | private void start() throws Throwable { 84 | bootstrap 85 | // 设置线程池组 86 | .group(bossGroup, workerGroup) 87 | // 设置非阻塞的连接套接字通道 88 | .channel(NioServerSocketChannel.class) 89 | // 禁用Nagle算法 90 | .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE) 91 | // 端口复用 92 | .childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE) 93 | // 设置缓冲区分配器 94 | .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 95 | // 设置处理请求的Handler 96 | .childHandler( 97 | new ChannelInitializer() { 98 | @Override 99 | protected void initChannel(NioSocketChannel ch) throws Exception { 100 | // 心跳检测时间3分钟 101 | int idleTimeout = Constants.DEFAULT_HEARTBEAT * 3; 102 | ch.pipeline() 103 | // 解码器 104 | .addLast("decoder", new RpcDecoder()) 105 | // 编码器 106 | .addLast("encoder", new RpcEncoder()) 107 | // 心跳检测 108 | .addLast( 109 | "server-idle-handler", 110 | new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS)) 111 | // 消息分发器 112 | .addLast("dispatcher", rpcDispatcher); 113 | } 114 | }); 115 | // 绑定主机和端口 116 | ChannelFuture channelFuture = bootstrap.bind(host, port); 117 | channelFuture.syncUninterruptibly(); 118 | channel = channelFuture.channel(); 119 | registerToZookeeper(); 120 | } 121 | 122 | private void registerToZookeeper() throws Throwable { 123 | CountDownLatch latch = new CountDownLatch(1); 124 | // 初始化ZooKeeper 125 | ZooKeeper zk = 126 | new ZooKeeper( 127 | zkAddr, 128 | Constants.ZK_SESSION_TIMEOUT, 129 | event -> { 130 | KeeperState state = event.getState(); 131 | if (state == KeeperState.SyncConnected) { 132 | latch.countDown(); 133 | return; 134 | } 135 | 136 | if (state == KeeperState.Disconnected || state == KeeperState.Expired) { 137 | try { 138 | // 自动重连 139 | registerToZookeeper(); 140 | } catch (Throwable throwable) { 141 | logger.error("reconnect zk failed", throwable); 142 | } 143 | } 144 | }); 145 | latch.await(); 146 | 147 | // 创建/easy-rpc节点 148 | String path = Constants.ZK_REGISTRY_PATH; 149 | Stat s = zk.exists(path, false); 150 | if (s == null) { 151 | zk.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 152 | } 153 | 154 | // 创建/easy-rpc/{topic}节点 155 | path += "/" + topic; 156 | s = zk.exists(path, false); 157 | if (s == null) { 158 | zk.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 159 | } 160 | 161 | // 创建/easy-rpc/{topic}/{ip:port}节点 162 | path += "/" + host + ":" + port; 163 | s = zk.exists(path, false); 164 | if (s == null) { 165 | zk.create(path, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); 166 | } 167 | } 168 | 169 | public void doClose() { 170 | try { 171 | if (channel != null) { 172 | // unbind. 173 | channel.close(); 174 | } 175 | 176 | channels.forEach((addr, channel1) -> channel1.close()); 177 | 178 | if (bootstrap != null) { 179 | bossGroup.shutdownGracefully(); 180 | workerGroup.shutdownGracefully(); 181 | } 182 | 183 | if (channels != null) { 184 | channels.clear(); 185 | } 186 | } catch (Throwable e) { 187 | logger.warn(e.getMessage(), e); 188 | } 189 | } 190 | 191 | public void register(IMsgHandler handler) { 192 | rpcDispatcher.register(handler); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/client/RpcFuture.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.client; 2 | 3 | import static cn.ziav.rpc.exception.ExceptionCode.TIME_OUT; 4 | import static cn.ziav.rpc.exception.ExceptionCode.UNKNOWN_ERROR; 5 | 6 | import cn.ziav.rpc.common.MsgRequest; 7 | import cn.ziav.rpc.common.MsgResponse; 8 | import cn.ziav.rpc.exception.RemotingException; 9 | import cn.ziav.rpc.timer.HashedWheelTimer; 10 | import cn.ziav.rpc.timer.Timeout; 11 | import cn.ziav.rpc.timer.Timer; 12 | import cn.ziav.rpc.timer.TimerTask; 13 | import cn.ziav.rpc.utils.NamedThreadFactory; 14 | import java.text.SimpleDateFormat; 15 | import java.util.Date; 16 | import java.util.Map; 17 | import java.util.concurrent.CompletableFuture; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.TimeUnit; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | /** 25 | * RpcFuture 26 | * 27 | * @author Zavi 28 | */ 29 | public class RpcFuture extends CompletableFuture { 30 | private static final Logger logger = LoggerFactory.getLogger(RpcFuture.class); 31 | 32 | /** */ 33 | private static final Map FUTURES = new ConcurrentHashMap<>(); 34 | 35 | public final long id; 36 | public final MsgRequest request; 37 | private final int timeout; 38 | private volatile long sent; 39 | private Timeout timeoutCheckTask; 40 | public ExecutorService executor; 41 | public EasyRpcCallback callback; 42 | 43 | /** 超时计时器 */ 44 | public static final Timer TIME_OUT_TIMER = 45 | new HashedWheelTimer( 46 | new NamedThreadFactory("easyRpc-future-timeout", true), 30, TimeUnit.MILLISECONDS); 47 | 48 | private ServerNode serverNode; 49 | 50 | public RpcFuture(MsgRequest request, int timeout) { 51 | this.id = request.id; 52 | this.request = request; 53 | this.timeout = timeout; 54 | FUTURES.put(id, this); 55 | } 56 | 57 | public static RpcFuture newFuture( 58 | MsgRequest request, 59 | EasyRpcCallback callback, 60 | int timeout, 61 | ServerNode serverNode, 62 | ExecutorService executor) { 63 | final RpcFuture future = new RpcFuture<>(request, timeout); 64 | future.executor = executor; 65 | future.callback = callback; 66 | future.serverNode = serverNode; 67 | // 启动超时检查任务 68 | timeoutCheck(future); 69 | return future; 70 | } 71 | 72 | private static void timeoutCheck(RpcFuture future) { 73 | // 初始化任务 74 | TimeoutCheckTask task = new TimeoutCheckTask(future.id); 75 | // 启动任务 76 | future.timeoutCheckTask = 77 | TIME_OUT_TIMER.newTimeout(task, future.timeout, TimeUnit.MILLISECONDS); 78 | } 79 | 80 | public static RpcFuture getFuture(long id) { 81 | return FUTURES.get(id); 82 | } 83 | 84 | @Override 85 | public boolean cancel(boolean mayInterruptIfRunning) { 86 | MsgResponse errorResult = new MsgResponse<>(id, request.mId); 87 | errorResult.mStatus = MsgResponse.CLIENT_ERROR; 88 | doReceived(errorResult); 89 | FUTURES.remove(id); 90 | return true; 91 | } 92 | 93 | public static void received(MsgResponse response, boolean timeout) { 94 | RpcFuture future = FUTURES.remove(response.id); 95 | if (future != null) { 96 | Timeout t = future.timeoutCheckTask; 97 | // 这里已经拿到结果了,如果不是超时的话,就cancel掉之前的超时任务 98 | if (!timeout) { 99 | t.cancel(); 100 | } 101 | 102 | future.doReceived(response); 103 | } else { 104 | logger.warn( 105 | "The timeout response finally returned at {}", 106 | new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); 107 | } 108 | } 109 | 110 | private void doReceived(MsgResponse res) { 111 | if (res == null) { 112 | throw new IllegalStateException("response cannot be null"); 113 | } 114 | 115 | if (serverNode != null && isSent()) { 116 | long cost = System.currentTimeMillis() - sent; 117 | serverNode.addCost(cost); 118 | } 119 | 120 | if (res.mStatus == MsgResponse.OK) { 121 | complete(res.mData); 122 | return; 123 | } 124 | if (res.mStatus == MsgResponse.CLIENT_TIMEOUT) { 125 | completeExceptionally(new RemotingException(TIME_OUT, "client timeout")); 126 | } 127 | if (res.mStatus == MsgResponse.SERVER_TIMEOUT) { 128 | completeExceptionally(new RemotingException(TIME_OUT, "server timeout")); 129 | return; 130 | } 131 | 132 | if (res.mData instanceof Throwable) { 133 | this.completeExceptionally((Throwable) res.mData); 134 | return; 135 | } 136 | 137 | this.completeExceptionally( 138 | new RemotingException(UNKNOWN_ERROR, "statusCode=" + res.mStatus + " data=" + res.mData)); 139 | } 140 | 141 | @Override 142 | public boolean complete(R value) { 143 | if (callback != null) { 144 | callback.success(value); 145 | } 146 | return super.complete(value); 147 | } 148 | 149 | @Override 150 | public boolean completeExceptionally(Throwable ex) { 151 | if (callback != null) { 152 | callback.fail(ex); 153 | } 154 | return super.completeExceptionally(ex); 155 | } 156 | 157 | public static void sent(MsgRequest request) { 158 | RpcFuture future = FUTURES.get(request.id); 159 | if (future != null) { 160 | future.doSent(); 161 | } 162 | } 163 | 164 | private void doSent() { 165 | sent = System.currentTimeMillis(); 166 | } 167 | 168 | /** 169 | * 是否已发送 170 | * 171 | * @return 172 | */ 173 | private boolean isSent() { 174 | return sent > 0; 175 | } 176 | 177 | /** 超时任务 */ 178 | private static class TimeoutCheckTask implements TimerTask { 179 | 180 | private final Long requestID; 181 | 182 | TimeoutCheckTask(Long requestID) { 183 | this.requestID = requestID; 184 | } 185 | 186 | @Override 187 | public void run(Timeout timeout) { 188 | RpcFuture future = RpcFuture.getFuture(requestID); 189 | // 判断future是否已完成 190 | if (future == null || future.isDone()) { 191 | return; 192 | } 193 | 194 | if (future.executor != null) { 195 | future.executor.execute(() -> notifyTimeout(future)); 196 | } else { 197 | notifyTimeout(future); 198 | } 199 | } 200 | 201 | /** 202 | * 通知超时 203 | * 204 | * @param future 205 | */ 206 | private void notifyTimeout(RpcFuture future) { 207 | // 1. 创建异常响应对象 208 | MsgResponse timeoutResponse = new MsgResponse(future.id, future.getMid()); 209 | // 2. 设置超时状态码 210 | timeoutResponse.mStatus = 211 | future.isSent() ? MsgResponse.SERVER_TIMEOUT : MsgResponse.CLIENT_TIMEOUT; 212 | // 3. 处理响应 213 | RpcFuture.received(timeoutResponse, true); 214 | } 215 | } 216 | 217 | private int getMid() { 218 | return request.mId; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/client/RpcClient.java: -------------------------------------------------------------------------------- 1 | package cn.ziav.rpc.client; 2 | 3 | import static cn.ziav.rpc.common.Constants.DEFAULT_CONNECT_TIMEOUT; 4 | import static cn.ziav.rpc.common.Constants.DEFAULT_HEARTBEAT; 5 | import static cn.ziav.rpc.exception.ExceptionCode.CLIENT_CONNECTED_FAILED; 6 | import static cn.ziav.rpc.exception.ExceptionCode.CLIENT_HAS_CLOSED; 7 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 8 | 9 | import cn.ziav.rpc.codec.RpcDecoder; 10 | import cn.ziav.rpc.codec.RpcEncoder; 11 | import cn.ziav.rpc.common.Constants; 12 | import cn.ziav.rpc.common.MsgRequest; 13 | import cn.ziav.rpc.exception.RemotingException; 14 | import cn.ziav.rpc.utils.NamedThreadFactory; 15 | import io.netty.bootstrap.Bootstrap; 16 | import io.netty.buffer.PooledByteBufAllocator; 17 | import io.netty.channel.Channel; 18 | import io.netty.channel.ChannelFuture; 19 | import io.netty.channel.ChannelInitializer; 20 | import io.netty.channel.ChannelOption; 21 | import io.netty.channel.nio.NioEventLoopGroup; 22 | import io.netty.channel.socket.nio.NioSocketChannel; 23 | import io.netty.handler.timeout.IdleStateHandler; 24 | import io.netty.util.concurrent.DefaultThreadFactory; 25 | import java.io.IOException; 26 | import java.util.ArrayList; 27 | import java.util.Comparator; 28 | import java.util.HashSet; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Set; 32 | import java.util.concurrent.ConcurrentHashMap; 33 | import java.util.concurrent.CountDownLatch; 34 | import java.util.concurrent.ExecutionException; 35 | import java.util.concurrent.ExecutorService; 36 | import java.util.concurrent.Executors; 37 | import java.util.concurrent.ThreadLocalRandom; 38 | import java.util.concurrent.locks.Lock; 39 | import java.util.concurrent.locks.ReentrantLock; 40 | import org.apache.zookeeper.KeeperException; 41 | import org.apache.zookeeper.Watcher.Event; 42 | import org.apache.zookeeper.ZooKeeper; 43 | import org.slf4j.Logger; 44 | import org.slf4j.LoggerFactory; 45 | 46 | /** @author Zavi */ 47 | public class RpcClient { 48 | private static final Logger logger = LoggerFactory.getLogger(RpcClient.class); 49 | 50 | /** zk注册中心 */ 51 | private final String zkAddr; 52 | /** 注册到具体的topic */ 53 | private final String topic; 54 | 55 | /** 连接处理器 */ 56 | private final RpcClientHandler rpcClientHandler; 57 | 58 | /** 客户端启动器 */ 59 | private final Bootstrap bootstrap; 60 | /** netty client bootstrap */ 61 | private static final NioEventLoopGroup NIO_EVENT_LOOP_GROUP = 62 | new NioEventLoopGroup( 63 | Constants.DEFAULT_IO_THREADS, new DefaultThreadFactory("NettyClientWorker", true)); 64 | 65 | private ZooKeeper zk; 66 | 67 | private volatile boolean closed; 68 | /** 服务器节点列表 */ 69 | private volatile List nodeList = new ArrayList<>(); 70 | 71 | private final Map nodeMap = new ConcurrentHashMap<>(); 72 | 73 | private Lock lock = new ReentrantLock(); 74 | 75 | private int roundIdx = 0; 76 | 77 | private final ExecutorService SHARED_EXECUTOR = 78 | Executors.newCachedThreadPool(new NamedThreadFactory("EasyRpcSharedHandler", true)); 79 | 80 | /** 81 | * 初始化Client端,并连接zookeeper 82 | * 83 | * @param zkAddr zk地址 84 | * @param topic 服务发现的topic 85 | * @throws Throwable 86 | */ 87 | public RpcClient(String zkAddr, String topic) throws Throwable { 88 | this.zkAddr = zkAddr; 89 | this.topic = topic; 90 | bootstrap = new Bootstrap(); 91 | bootstrap 92 | // 设置线程池组 93 | .group(NIO_EVENT_LOOP_GROUP) 94 | // 设置连接存活探测 95 | .option(ChannelOption.SO_KEEPALIVE, true) 96 | // 禁用Nagle算法 97 | .option(ChannelOption.TCP_NODELAY, true) 98 | // 设置缓冲区分配器 99 | .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) 100 | // 设置连接超时 101 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, DEFAULT_CONNECT_TIMEOUT) 102 | // 设置非阻塞的连接套接字通道 103 | .channel(NioSocketChannel.class); 104 | 105 | this.rpcClientHandler = new RpcClientHandler(); 106 | bootstrap.handler( 107 | new ChannelInitializer() { 108 | 109 | @Override 110 | protected void initChannel(Channel ch) throws Exception { 111 | ch.pipeline() 112 | // 解码器 113 | .addLast("decoder", new RpcDecoder()) 114 | // 编码器 115 | .addLast("encoder", new RpcEncoder()) 116 | // 心跳检测 117 | .addLast( 118 | "client-idle-handler", 119 | new IdleStateHandler(DEFAULT_HEARTBEAT * 3, 0, 0, MILLISECONDS)) 120 | // 客户端请求处理器 121 | .addLast("handler", rpcClientHandler); 122 | } 123 | }); 124 | 125 | // 初始化Zookeeper 126 | initZooKeeper(zkAddr); 127 | // 更新服务器节点信息 128 | updateNodeList(); 129 | } 130 | 131 | /** 132 | * 初始化Zookeeper 133 | * 134 | * @param zkAddr 135 | * @throws IOException 136 | * @throws InterruptedException 137 | */ 138 | private void initZooKeeper(String zkAddr) throws IOException, InterruptedException { 139 | CountDownLatch latch = new CountDownLatch(1); 140 | 141 | ZooKeeper zk = 142 | new ZooKeeper( 143 | zkAddr, 144 | Constants.ZK_SESSION_TIMEOUT, 145 | event -> { 146 | if (event.getState() == Event.KeeperState.SyncConnected) { 147 | latch.countDown(); 148 | } 149 | }); 150 | latch.await(); 151 | this.zk = zk; 152 | } 153 | 154 | /** 更新服务器节点信息 */ 155 | private void updateNodeList() { 156 | lock.lock(); 157 | try { 158 | List oldNodeList = this.nodeList; 159 | this.nodeList = 160 | zk.getChildren( 161 | Constants.ZK_REGISTRY_PATH + "/" + topic, 162 | event -> { 163 | if (event.getType() == Event.EventType.NodeChildrenChanged) { 164 | updateNodeList(); 165 | } 166 | }); 167 | // 新列表和老列表完全相同,那么什么都不需要做 168 | if (this.nodeList.containsAll(oldNodeList) && this.nodeList.size() == oldNodeList.size()) { 169 | return; 170 | } 171 | 172 | Set set = new HashSet<>(this.nodeList); 173 | // 新老列表存在交集或没有交集 174 | boolean flag = set.retainAll(oldNodeList); 175 | if (flag) { 176 | // 移除交集部分,剩下的就是需要被删除的 177 | oldNodeList.removeAll(set); 178 | oldNodeList.forEach(this.nodeMap::remove); 179 | } 180 | 181 | // 其他情况,直接putIfAbsent 182 | this.nodeList.forEach(addr -> this.nodeMap.putIfAbsent(addr, new ServerNode(addr))); 183 | } catch (KeeperException | InterruptedException e) { 184 | logger.error("", e); 185 | } finally { 186 | lock.unlock(); 187 | } 188 | } 189 | 190 | public R send(String addr, int msgId, T body, int timeout) throws Throwable { 191 | // 判断client是否已关闭 192 | if (closed) { 193 | throw new RemotingException(CLIENT_HAS_CLOSED); 194 | } 195 | // 根据远程服务器地址获取连接通道 196 | Channel channel = getOrCreateChannel(addr); 197 | // 构建请求 198 | MsgRequest request = new MsgRequest<>(); 199 | request.mTwoWay = false; 200 | request.mData = body; 201 | request.mId = msgId; 202 | // 代理给RpcFuture处理结果 203 | RpcFuture rpcFuture = 204 | RpcFuture.newFuture(request, null, timeout, nodeMap.get(addr), SHARED_EXECUTOR); 205 | try { 206 | // 写入缓冲区 207 | channel.writeAndFlush(request); 208 | // 标记请求已发送 209 | RpcFuture.sent(request); 210 | return rpcFuture.get(); 211 | } catch (Throwable e) { 212 | // 异常取消 213 | rpcFuture.cancel(true); 214 | if (e instanceof ExecutionException) { 215 | throw e.getCause(); 216 | } 217 | throw e; 218 | } 219 | } 220 | 221 | /** 222 | * 获取或创建Channel 223 | * 224 | * @param addr 225 | * @return 226 | * @throws RemotingException 227 | */ 228 | private Channel getOrCreateChannel(String addr) throws RemotingException { 229 | // 根据Server端的地址获取连接Channel 230 | Channel channel = rpcClientHandler.getChannel(addr); 231 | if (channel != null) { 232 | return channel; 233 | } 234 | 235 | String[] split = addr.split(":"); 236 | // 建立连接 237 | ChannelFuture future = bootstrap.connect(split[0], Integer.parseInt(split[1])); 238 | // 阻塞等待结果 239 | boolean ret = future.awaitUninterruptibly(DEFAULT_CONNECT_TIMEOUT, MILLISECONDS); 240 | if (ret && future.isSuccess()) { 241 | Channel newChannel = future.channel(); 242 | channel = rpcClientHandler.getOrAddChannel(addr, future.channel()); 243 | // 如果旧的连接跟新连接是同一个对象,那么就把新的给关掉 244 | if (newChannel != channel) { 245 | newChannel.close(); 246 | } 247 | } else if (future.cause() != null) { 248 | // 连接异常 249 | throw new RemotingException(CLIENT_CONNECTED_FAILED, future.cause()); 250 | } else { 251 | // 连接发生未知异常 252 | throw new RemotingException(CLIENT_CONNECTED_FAILED, "unknown error"); 253 | } 254 | return channel; 255 | } 256 | 257 | public void sendAsync( 258 | String addr, int msgId, T body, int timeout, EasyRpcCallback callback) { 259 | // 判断client是否已关闭 260 | if (closed) { 261 | callback.fail(new RemotingException(CLIENT_HAS_CLOSED)); 262 | return; 263 | } 264 | Channel channel; 265 | try { 266 | channel = getOrCreateChannel(addr); 267 | } catch (RemotingException e) { 268 | closed = true; 269 | callback.fail(e); 270 | return; 271 | } 272 | 273 | // 构建请求对象 274 | MsgRequest request = new MsgRequest<>(); 275 | request.mTwoWay = false; 276 | request.mId = msgId; 277 | request.mData = body; 278 | // 代理给RpcFuture处理结果 279 | RpcFuture rpcFuture = 280 | RpcFuture.newFuture(request, callback, timeout, nodeMap.get(addr), SHARED_EXECUTOR); 281 | try { 282 | // 写入缓冲区 283 | channel.writeAndFlush(request); 284 | // 标记请求已发送 285 | RpcFuture.sent(request); 286 | } catch (Exception e) { 287 | closed = true; 288 | rpcFuture.cancel(true); 289 | callback.fail(e); 290 | } 291 | } 292 | 293 | public String randomNode() { 294 | lock.lock(); 295 | try { 296 | int i = ThreadLocalRandom.current().nextInt(nodeList.size()); 297 | return nodeList.get(i); 298 | } finally { 299 | lock.unlock(); 300 | } 301 | } 302 | 303 | public String roundNode() { 304 | lock.lock(); 305 | try { 306 | if (roundIdx > nodeList.size() - 1) { 307 | roundIdx = 0; 308 | } 309 | 310 | return nodeList.get(roundIdx++); 311 | } finally { 312 | lock.unlock(); 313 | } 314 | } 315 | 316 | public String hashNode(Object object) { 317 | int mod = object.hashCode() % nodeList.size(); 318 | return nodeList.get(Math.abs(mod)); 319 | } 320 | 321 | public String lowLatencyNode() { 322 | ServerNode serverNode = 323 | this.nodeMap.values().stream() 324 | .min(Comparator.comparingDouble(ServerNode::calAvgRespTime)) 325 | .get(); 326 | return serverNode.addr; 327 | } 328 | 329 | public void doClose() { 330 | closed = true; 331 | rpcClientHandler.closeChannel(); 332 | SHARED_EXECUTOR.shutdownNow(); 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/utils/CollectionUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cn.ziav.rpc.utils; 18 | 19 | import static java.util.Collections.emptySet; 20 | import static java.util.Collections.unmodifiableSet; 21 | 22 | import java.util.AbstractSet; 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.Collections; 26 | import java.util.Comparator; 27 | import java.util.HashMap; 28 | import java.util.LinkedHashSet; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Set; 32 | import org.apache.commons.lang3.ArrayUtils; 33 | import org.apache.commons.lang3.StringUtils; 34 | 35 | /** 36 | * Miscellaneous collection utility methods. Mainly for internal use within the framework. 37 | * 38 | * @author william.liangf 39 | * @since 2.0.7 40 | */ 41 | public class CollectionUtils { 42 | 43 | private static final Comparator SIMPLE_NAME_COMPARATOR = 44 | new Comparator() { 45 | @Override 46 | public int compare(String s1, String s2) { 47 | if (s1 == null && s2 == null) { 48 | return 0; 49 | } 50 | if (s1 == null) { 51 | return -1; 52 | } 53 | if (s2 == null) { 54 | return 1; 55 | } 56 | int i1 = s1.lastIndexOf('.'); 57 | if (i1 >= 0) { 58 | s1 = s1.substring(i1 + 1); 59 | } 60 | int i2 = s2.lastIndexOf('.'); 61 | if (i2 >= 0) { 62 | s2 = s2.substring(i2 + 1); 63 | } 64 | return s1.compareToIgnoreCase(s2); 65 | } 66 | }; 67 | 68 | private CollectionUtils() {} 69 | 70 | @SuppressWarnings({"unchecked", "rawtypes"}) 71 | public static List sort(List list) { 72 | if (isNotEmpty(list)) { 73 | Collections.sort((List) list); 74 | } 75 | return list; 76 | } 77 | 78 | public static List sortSimpleName(List list) { 79 | if (list != null && list.size() > 0) { 80 | Collections.sort(list, SIMPLE_NAME_COMPARATOR); 81 | } 82 | return list; 83 | } 84 | 85 | public static Map> splitAll( 86 | Map> list, String separator) { 87 | if (list == null) { 88 | return null; 89 | } 90 | Map> result = new HashMap<>(); 91 | for (Map.Entry> entry : list.entrySet()) { 92 | result.put(entry.getKey(), split(entry.getValue(), separator)); 93 | } 94 | return result; 95 | } 96 | 97 | public static Map> joinAll( 98 | Map> map, String separator) { 99 | if (map == null) { 100 | return null; 101 | } 102 | Map> result = new HashMap<>(); 103 | for (Map.Entry> entry : map.entrySet()) { 104 | result.put(entry.getKey(), join(entry.getValue(), separator)); 105 | } 106 | return result; 107 | } 108 | 109 | public static Map split(List list, String separator) { 110 | if (list == null) { 111 | return null; 112 | } 113 | Map map = new HashMap<>(); 114 | if (list.isEmpty()) { 115 | return map; 116 | } 117 | for (String item : list) { 118 | int index = item.indexOf(separator); 119 | if (index == -1) { 120 | map.put(item, ""); 121 | } else { 122 | map.put(item.substring(0, index), item.substring(index + 1)); 123 | } 124 | } 125 | return map; 126 | } 127 | 128 | public static List join(Map map, String separator) { 129 | if (map == null) { 130 | return null; 131 | } 132 | List list = new ArrayList<>(); 133 | if (map.size() == 0) { 134 | return list; 135 | } 136 | for (Map.Entry entry : map.entrySet()) { 137 | String key = entry.getKey(); 138 | String value = entry.getValue(); 139 | if (StringUtils.isEmpty(value)) { 140 | list.add(key); 141 | } else { 142 | list.add(key + separator + value); 143 | } 144 | } 145 | return list; 146 | } 147 | 148 | public static String join(List list, String separator) { 149 | StringBuilder sb = new StringBuilder(); 150 | for (String ele : list) { 151 | if (sb.length() > 0) { 152 | sb.append(separator); 153 | } 154 | sb.append(ele); 155 | } 156 | return sb.toString(); 157 | } 158 | 159 | public static boolean mapEquals(Map map1, Map map2) { 160 | if (map1 == null && map2 == null) { 161 | return true; 162 | } 163 | if (map1 == null || map2 == null) { 164 | return false; 165 | } 166 | if (map1.size() != map2.size()) { 167 | return false; 168 | } 169 | for (Map.Entry entry : map1.entrySet()) { 170 | Object key = entry.getKey(); 171 | Object value1 = entry.getValue(); 172 | Object value2 = map2.get(key); 173 | if (!objectEquals(value1, value2)) { 174 | return false; 175 | } 176 | } 177 | return true; 178 | } 179 | 180 | private static boolean objectEquals(Object obj1, Object obj2) { 181 | if (obj1 == null && obj2 == null) { 182 | return true; 183 | } 184 | if (obj1 == null || obj2 == null) { 185 | return false; 186 | } 187 | return obj1.equals(obj2); 188 | } 189 | 190 | public static Map toStringMap(String... pairs) { 191 | Map parameters = new HashMap<>(); 192 | if (ArrayUtils.isEmpty(pairs)) { 193 | return parameters; 194 | } 195 | 196 | if (pairs.length > 0) { 197 | if (pairs.length % 2 != 0) { 198 | throw new IllegalArgumentException("pairs must be even."); 199 | } 200 | for (int i = 0; i < pairs.length; i = i + 2) { 201 | parameters.put(pairs[i], pairs[i + 1]); 202 | } 203 | } 204 | return parameters; 205 | } 206 | 207 | @SuppressWarnings("unchecked") 208 | public static Map toMap(Object... pairs) { 209 | Map ret = new HashMap<>(); 210 | if (pairs == null || pairs.length == 0) { 211 | return ret; 212 | } 213 | 214 | if (pairs.length % 2 != 0) { 215 | throw new IllegalArgumentException("Map pairs can not be odd number."); 216 | } 217 | int len = pairs.length / 2; 218 | for (int i = 0; i < len; i++) { 219 | ret.put((K) pairs[2 * i], (V) pairs[2 * i + 1]); 220 | } 221 | return ret; 222 | } 223 | 224 | /** 225 | * Return {@code true} if the supplied Collection is {@code null} or empty. Otherwise, return 226 | * {@code false}. 227 | * 228 | * @param collection the Collection to check 229 | * @return whether the given Collection is empty 230 | */ 231 | public static boolean isEmpty(Collection collection) { 232 | return collection == null || collection.isEmpty(); 233 | } 234 | 235 | /** 236 | * Return {@code true} if the supplied Collection is {@code not null} or not empty. Otherwise, 237 | * return {@code false}. 238 | * 239 | * @param collection the Collection to check 240 | * @return whether the given Collection is not empty 241 | */ 242 | public static boolean isNotEmpty(Collection collection) { 243 | return !isEmpty(collection); 244 | } 245 | 246 | /** 247 | * Return {@code true} if the supplied Map is {@code null} or empty. Otherwise, return {@code 248 | * false}. 249 | * 250 | * @param map the Map to check 251 | * @return whether the given Map is empty 252 | */ 253 | public static boolean isEmptyMap(Map map) { 254 | return map == null || map.size() == 0; 255 | } 256 | 257 | /** 258 | * Return {@code true} if the supplied Map is {@code not null} or not empty. Otherwise, return 259 | * {@code false}. 260 | * 261 | * @param map the Map to check 262 | * @return whether the given Map is not empty 263 | */ 264 | public static boolean isNotEmptyMap(Map map) { 265 | return !isEmptyMap(map); 266 | } 267 | 268 | /** 269 | * Convert to multiple values to be {@link LinkedHashSet} 270 | * 271 | * @param values one or more values 272 | * @param the type of values 273 | * @return read-only {@link Set} 274 | */ 275 | public static Set ofSet(T... values) { 276 | int size = values == null ? 0 : values.length; 277 | if (size < 1) { 278 | return emptySet(); 279 | } 280 | 281 | float loadFactor = 1f / ((size + 1) * 1.0f); 282 | 283 | if (loadFactor > 0.75f) { 284 | loadFactor = 0.75f; 285 | } 286 | 287 | Set elements = new LinkedHashSet<>(size, loadFactor); 288 | for (int i = 0; i < size; i++) { 289 | elements.add(values[i]); 290 | } 291 | return unmodifiableSet(elements); 292 | } 293 | 294 | /** 295 | * Get the size of the specified {@link Collection} 296 | * 297 | * @param collection the specified {@link Collection} 298 | * @return must be positive number 299 | * @since 2.7.6 300 | */ 301 | public static int size(Collection collection) { 302 | return collection == null ? 0 : collection.size(); 303 | } 304 | 305 | /** 306 | * Compares the specified collection with another, the main implementation references {@link 307 | * AbstractSet} 308 | * 309 | * @param one {@link Collection} 310 | * @param another {@link Collection} 311 | * @return if equals, return true, or false 312 | * @since 2.7.6 313 | */ 314 | public static boolean equals(Collection one, Collection another) { 315 | 316 | if (one == another) { 317 | return true; 318 | } 319 | 320 | if (isEmpty(one) && isEmpty(another)) { 321 | return true; 322 | } 323 | 324 | if (size(one) != size(another)) { 325 | return false; 326 | } 327 | 328 | try { 329 | return one.containsAll(another); 330 | } catch (ClassCastException unused) { 331 | return false; 332 | } catch (NullPointerException unused) { 333 | return false; 334 | } 335 | } 336 | 337 | /** 338 | * Add the multiple values into {@link Collection the specified collection} 339 | * 340 | * @param collection {@link Collection the specified collection} 341 | * @param values the multiple values 342 | * @param the type of values 343 | * @return the effected count after added 344 | * @since 2.7.6 345 | */ 346 | public static int addAll(Collection collection, T... values) { 347 | 348 | int size = values == null ? 0 : values.length; 349 | 350 | if (collection == null || size < 1) { 351 | return 0; 352 | } 353 | 354 | int effectedCount = 0; 355 | for (int i = 0; i < size; i++) { 356 | if (collection.add(values[i])) { 357 | effectedCount++; 358 | } 359 | } 360 | 361 | return effectedCount; 362 | } 363 | 364 | /** 365 | * Take the first element from the specified collection 366 | * 367 | * @param values the collection object 368 | * @param the type of element of collection 369 | * @return if found, return the first one, or null 370 | * @since 2.7.6 371 | */ 372 | public static T first(Collection values) { 373 | if (isEmpty(values)) { 374 | return null; 375 | } 376 | if (values instanceof List) { 377 | List list = (List) values; 378 | return list.get(0); 379 | } else { 380 | return values.iterator().next(); 381 | } 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/utils/ClassUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package cn.ziav.rpc.utils; 18 | 19 | import static cn.ziav.rpc.utils.CollectionUtils.ofSet; 20 | import static cn.ziav.rpc.utils.Streams.filterAll; 21 | import static java.util.Arrays.asList; 22 | import static java.util.Collections.emptySet; 23 | import static java.util.Collections.unmodifiableSet; 24 | import static java.util.stream.Collectors.toList; 25 | import static org.apache.commons.lang3.ArrayUtils.isNotEmpty; 26 | 27 | import java.lang.reflect.Array; 28 | import java.math.BigDecimal; 29 | import java.math.BigInteger; 30 | import java.util.Arrays; 31 | import java.util.Collection; 32 | import java.util.Date; 33 | import java.util.HashMap; 34 | import java.util.HashSet; 35 | import java.util.LinkedHashSet; 36 | import java.util.Map; 37 | import java.util.Objects; 38 | import java.util.Set; 39 | import java.util.function.Predicate; 40 | 41 | public class ClassUtils { 42 | /** Suffix for array class names: "[]" */ 43 | public static final String ARRAY_SUFFIX = "[]"; 44 | /** Prefix for internal array class names: "[L" */ 45 | private static final String INTERNAL_ARRAY_PREFIX = "[L"; 46 | /** 47 | * Map with primitive type name as key and corresponding primitive type as value, for example: 48 | * "int" -> "int.class". 49 | */ 50 | private static final Map> PRIMITIVE_TYPE_NAME_MAP = 51 | new HashMap>(32); 52 | /** 53 | * Map with primitive wrapper type as key and corresponding primitive type as value, for example: 54 | * Integer.class -> int.class. 55 | */ 56 | private static final Map, Class> PRIMITIVE_WRAPPER_TYPE_MAP = 57 | new HashMap, Class>(16); 58 | 59 | /** 60 | * Simple Types including: 61 | * 62 | *
    63 | *
  • {@link Void} 64 | *
  • {@link Boolean} 65 | *
  • {@link Character} 66 | *
  • {@link Byte} 67 | *
  • {@link Integer} 68 | *
  • {@link Float} 69 | *
  • {@link Double} 70 | *
  • {@link String} 71 | *
  • {@link BigDecimal} 72 | *
  • {@link BigInteger} 73 | *
  • {@link Date} 74 | *
  • {@link Object} 75 | *
76 | * 77 | * @see javax.management.openmbean.SimpleType 78 | * @since 2.7.6 79 | */ 80 | public static final Set> SIMPLE_TYPES = 81 | ofSet( 82 | Void.class, 83 | Boolean.class, 84 | Character.class, 85 | Byte.class, 86 | Short.class, 87 | Integer.class, 88 | Long.class, 89 | Float.class, 90 | Double.class, 91 | String.class, 92 | BigDecimal.class, 93 | BigInteger.class, 94 | Date.class, 95 | Object.class); 96 | 97 | private static final char PACKAGE_SEPARATOR_CHAR = '.'; 98 | 99 | static { 100 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, boolean.class); 101 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, byte.class); 102 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Character.class, char.class); 103 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Double.class, double.class); 104 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Float.class, float.class); 105 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Integer.class, int.class); 106 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Long.class, long.class); 107 | PRIMITIVE_WRAPPER_TYPE_MAP.put(Short.class, short.class); 108 | 109 | Set> primitiveTypeNames = new HashSet<>(32); 110 | primitiveTypeNames.addAll(PRIMITIVE_WRAPPER_TYPE_MAP.values()); 111 | primitiveTypeNames.addAll( 112 | Arrays.asList( 113 | boolean[].class, 114 | byte[].class, 115 | char[].class, 116 | double[].class, 117 | float[].class, 118 | int[].class, 119 | long[].class, 120 | short[].class)); 121 | for (Class primitiveTypeName : primitiveTypeNames) { 122 | PRIMITIVE_TYPE_NAME_MAP.put(primitiveTypeName.getName(), primitiveTypeName); 123 | } 124 | } 125 | 126 | public static Class forNameWithThreadContextClassLoader(String name) 127 | throws ClassNotFoundException { 128 | return forName(name, Thread.currentThread().getContextClassLoader()); 129 | } 130 | 131 | public static Class forNameWithCallerClassLoader(String name, Class caller) 132 | throws ClassNotFoundException { 133 | return forName(name, caller.getClassLoader()); 134 | } 135 | 136 | public static ClassLoader getCallerClassLoader(Class caller) { 137 | return caller.getClassLoader(); 138 | } 139 | 140 | /** 141 | * get class loader 142 | * 143 | * @param clazz 144 | * @return class loader 145 | */ 146 | public static ClassLoader getClassLoader(Class clazz) { 147 | ClassLoader cl = null; 148 | try { 149 | cl = Thread.currentThread().getContextClassLoader(); 150 | } catch (Throwable ex) { 151 | // Cannot access thread context ClassLoader - falling back to system class loader... 152 | } 153 | if (cl == null) { 154 | // No thread context class loader -> use class loader of this class. 155 | cl = clazz.getClassLoader(); 156 | if (cl == null) { 157 | // getClassLoader() returning null indicates the bootstrap ClassLoader 158 | try { 159 | cl = ClassLoader.getSystemClassLoader(); 160 | } catch (Throwable ex) { 161 | // Cannot access system ClassLoader - oh well, maybe the caller can live with null... 162 | } 163 | } 164 | } 165 | 166 | return cl; 167 | } 168 | 169 | /** 170 | * Return the default ClassLoader to use: typically the thread context ClassLoader, if available; 171 | * the ClassLoader that loaded the ClassUtils class will be used as fallback. 172 | * 173 | *

Call this method if you intend to use the thread context ClassLoader in a scenario where you 174 | * absolutely need a non-null ClassLoader reference: for example, for class path resource loading 175 | * (but not necessarily for Class.forName, which accepts a null 176 | * ClassLoader reference as well). 177 | * 178 | * @return the default ClassLoader (never null) 179 | * @see Thread#getContextClassLoader() 180 | */ 181 | public static ClassLoader getClassLoader() { 182 | return getClassLoader(ClassUtils.class); 183 | } 184 | 185 | /** Same as Class.forName(), except that it works for primitive types. */ 186 | public static Class forName(String name) throws ClassNotFoundException { 187 | return forName(name, getClassLoader()); 188 | } 189 | 190 | /** 191 | * Replacement for Class.forName() that also returns Class instances for primitives 192 | * (like "int") and array class names (like "String[]"). 193 | * 194 | * @param name the name of the Class 195 | * @param classLoader the class loader to use (may be null, which indicates the 196 | * default class loader) 197 | * @return Class instance for the supplied name 198 | * @throws ClassNotFoundException if the class was not found 199 | * @throws LinkageError if the class file could not be loaded 200 | * @see Class#forName(String, boolean, ClassLoader) 201 | */ 202 | public static Class forName(String name, ClassLoader classLoader) 203 | throws ClassNotFoundException, LinkageError { 204 | 205 | Class clazz = resolvePrimitiveClassName(name); 206 | if (clazz != null) { 207 | return clazz; 208 | } 209 | 210 | // "java.lang.String[]" style arrays 211 | if (name.endsWith(ARRAY_SUFFIX)) { 212 | String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length()); 213 | Class elementClass = forName(elementClassName, classLoader); 214 | return Array.newInstance(elementClass, 0).getClass(); 215 | } 216 | 217 | // "[Ljava.lang.String;" style arrays 218 | int internalArrayMarker = name.indexOf(INTERNAL_ARRAY_PREFIX); 219 | if (internalArrayMarker != -1 && name.endsWith(";")) { 220 | String elementClassName = null; 221 | if (internalArrayMarker == 0) { 222 | elementClassName = name.substring(INTERNAL_ARRAY_PREFIX.length(), name.length() - 1); 223 | } else if (name.startsWith("[")) { 224 | elementClassName = name.substring(1); 225 | } 226 | Class elementClass = forName(elementClassName, classLoader); 227 | return Array.newInstance(elementClass, 0).getClass(); 228 | } 229 | 230 | ClassLoader classLoaderToUse = classLoader; 231 | if (classLoaderToUse == null) { 232 | classLoaderToUse = getClassLoader(); 233 | } 234 | return classLoaderToUse.loadClass(name); 235 | } 236 | 237 | /** 238 | * Resolve the given class name as primitive class, if appropriate, according to the JVM's naming 239 | * rules for primitive classes. 240 | * 241 | *

Also supports the JVM's internal class names for primitive arrays. Does not support 242 | * the "[]" suffix notation for primitive arrays; this is only supported by {@link #forName}. 243 | * 244 | * @param name the name of the potentially primitive class 245 | * @return the primitive class, or null if the name does not denote a primitive class 246 | * or primitive array class 247 | */ 248 | public static Class resolvePrimitiveClassName(String name) { 249 | Class result = null; 250 | // Most class names will be quite long, considering that they 251 | // SHOULD sit in a package, so a length check is worthwhile. 252 | if (name != null && name.length() <= 8) { 253 | // Could be a primitive - likely. 254 | result = (Class) PRIMITIVE_TYPE_NAME_MAP.get(name); 255 | } 256 | return result; 257 | } 258 | 259 | public static String toShortString(Object obj) { 260 | if (obj == null) { 261 | return "null"; 262 | } 263 | return obj.getClass().getSimpleName() + "@" + System.identityHashCode(obj); 264 | } 265 | 266 | public static String simpleClassName(Class clazz) { 267 | if (clazz == null) { 268 | throw new NullPointerException("clazz"); 269 | } 270 | String className = clazz.getName(); 271 | final int lastDotIdx = className.lastIndexOf(PACKAGE_SEPARATOR_CHAR); 272 | if (lastDotIdx > -1) { 273 | return className.substring(lastDotIdx + 1); 274 | } 275 | return className; 276 | } 277 | 278 | /** 279 | * The specified type is primitive type or simple type 280 | * 281 | * @param type the type to test 282 | * @return 283 | * @deprecated as 2.7.6, use {@link Class#isPrimitive()} plus {@link #isSimpleType(Class)} instead 284 | */ 285 | public static boolean isPrimitive(Class type) { 286 | return type != null && (type.isPrimitive() || isSimpleType(type)); 287 | } 288 | 289 | /** 290 | * The specified type is simple type or not 291 | * 292 | * @param type the type to test 293 | * @return if type is one element of {@link #SIMPLE_TYPES}, return true, 294 | * or false 295 | * @see #SIMPLE_TYPES 296 | * @since 2.7.6 297 | */ 298 | public static boolean isSimpleType(Class type) { 299 | return SIMPLE_TYPES.contains(type); 300 | } 301 | 302 | public static Object convertPrimitive(Class type, String value) { 303 | if (value == null) { 304 | return null; 305 | } else if (type == char.class || type == Character.class) { 306 | return value.length() > 0 ? value.charAt(0) : '\0'; 307 | } else if (type == boolean.class || type == Boolean.class) { 308 | return Boolean.valueOf(value); 309 | } 310 | try { 311 | if (type == byte.class || type == Byte.class) { 312 | return Byte.valueOf(value); 313 | } else if (type == short.class || type == Short.class) { 314 | return Short.valueOf(value); 315 | } else if (type == int.class || type == Integer.class) { 316 | return Integer.valueOf(value); 317 | } else if (type == long.class || type == Long.class) { 318 | return Long.valueOf(value); 319 | } else if (type == float.class || type == Float.class) { 320 | return Float.valueOf(value); 321 | } else if (type == double.class || type == Double.class) { 322 | return Double.valueOf(value); 323 | } 324 | } catch (NumberFormatException e) { 325 | return null; 326 | } 327 | return value; 328 | } 329 | 330 | /** 331 | * We only check boolean value at this moment. 332 | * 333 | * @param type 334 | * @param value 335 | * @return 336 | */ 337 | public static boolean isTypeMatch(Class type, String value) { 338 | if ((type == boolean.class || type == Boolean.class) 339 | && !("true".equals(value) || "false".equals(value))) { 340 | return false; 341 | } 342 | return true; 343 | } 344 | 345 | /** 346 | * Get all super classes from the specified type 347 | * 348 | * @param type the specified type 349 | * @param classFilters the filters for classes 350 | * @return non-null read-only {@link Set} 351 | * @since 2.7.6 352 | */ 353 | public static Set> getAllSuperClasses( 354 | Class type, Predicate>... classFilters) { 355 | 356 | Set> allSuperClasses = new LinkedHashSet<>(); 357 | 358 | Class superClass = type.getSuperclass(); 359 | 360 | if (superClass != null) { 361 | // add current super class 362 | allSuperClasses.add(superClass); 363 | // add ancestor classes 364 | allSuperClasses.addAll(getAllSuperClasses(superClass)); 365 | } 366 | 367 | return unmodifiableSet(filterAll(allSuperClasses, classFilters)); 368 | } 369 | 370 | /** 371 | * Get all interfaces from the specified type 372 | * 373 | * @param type the specified type 374 | * @param interfaceFilters the filters for interfaces 375 | * @return non-null read-only {@link Set} 376 | * @since 2.7.6 377 | */ 378 | public static Set> getAllInterfaces( 379 | Class type, Predicate>... interfaceFilters) { 380 | 381 | if (type == null || type.isPrimitive()) { 382 | return emptySet(); 383 | } 384 | 385 | Set> allInterfaces = new LinkedHashSet<>(); 386 | 387 | Class[] interfaces = type.getInterfaces(); 388 | 389 | if (isNotEmpty(interfaces)) { 390 | // add current interfaces 391 | allInterfaces.addAll(asList(interfaces)); 392 | } 393 | 394 | // add all super interfaces 395 | getAllSuperClasses(type) 396 | .forEach(superType -> allInterfaces.addAll(getAllInterfaces(superType))); 397 | 398 | // add all super interfaces from all interfaces 399 | allInterfaces.stream() 400 | .map(ClassUtils::getAllInterfaces) 401 | .flatMap(Collection::stream) 402 | .collect(toList()) 403 | .forEach(allInterfaces::add); 404 | 405 | return filterAll(allInterfaces, interfaceFilters); 406 | } 407 | 408 | /** 409 | * Get all inherited types from the specified type 410 | * 411 | * @param type the specified type 412 | * @param typeFilters the filters for types 413 | * @return non-null read-only {@link Set} 414 | * @since 2.7.6 415 | */ 416 | public static Set> getAllInheritedTypes( 417 | Class type, Predicate>... typeFilters) { 418 | // Add all super classes 419 | Set> types = new LinkedHashSet<>(getAllSuperClasses(type, typeFilters)); 420 | // Add all interface classes 421 | types.addAll(getAllInterfaces(type, typeFilters)); 422 | return unmodifiableSet(types); 423 | } 424 | 425 | /** 426 | * the semantics is same as {@link Class#isAssignableFrom(Class)} 427 | * 428 | * @param superType the super type 429 | * @param targetType the target type 430 | * @return see {@link Class#isAssignableFrom(Class)} 431 | * @since 2.7.6 432 | */ 433 | public static boolean isAssignableFrom(Class superType, Class targetType) { 434 | // any argument is null 435 | if (superType == null || targetType == null) { 436 | return false; 437 | } 438 | // equals 439 | if (Objects.equals(superType, targetType)) { 440 | return true; 441 | } 442 | // isAssignableFrom 443 | return superType.isAssignableFrom(targetType); 444 | } 445 | 446 | /** 447 | * Test the specified class name is present in the {@link ClassLoader} 448 | * 449 | * @param className the name of {@link Class} 450 | * @param classLoader {@link ClassLoader} 451 | * @return If found, return true 452 | * @since 2.7.6 453 | */ 454 | public static boolean isPresent(String className, ClassLoader classLoader) { 455 | try { 456 | forName(className, classLoader); 457 | } catch (Throwable ignored) { // Ignored 458 | return false; 459 | } 460 | return true; 461 | } 462 | 463 | /** 464 | * Resolve the {@link Class} by the specified name and {@link ClassLoader} 465 | * 466 | * @param className the name of {@link Class} 467 | * @param classLoader {@link ClassLoader} 468 | * @return If can't be resolved , return null 469 | * @since 2.7.6 470 | */ 471 | public static Class resolveClass(String className, ClassLoader classLoader) { 472 | Class targetClass = null; 473 | try { 474 | targetClass = forName(className, classLoader); 475 | } catch (Throwable ignored) { // Ignored 476 | } 477 | return targetClass; 478 | } 479 | 480 | /** 481 | * Is generic class or not? 482 | * 483 | * @param type the target type 484 | * @return if the target type is not null or void or Void.class, return true 485 | * , or false 486 | * @since 2.7.6 487 | */ 488 | public static boolean isGenericClass(Class type) { 489 | return type != null && !void.class.equals(type) && !Void.class.equals(type); 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /src/main/java/cn/ziav/rpc/timer/HashedWheelTimer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package cn.ziav.rpc.timer; 18 | 19 | import cn.ziav.rpc.utils.ClassUtils; 20 | import java.util.Collections; 21 | import java.util.HashSet; 22 | import java.util.Locale; 23 | import java.util.Queue; 24 | import java.util.Set; 25 | import java.util.concurrent.CountDownLatch; 26 | import java.util.concurrent.Executors; 27 | import java.util.concurrent.LinkedBlockingQueue; 28 | import java.util.concurrent.RejectedExecutionException; 29 | import java.util.concurrent.ThreadFactory; 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.concurrent.atomic.AtomicBoolean; 32 | import java.util.concurrent.atomic.AtomicInteger; 33 | import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; 34 | import java.util.concurrent.atomic.AtomicLong; 35 | import org.slf4j.Logger; 36 | import org.slf4j.LoggerFactory; 37 | 38 | /** 39 | * A {@link Timer} optimized for approximated I/O timeout scheduling. 40 | * 41 | *

Tick Duration

42 | * 43 | *

As described with 'approximated', this timer does not execute the scheduled {@link TimerTask} 44 | * on time. {@link HashedWheelTimer}, on every tick, will check if there are any {@link TimerTask}s 45 | * behind the schedule and execute them. 46 | * 47 | *

You can increase or decrease the accuracy of the execution timing by specifying smaller or 48 | * larger tick duration in the constructor. In most network applications, I/O timeout does not need 49 | * to be accurate. Therefore, the default tick duration is 100 milliseconds and you will not need to 50 | * try different configurations in most cases. 51 | * 52 | *

Ticks per Wheel (Wheel Size)

53 | * 54 | *

{@link HashedWheelTimer} maintains a data structure called 'wheel'. To put simply, a wheel is 55 | * a hash table of {@link TimerTask}s whose hash function is 'dead line of the task'. The default 56 | * number of ticks per wheel (i.e. the size of the wheel) is 512. You could specify a larger value 57 | * if you are going to schedule a lot of timeouts. 58 | * 59 | *

Do not create many instances.

60 | * 61 | *

{@link HashedWheelTimer} creates a new thread whenever it is instantiated and started. 62 | * Therefore, you should make sure to create only one instance and share it across your application. 63 | * One of the common mistakes, that makes your application unresponsive, is to create a new instance 64 | * for every connection. 65 | * 66 | *

Implementation Details

67 | * 68 | *

{@link HashedWheelTimer} is based on George 69 | * Varghese and Tony Lauck's paper, 'Hashed and Hierarchical Timing 71 | * Wheels: data structures to efficiently implement a timer facility'. More comprehensive slides 72 | * are located here. 73 | */ 74 | public class HashedWheelTimer implements Timer { 75 | 76 | /** may be in spi? */ 77 | public static final String NAME = "hased"; 78 | 79 | private static final Logger logger = LoggerFactory.getLogger(HashedWheelTimer.class); 80 | 81 | private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); 82 | private static final AtomicBoolean WARNED_TOO_MANY_INSTANCES = new AtomicBoolean(); 83 | private static final int INSTANCE_COUNT_LIMIT = 64; 84 | private static final AtomicIntegerFieldUpdater WORKER_STATE_UPDATER = 85 | AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimer.class, "workerState"); 86 | 87 | private final Worker worker = new Worker(); 88 | private final Thread workerThread; 89 | 90 | private static final int WORKER_STATE_INIT = 0; 91 | private static final int WORKER_STATE_STARTED = 1; 92 | private static final int WORKER_STATE_SHUTDOWN = 2; 93 | 94 | /** 0 - init, 1 - started, 2 - shut down */ 95 | @SuppressWarnings({"unused", "FieldMayBeFinal"}) 96 | private volatile int workerState; 97 | 98 | private final long tickDuration; 99 | private final HashedWheelBucket[] wheel; 100 | private final int mask; 101 | private final CountDownLatch startTimeInitialized = new CountDownLatch(1); 102 | private final Queue timeouts = new LinkedBlockingQueue<>(); 103 | private final Queue cancelledTimeouts = new LinkedBlockingQueue<>(); 104 | private final AtomicLong pendingTimeouts = new AtomicLong(0); 105 | private final long maxPendingTimeouts; 106 | 107 | private volatile long startTime; 108 | 109 | /** 110 | * Creates a new timer with the default thread factory ({@link Executors#defaultThreadFactory()}), 111 | * default tick duration, and default number of ticks per wheel. 112 | */ 113 | public HashedWheelTimer() { 114 | this(Executors.defaultThreadFactory()); 115 | } 116 | 117 | /** 118 | * Creates a new timer with the default thread factory ({@link Executors#defaultThreadFactory()}) 119 | * and default number of ticks per wheel. 120 | * 121 | * @param tickDuration the duration between tick 122 | * @param unit the time unit of the {@code tickDuration} 123 | * @throws NullPointerException if {@code unit} is {@code null} 124 | * @throws IllegalArgumentException if {@code tickDuration} is <= 0 125 | */ 126 | public HashedWheelTimer(long tickDuration, TimeUnit unit) { 127 | this(Executors.defaultThreadFactory(), tickDuration, unit); 128 | } 129 | 130 | /** 131 | * Creates a new timer with the default thread factory ({@link Executors#defaultThreadFactory()}). 132 | * 133 | * @param tickDuration the duration between tick 134 | * @param unit the time unit of the {@code tickDuration} 135 | * @param ticksPerWheel the size of the wheel 136 | * @throws NullPointerException if {@code unit} is {@code null} 137 | * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is 138 | * <= 0 139 | */ 140 | public HashedWheelTimer(long tickDuration, TimeUnit unit, int ticksPerWheel) { 141 | this(Executors.defaultThreadFactory(), tickDuration, unit, ticksPerWheel); 142 | } 143 | 144 | /** 145 | * Creates a new timer with the default tick duration and default number of ticks per wheel. 146 | * 147 | * @param threadFactory a {@link ThreadFactory} that creates a background {@link Thread} which is 148 | * dedicated to {@link TimerTask} execution. 149 | * @throws NullPointerException if {@code threadFactory} is {@code null} 150 | */ 151 | public HashedWheelTimer(ThreadFactory threadFactory) { 152 | this(threadFactory, 100, TimeUnit.MILLISECONDS); 153 | } 154 | 155 | /** 156 | * Creates a new timer with the default number of ticks per wheel. 157 | * 158 | * @param threadFactory a {@link ThreadFactory} that creates a background {@link Thread} which is 159 | * dedicated to {@link TimerTask} execution. 160 | * @param tickDuration the duration between tick 161 | * @param unit the time unit of the {@code tickDuration} 162 | * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code 163 | * null} 164 | * @throws IllegalArgumentException if {@code tickDuration} is <= 0 165 | */ 166 | public HashedWheelTimer(ThreadFactory threadFactory, long tickDuration, TimeUnit unit) { 167 | this(threadFactory, tickDuration, unit, 512); 168 | } 169 | 170 | /** 171 | * Creates a new timer. 172 | * 173 | * @param threadFactory a {@link ThreadFactory} that creates a background {@link Thread} which is 174 | * dedicated to {@link TimerTask} execution. 175 | * @param tickDuration the duration between tick 176 | * @param unit the time unit of the {@code tickDuration} 177 | * @param ticksPerWheel the size of the wheel 178 | * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code 179 | * null} 180 | * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is 181 | * <= 0 182 | */ 183 | public HashedWheelTimer( 184 | ThreadFactory threadFactory, long tickDuration, TimeUnit unit, int ticksPerWheel) { 185 | this(threadFactory, tickDuration, unit, ticksPerWheel, -1); 186 | } 187 | 188 | /** 189 | * Creates a new timer. 190 | * 191 | * @param threadFactory a {@link ThreadFactory} that creates a background {@link Thread} which is 192 | * dedicated to {@link TimerTask} execution. 193 | * @param tickDuration the duration between tick 194 | * @param unit the time unit of the {@code tickDuration} 195 | * @param ticksPerWheel the size of the wheel 196 | * @param maxPendingTimeouts The maximum number of pending timeouts after which call to {@code 197 | * newTimeout} will result in {@link RejectedExecutionException} being thrown. No maximum 198 | * pending timeouts limit is assumed if this value is 0 or negative. 199 | * @throws NullPointerException if either of {@code threadFactory} and {@code unit} is {@code 200 | * null} 201 | * @throws IllegalArgumentException if either of {@code tickDuration} and {@code ticksPerWheel} is 202 | * <= 0 203 | */ 204 | public HashedWheelTimer( 205 | ThreadFactory threadFactory, 206 | long tickDuration, 207 | TimeUnit unit, 208 | int ticksPerWheel, 209 | long maxPendingTimeouts) { 210 | 211 | if (threadFactory == null) { 212 | throw new NullPointerException("threadFactory"); 213 | } 214 | if (unit == null) { 215 | throw new NullPointerException("unit"); 216 | } 217 | if (tickDuration <= 0) { 218 | throw new IllegalArgumentException("tickDuration must be greater than 0: " + tickDuration); 219 | } 220 | if (ticksPerWheel <= 0) { 221 | throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel); 222 | } 223 | 224 | // Normalize ticksPerWheel to power of two and initialize the wheel. 225 | wheel = createWheel(ticksPerWheel); 226 | mask = wheel.length - 1; 227 | 228 | // Convert tickDuration to nanos. 229 | this.tickDuration = unit.toNanos(tickDuration); 230 | 231 | // Prevent overflow. 232 | if (this.tickDuration >= Long.MAX_VALUE / wheel.length) { 233 | throw new IllegalArgumentException( 234 | String.format( 235 | "tickDuration: %d (expected: 0 < tickDuration in nanos < %d", 236 | tickDuration, Long.MAX_VALUE / wheel.length)); 237 | } 238 | workerThread = threadFactory.newThread(worker); 239 | 240 | this.maxPendingTimeouts = maxPendingTimeouts; 241 | 242 | if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT 243 | && WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) { 244 | reportTooManyInstances(); 245 | } 246 | } 247 | 248 | @Override 249 | protected void finalize() throws Throwable { 250 | try { 251 | super.finalize(); 252 | } finally { 253 | // This object is going to be GCed and it is assumed the ship has sailed to do a proper 254 | // shutdown. If 255 | // we have not yet shutdown then we want to make sure we decrement the active instance count. 256 | if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { 257 | INSTANCE_COUNTER.decrementAndGet(); 258 | } 259 | } 260 | } 261 | 262 | private static HashedWheelBucket[] createWheel(int ticksPerWheel) { 263 | if (ticksPerWheel <= 0) { 264 | throw new IllegalArgumentException("ticksPerWheel must be greater than 0: " + ticksPerWheel); 265 | } 266 | if (ticksPerWheel > 1073741824) { 267 | throw new IllegalArgumentException( 268 | "ticksPerWheel may not be greater than 2^30: " + ticksPerWheel); 269 | } 270 | 271 | ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel); 272 | HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel]; 273 | for (int i = 0; i < wheel.length; i++) { 274 | wheel[i] = new HashedWheelBucket(); 275 | } 276 | return wheel; 277 | } 278 | 279 | private static int normalizeTicksPerWheel(int ticksPerWheel) { 280 | int normalizedTicksPerWheel = ticksPerWheel - 1; 281 | normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 1; 282 | normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 2; 283 | normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 4; 284 | normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 8; 285 | normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 16; 286 | return normalizedTicksPerWheel + 1; 287 | } 288 | 289 | /** 290 | * Starts the background thread explicitly. The background thread will start automatically on 291 | * demand even if you did not call this method. 292 | * 293 | * @throws IllegalStateException if this timer has been {@linkplain #stop() stopped} already 294 | */ 295 | public void start() { 296 | switch (WORKER_STATE_UPDATER.get(this)) { 297 | case WORKER_STATE_INIT: 298 | if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) { 299 | workerThread.start(); 300 | } 301 | break; 302 | case WORKER_STATE_STARTED: 303 | break; 304 | case WORKER_STATE_SHUTDOWN: 305 | throw new IllegalStateException("cannot be started once stopped"); 306 | default: 307 | throw new Error("Invalid WorkerState"); 308 | } 309 | 310 | // Wait until the startTime is initialized by the worker. 311 | while (startTime == 0) { 312 | try { 313 | startTimeInitialized.await(); 314 | } catch (InterruptedException ignore) { 315 | // Ignore - it will be ready very soon. 316 | } 317 | } 318 | } 319 | 320 | @Override 321 | public Set stop() { 322 | if (Thread.currentThread() == workerThread) { 323 | throw new IllegalStateException( 324 | HashedWheelTimer.class.getSimpleName() 325 | + ".stop() cannot be called from " 326 | + TimerTask.class.getSimpleName()); 327 | } 328 | 329 | if (!WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_STARTED, WORKER_STATE_SHUTDOWN)) { 330 | // workerState can be 0 or 2 at this moment - let it always be 2. 331 | if (WORKER_STATE_UPDATER.getAndSet(this, WORKER_STATE_SHUTDOWN) != WORKER_STATE_SHUTDOWN) { 332 | INSTANCE_COUNTER.decrementAndGet(); 333 | } 334 | 335 | return Collections.emptySet(); 336 | } 337 | 338 | try { 339 | boolean interrupted = false; 340 | while (workerThread.isAlive()) { 341 | workerThread.interrupt(); 342 | try { 343 | workerThread.join(100); 344 | } catch (InterruptedException ignored) { 345 | interrupted = true; 346 | } 347 | } 348 | 349 | if (interrupted) { 350 | Thread.currentThread().interrupt(); 351 | } 352 | } finally { 353 | INSTANCE_COUNTER.decrementAndGet(); 354 | } 355 | return worker.unprocessedTimeouts(); 356 | } 357 | 358 | @Override 359 | public boolean isStop() { 360 | return WORKER_STATE_SHUTDOWN == WORKER_STATE_UPDATER.get(this); 361 | } 362 | 363 | @Override 364 | public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) { 365 | if (task == null) { 366 | throw new NullPointerException("task"); 367 | } 368 | if (unit == null) { 369 | throw new NullPointerException("unit"); 370 | } 371 | 372 | long pendingTimeoutsCount = pendingTimeouts.incrementAndGet(); 373 | 374 | if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) { 375 | pendingTimeouts.decrementAndGet(); 376 | throw new RejectedExecutionException( 377 | "Number of pending timeouts (" 378 | + pendingTimeoutsCount 379 | + ") is greater than or equal to maximum allowed pending " 380 | + "timeouts (" 381 | + maxPendingTimeouts 382 | + ")"); 383 | } 384 | 385 | start(); 386 | 387 | // Add the timeout to the timeout queue which will be processed on the next tick. 388 | // During processing all the queued HashedWheelTimeouts will be added to the correct 389 | // HashedWheelBucket. 390 | long deadline = System.nanoTime() + unit.toNanos(delay) - startTime; 391 | 392 | // Guard against overflow. 393 | if (delay > 0 && deadline < 0) { 394 | deadline = Long.MAX_VALUE; 395 | } 396 | HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline); 397 | timeouts.add(timeout); 398 | return timeout; 399 | } 400 | 401 | /** Returns the number of pending timeouts of this {@link Timer}. */ 402 | public long pendingTimeouts() { 403 | return pendingTimeouts.get(); 404 | } 405 | 406 | private static void reportTooManyInstances() { 407 | String resourceType = ClassUtils.simpleClassName(HashedWheelTimer.class); 408 | logger.error( 409 | "You are creating too many " 410 | + resourceType 411 | + " instances. " 412 | + resourceType 413 | + " is a shared resource that must be reused across the JVM," 414 | + "so that only a few instances are created."); 415 | } 416 | 417 | private final class Worker implements Runnable { 418 | private final Set unprocessedTimeouts = new HashSet(); 419 | 420 | private long tick; 421 | 422 | @Override 423 | public void run() { 424 | // Initialize the startTime. 425 | startTime = System.nanoTime(); 426 | if (startTime == 0) { 427 | // We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when 428 | // initialized. 429 | startTime = 1; 430 | } 431 | 432 | // Notify the other threads waiting for the initialization at start(). 433 | startTimeInitialized.countDown(); 434 | 435 | do { 436 | final long deadline = waitForNextTick(); 437 | if (deadline > 0) { 438 | int idx = (int) (tick & mask); 439 | processCancelledTasks(); 440 | HashedWheelBucket bucket = wheel[idx]; 441 | transferTimeoutsToBuckets(); 442 | bucket.expireTimeouts(deadline); 443 | tick++; 444 | } 445 | } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED); 446 | 447 | // Fill the unprocessedTimeouts so we can return them from stop() method. 448 | for (HashedWheelBucket bucket : wheel) { 449 | bucket.clearTimeouts(unprocessedTimeouts); 450 | } 451 | for (; ; ) { 452 | HashedWheelTimeout timeout = timeouts.poll(); 453 | if (timeout == null) { 454 | break; 455 | } 456 | if (!timeout.isCancelled()) { 457 | unprocessedTimeouts.add(timeout); 458 | } 459 | } 460 | processCancelledTasks(); 461 | } 462 | 463 | private void transferTimeoutsToBuckets() { 464 | // transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread 465 | // when it just 466 | // adds new timeouts in a loop. 467 | for (int i = 0; i < 100000; i++) { 468 | HashedWheelTimeout timeout = timeouts.poll(); 469 | if (timeout == null) { 470 | // all processed 471 | break; 472 | } 473 | if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) { 474 | // Was cancelled in the meantime. 475 | continue; 476 | } 477 | 478 | long calculated = timeout.deadline / tickDuration; 479 | timeout.remainingRounds = (calculated - tick) / wheel.length; 480 | 481 | // Ensure we don't schedule for past. 482 | final long ticks = Math.max(calculated, tick); 483 | int stopIndex = (int) (ticks & mask); 484 | 485 | HashedWheelBucket bucket = wheel[stopIndex]; 486 | bucket.addTimeout(timeout); 487 | } 488 | } 489 | 490 | private void processCancelledTasks() { 491 | for (; ; ) { 492 | HashedWheelTimeout timeout = cancelledTimeouts.poll(); 493 | if (timeout == null) { 494 | // all processed 495 | break; 496 | } 497 | try { 498 | timeout.remove(); 499 | } catch (Throwable t) { 500 | if (logger.isWarnEnabled()) { 501 | logger.warn("An exception was thrown while process a cancellation task", t); 502 | } 503 | } 504 | } 505 | } 506 | 507 | /** 508 | * calculate goal nanoTime from startTime and current tick number, then wait until that goal has 509 | * been reached. 510 | * 511 | * @return Long.MIN_VALUE if received a shutdown request, current time otherwise (with 512 | * Long.MIN_VALUE changed by +1) 513 | */ 514 | private long waitForNextTick() { 515 | long deadline = tickDuration * (tick + 1); 516 | 517 | for (; ; ) { 518 | final long currentTime = System.nanoTime() - startTime; 519 | long sleepTimeMs = (deadline - currentTime + 999999) / 1000000; 520 | 521 | if (sleepTimeMs <= 0) { 522 | if (currentTime == Long.MIN_VALUE) { 523 | return -Long.MAX_VALUE; 524 | } else { 525 | return currentTime; 526 | } 527 | } 528 | if (isWindows()) { 529 | sleepTimeMs = sleepTimeMs / 10 * 10; 530 | } 531 | 532 | try { 533 | Thread.sleep(sleepTimeMs); 534 | } catch (InterruptedException ignored) { 535 | if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) { 536 | return Long.MIN_VALUE; 537 | } 538 | } 539 | } 540 | } 541 | 542 | Set unprocessedTimeouts() { 543 | return Collections.unmodifiableSet(unprocessedTimeouts); 544 | } 545 | } 546 | 547 | private static final class HashedWheelTimeout implements Timeout { 548 | 549 | private static final int ST_INIT = 0; 550 | private static final int ST_CANCELLED = 1; 551 | private static final int ST_EXPIRED = 2; 552 | private static final AtomicIntegerFieldUpdater STATE_UPDATER = 553 | AtomicIntegerFieldUpdater.newUpdater(HashedWheelTimeout.class, "state"); 554 | 555 | private final HashedWheelTimer timer; 556 | private final TimerTask task; 557 | private final long deadline; 558 | 559 | @SuppressWarnings({"unused", "FieldMayBeFinal", "RedundantFieldInitialization"}) 560 | private volatile int state = ST_INIT; 561 | 562 | /** 563 | * RemainingRounds will be calculated and set by Worker.transferTimeoutsToBuckets() before the 564 | * HashedWheelTimeout will be added to the correct HashedWheelBucket. 565 | */ 566 | long remainingRounds; 567 | 568 | /** 569 | * This will be used to chain timeouts in HashedWheelTimerBucket via a double-linked-list. As 570 | * only the workerThread will act on it there is no need for synchronization / volatile. 571 | */ 572 | HashedWheelTimeout next; 573 | 574 | HashedWheelTimeout prev; 575 | 576 | /** The bucket to which the timeout was added */ 577 | HashedWheelBucket bucket; 578 | 579 | HashedWheelTimeout(HashedWheelTimer timer, TimerTask task, long deadline) { 580 | this.timer = timer; 581 | this.task = task; 582 | this.deadline = deadline; 583 | } 584 | 585 | @Override 586 | public Timer timer() { 587 | return timer; 588 | } 589 | 590 | @Override 591 | public TimerTask task() { 592 | return task; 593 | } 594 | 595 | @Override 596 | public boolean cancel() { 597 | // only update the state it will be removed from HashedWheelBucket on next tick. 598 | if (!compareAndSetState(ST_INIT, ST_CANCELLED)) { 599 | return false; 600 | } 601 | // If a task should be canceled we put this to another queue which will be processed on each 602 | // tick. 603 | // So this means that we will have a GC latency of max. 1 tick duration which is good enough. 604 | // This way 605 | // we can make again use of our MpscLinkedQueue and so minimize the locking / overhead as much 606 | // as possible. 607 | timer.cancelledTimeouts.add(this); 608 | return true; 609 | } 610 | 611 | void remove() { 612 | HashedWheelBucket bucket = this.bucket; 613 | if (bucket != null) { 614 | bucket.remove(this); 615 | } else { 616 | timer.pendingTimeouts.decrementAndGet(); 617 | } 618 | } 619 | 620 | public boolean compareAndSetState(int expected, int state) { 621 | return STATE_UPDATER.compareAndSet(this, expected, state); 622 | } 623 | 624 | public int state() { 625 | return state; 626 | } 627 | 628 | @Override 629 | public boolean isCancelled() { 630 | return state() == ST_CANCELLED; 631 | } 632 | 633 | @Override 634 | public boolean isExpired() { 635 | return state() == ST_EXPIRED; 636 | } 637 | 638 | public void expire() { 639 | if (!compareAndSetState(ST_INIT, ST_EXPIRED)) { 640 | return; 641 | } 642 | 643 | try { 644 | task.run(this); 645 | } catch (Throwable t) { 646 | if (logger.isWarnEnabled()) { 647 | logger.warn("An exception was thrown by " + TimerTask.class.getSimpleName() + '.', t); 648 | } 649 | } 650 | } 651 | 652 | @Override 653 | public String toString() { 654 | final long currentTime = System.nanoTime(); 655 | long remaining = deadline - currentTime + timer.startTime; 656 | String simpleClassName = ClassUtils.simpleClassName(this.getClass()); 657 | 658 | StringBuilder buf = 659 | new StringBuilder(192).append(simpleClassName).append('(').append("deadline: "); 660 | if (remaining > 0) { 661 | buf.append(remaining).append(" ns later"); 662 | } else if (remaining < 0) { 663 | buf.append(-remaining).append(" ns ago"); 664 | } else { 665 | buf.append("now"); 666 | } 667 | 668 | if (isCancelled()) { 669 | buf.append(", cancelled"); 670 | } 671 | 672 | return buf.append(", task: ").append(task()).append(')').toString(); 673 | } 674 | } 675 | 676 | /** 677 | * Bucket that stores HashedWheelTimeouts. These are stored in a linked-list like datastructure to 678 | * allow easy removal of HashedWheelTimeouts in the middle. Also the HashedWheelTimeout act as 679 | * nodes themself and so no extra object creation is needed. 680 | */ 681 | private static final class HashedWheelBucket { 682 | 683 | /** Used for the linked-list datastructure */ 684 | private HashedWheelTimeout head; 685 | 686 | private HashedWheelTimeout tail; 687 | 688 | /** Add {@link HashedWheelTimeout} to this bucket. */ 689 | void addTimeout(HashedWheelTimeout timeout) { 690 | assert timeout.bucket == null; 691 | timeout.bucket = this; 692 | if (head == null) { 693 | head = tail = timeout; 694 | } else { 695 | tail.next = timeout; 696 | timeout.prev = tail; 697 | tail = timeout; 698 | } 699 | } 700 | 701 | /** Expire all {@link HashedWheelTimeout}s for the given {@code deadline}. */ 702 | void expireTimeouts(long deadline) { 703 | HashedWheelTimeout timeout = head; 704 | 705 | // process all timeouts 706 | while (timeout != null) { 707 | HashedWheelTimeout next = timeout.next; 708 | if (timeout.remainingRounds <= 0) { 709 | next = remove(timeout); 710 | if (timeout.deadline <= deadline) { 711 | timeout.expire(); 712 | } else { 713 | // The timeout was placed into a wrong slot. This should never happen. 714 | throw new IllegalStateException( 715 | String.format("timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline)); 716 | } 717 | } else if (timeout.isCancelled()) { 718 | next = remove(timeout); 719 | } else { 720 | timeout.remainingRounds--; 721 | } 722 | timeout = next; 723 | } 724 | } 725 | 726 | public HashedWheelTimeout remove(HashedWheelTimeout timeout) { 727 | HashedWheelTimeout next = timeout.next; 728 | // remove timeout that was either processed or cancelled by updating the linked-list 729 | if (timeout.prev != null) { 730 | timeout.prev.next = next; 731 | } 732 | if (timeout.next != null) { 733 | timeout.next.prev = timeout.prev; 734 | } 735 | 736 | if (timeout == head) { 737 | // if timeout is also the tail we need to adjust the entry too 738 | if (timeout == tail) { 739 | tail = null; 740 | head = null; 741 | } else { 742 | head = next; 743 | } 744 | } else if (timeout == tail) { 745 | // if the timeout is the tail modify the tail to be the prev node. 746 | tail = timeout.prev; 747 | } 748 | // null out prev, next and bucket to allow for GC. 749 | timeout.prev = null; 750 | timeout.next = null; 751 | timeout.bucket = null; 752 | timeout.timer.pendingTimeouts.decrementAndGet(); 753 | return next; 754 | } 755 | 756 | /** Clear this bucket and return all not expired / cancelled {@link Timeout}s. */ 757 | void clearTimeouts(Set set) { 758 | for (; ; ) { 759 | HashedWheelTimeout timeout = pollTimeout(); 760 | if (timeout == null) { 761 | return; 762 | } 763 | if (timeout.isExpired() || timeout.isCancelled()) { 764 | continue; 765 | } 766 | set.add(timeout); 767 | } 768 | } 769 | 770 | private HashedWheelTimeout pollTimeout() { 771 | HashedWheelTimeout head = this.head; 772 | if (head == null) { 773 | return null; 774 | } 775 | HashedWheelTimeout next = head.next; 776 | if (next == null) { 777 | tail = this.head = null; 778 | } else { 779 | this.head = next; 780 | next.prev = null; 781 | } 782 | 783 | // null out prev and next to allow for GC. 784 | head.next = null; 785 | head.prev = null; 786 | head.bucket = null; 787 | return head; 788 | } 789 | } 790 | 791 | private boolean isWindows() { 792 | return System.getProperty("os.name", "").toLowerCase(Locale.US).contains("win"); 793 | } 794 | } 795 | --------------------------------------------------------------------------------