2.0版本RPC-Server改動不大,主要變化在於RPC-Client使用了服務地址緩存,並引入監控機制,第一時間獲取zk集羣中服務地址信息變化並刷新本地緩存。另外,RPC-Client還使用了RpcClientProperties開放對負載均衡策略和序列化策略的選擇。
系列文章:
手寫通用類型負載均衡路由引擎(含隨機、輪詢、哈希等及其帶權形式)
實現 序列化引擎(支持 JDK默認、Hessian、Json、Protostuff、Xml、Avro、ProtocolBuffer、Thrift等序列化方式)
從零寫分佈式RPC框架 系列 2.0 (1)架構升級
從零寫分佈式RPC框架 系列 2.0 (2)RPC-Common模塊設計實現
從零寫分佈式RPC框架 系列 2.0 (3)RPC-Server和RPC-Client模塊改造
文章目錄
RPC-Server
1 結構圖
注意,RpcService註解移動到了RPC-Common模塊下,另外新加了ServiceInfo代表將存到註冊中心的服務信息(也在RPC-Common模塊下),其他的除了RpcServer基本沒有變化
2 RpcService註解
主要是多了weight和workerThreads,分別代表權重和最大工作線程數。
/**
* @version V1.0
* @author: lin_shen
* @date: 2018/10/31
* @Description:
* RPC服務註解(標註在rpc服務實現類上)
* 使用@Service註解使被@RpcService標註的類都能被Spring管理
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Service
public @interface RpcService {
Class<?> value();
int weight() default 1;
int workerThreads() default 10;
}
3 ServiceInfo
/**
* @version V1.0
* @author: lin_shen
* @date: 18-11-13
* @Description: 服務信息,用於存儲到註冊中心
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceInfo implements WeightGetAble {
private String host;
private int port;
/**
* 權重信息
*/
private int weight;
/**
* 最大工作線程數
*/
private int workerThreads;
public ServiceInfo (ServiceInfo serviceInfo){
this.host = serviceInfo.host;
this.port = serviceInfo.port;
this.weight = serviceInfo.weight;
this.workerThreads = serviceInfo.workerThreads;
}
@Override
public int getWeightFactors() {
return getWeight();
}
}
4 RpcServer
RpcServer主要是多了對 serviceSemaphoreMap 和 serviceRpcServiceMap的管理。其中serviceSemaphoreMap 將作爲參數傳入RpcServerHandler提供限流信息,而serviceRpcServiceMap將註冊到ZK集羣。
/**
* @version V1.0
* @author: lin_shen
* @date: 2018/10/31
* @Description: TODO
*/
@Log4j2
@AutoConfigureAfter({ZKServiceRegistry.class})
@EnableConfigurationProperties(RpcServerProperties.class)
public class RpcServer implements ApplicationContextAware, InitializingBean {
/**
* 存放 服務名稱 與 服務實例 之間的映射關係
*/
private Map<String,Object> handlerMap=new HashMap<>();
/**
* 存放 服務名稱 與 信號量 之間的映射關係
* 用於限制每個服務的工作線程數
*/
private Map<String, Semaphore> serviceSemaphoreMap=new HashMap<>();
/**
* 存放 服務名稱 與 服務信息 之間的映射關係
*/
private Map<String, RpcService> serviceRpcServiceMap=new HashMap<>();
@Autowired
private RpcServerProperties rpcProperties;
@Autowired
private ZKServiceRegistry rpcServiceRegistry;
/**
* 在類初始化時執行,將所有被@RpcService標記的類納入管理
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//獲取帶有@RpcService註解的類
Map<String,Object> rpcServiceMap=applicationContext.getBeansWithAnnotation(RpcService.class);
//以@RpcService註解的value的類的類名爲鍵將該標記類存入handlerMap和serviceSemaphoreMap
if(!CollectionUtils.isEmpty(rpcServiceMap)){
for(Object object:rpcServiceMap.values()){
RpcService rpcService=object.getClass().getAnnotation(RpcService.class);
String serviceName=rpcService.value().getName();
handlerMap.put(serviceName,object);
serviceSemaphoreMap.put(serviceName,new Semaphore(rpcService.workerThreads()));
serviceRpcServiceMap.put(serviceName,rpcService);
}
}
}
/**
* 在所有屬性值設置完成後執行,負責啓動RPC服務
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
//管理相關childGroup
EventLoopGroup bossGroup=new NioEventLoopGroup();
//處理相關RPC請求
EventLoopGroup childGroup=new NioEventLoopGroup();
try {
//啓動RPC服務
ServerBootstrap bootstrap=new ServerBootstrap();
bootstrap.group(bossGroup,childGroup);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG,1024)
.childOption(ChannelOption.TCP_NODELAY,true)
.handler(new LoggingHandler(LogLevel.INFO));
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline=channel.pipeline();
//解碼RPC請求
pipeline.addLast(new RemotingTransporterDecoder());
//編碼RPC請求
pipeline.addFirst(new RemotingTransporterEncoder());
//處理RPC請求
pipeline.addLast(new RpcServerHandler(handlerMap,serviceSemaphoreMap));
}
});
//同步啓動,RPC服務器啓動完畢後才執行後續代碼
ChannelFuture future=bootstrap.bind(rpcProperties.getPort()).sync();
log.info("server started,listening on {}",rpcProperties.getPort());
//啓動後註冊服務
registry();
//釋放資源
future.channel().closeFuture().sync();
}catch (Exception e){
log.entry("server exception",e);
}finally {
//關閉RPC服務
childGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
private void registry() throws UnknownHostException {
//註冊RPC服務地址
String hostAddress=InetAddress.getLocalHost().getHostAddress();
int port=rpcProperties.getPort();
for(String interfaceName:handlerMap.keySet()){
ServiceInfo serviceInfo=
new ServiceInfo(hostAddress,port,serviceRpcServiceMap.get(interfaceName).weight(),serviceRpcServiceMap.get(interfaceName).workerThreads());
String serviceInfoString= JSON.toJSONString(serviceInfo);
rpcServiceRegistry.register(interfaceName,serviceInfoString);
log.info("register service:{}=>{}",interfaceName,serviceInfoString);
}
}
}
RPC-Client
1 結構圖
新增RpcClientProperties提供配置屬性讀入(路由策略和序列化方式),ZKServiceDiscovery增加ConcurrentMap<String,List> servicePathsMap來管理服務地址列表。RpcClient相應作出調整。
2 RpcClientProperties
注意這裏屬性用的是枚舉類型而不是字符串,另外默認路由策略是隨機,默認序列化策略是json
@Data
@ConfigurationProperties(prefix = "rpc.client")
public class RpcClientProperties {
private RouteStrategyEnum routeStrategy= RouteStrategyEnum.Random;
private SerializeTypeEnum serializeType=SerializeTypeEnum.JSON;
}
3 ZKServiceDiscovery
這裏使用了IZkChildListener 來對目標路徑下子節點變化進行監控,如果發生變化(新增或刪減)則重新執行discover方法拉取最新服務地址列表。
zkChildListenerMap的作用是管理服務和對應的服務地址列表監聽器,避免重複註冊監聽器。
/**
* @version V1.0
* @author: lin_shen
* @date: 2018/10/31
* @Description: zookeeper服務註冊中心
*/
@Component
@Log4j2
@EnableConfigurationProperties(ZKProperties.class)
public class ZKServiceDiscovery {
@Autowired
private ZKProperties zkProperties;
/**
* 服務名和服務地址列表的Map
*/
private ConcurrentMap<String,List<String>> servicePathsMap=new ConcurrentHashMap<>();
/**
* 服務監聽器 Map,監聽子節點服務信息
*/
private ConcurrentMap<String, IZkChildListener> zkChildListenerMap=new ConcurrentHashMap<>();
private ZkClient zkClient;
@PostConstruct
public void init() {
// 創建 ZooKeeper 客戶端
zkClient = new ZkClient(zkProperties.getAddress(), zkProperties.getSessionTimeOut(), zkProperties.getConnectTimeOut());
log.info("connect to zookeeper");
}
/**
*
* 根據服務名獲取服務地址並保持監控
* @param serviceName
* @return
*/
public void discover(String serviceName){
log.info("discovering:"+serviceName);
String servicePath=zkProperties.getRegistryPath()+"/"+serviceName;
//找不到對應服務
if(!zkClient.exists(servicePath)){
throw new RuntimeException("can not find any service node on path: "+servicePath);
}
//獲取服務地址列表
List<String> addressList=zkClient.getChildren(servicePath);
if(CollectionUtils.isEmpty(addressList)){
throw new RuntimeException("can not find any address node on path: "+servicePath);
}
//保存地址列表
List<String> paths=new ArrayList<>(addressList.size());
for(String address:addressList){
paths.add(zkClient.readData(servicePath+"/"+address));
}
servicePathsMap.put(serviceName,paths);
//保持監控
if(!zkChildListenerMap.containsKey(serviceName)){
IZkChildListener iZkChildListener= (parentPath, currentChilds) -> {
//當子節點列表變化時重新discover
discover(serviceName);
log.info("子節點列表發生變化 ");
};
zkClient.subscribeChildChanges(servicePath, iZkChildListener);
zkChildListenerMap.put(serviceName,iZkChildListener);
}
}
public List<String> getAddressList(String serviceName){
List<String> addressList=servicePathsMap.get(serviceName);
if(addressList==null||addressList.isEmpty()){
discover(serviceName);
return servicePathsMap.get(serviceName);
}
return addressList;
}
}
4 RpcClient
主要是配合RemotingTransporter做了調整和升級,整體變化不大。
另外一個要注意的就是 ConcurrentMap<String,RouteStrategy> serviceRouteStrategyMap,用於在使用輪詢策略時,爲不同的服務調用保管對應的輪詢器(輪詢器內部存儲index記錄,是有狀態的)。
@Log4j2
@Component
@AutoConfigureAfter(ZKServiceDiscovery.class)
@EnableConfigurationProperties(RpcClientProperties.class)
public class RpcClient {
@Autowired
private ZKServiceDiscovery zkServiceDiscovery;
@Autowired
private RpcClientProperties rpcClientProperties;
/**
* 維持服務的 輪詢 路由狀態
* 不同服務狀態不同(服務列表也不同)
* 非輪詢無需維持狀態
*/
private ConcurrentMap<String,RouteStrategy> serviceRouteStrategyMap=new ConcurrentHashMap<>();
/**
* 存放請求編號與響應對象的映射關係
*/
private ConcurrentMap<Long, RemotingTransporter> remotingTransporterMap=new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public <T> T create(final Class<?> interfaceClass){
//創建動態代理對象
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
(proxy, method, args) -> {
//創建RPC請求對象
RpcRequest rpcRequest=new RpcRequest();
rpcRequest.setInterfaceName(method.getDeclaringClass().getName());
rpcRequest.setMethodName(method.getName());
rpcRequest.setParameterTypes(method.getParameterTypes());
rpcRequest.setParameters(args);
//獲取RPC服務信息列表
String serviceName=interfaceClass.getName();
List<String> addressList=zkServiceDiscovery.getAddressList(serviceName);
List<ServiceInfo> serviceInfoList=new ArrayList<>(addressList.size());
for(String serviceInfoString:addressList){
serviceInfoList.add(JSON.parseObject(serviceInfoString,ServiceInfo.class));
}
//根據配置文件獲取路由策略
log.info("使用負載均衡策略:"+rpcClientProperties.getRouteStrategy());
log.info("使用序列化策略:"+rpcClientProperties.getSerializeType());
RouteStrategy routeStrategy ;
//如果使用輪詢,則需要保存狀態(按服務名保存)
if(RouteStrategyEnum.Polling==rpcClientProperties.getRouteStrategy()){
routeStrategy=serviceRouteStrategyMap.getOrDefault(serviceName,RouteEngine.queryClusterStrategy(RouteStrategyEnum.Polling));
serviceRouteStrategyMap.put(serviceName,routeStrategy);
}else {
routeStrategy= RouteEngine.queryClusterStrategy(rpcClientProperties.getRouteStrategy());
}
//根據路由策略選取服務提供方
ServiceInfo serviceInfo = routeStrategy.select(serviceInfoList);
RemotingTransporter remotingTransporter=new RemotingTransporter();
//設置flag爲請求,雙路,非ping,非其他,序列化方式爲 配置文件中SerializeTypeEnum對應的code
remotingTransporter.setFlag(new RemotingTransporter.Flag(true,true,false,false,rpcClientProperties.getSerializeType().getCode()));
remotingTransporter.setBodyContent(rpcRequest);
log.info("get serviceInfo:"+serviceInfo);
//從RPC服務地址中解析主機名與端口號
//發送RPC請求
RpcResponse rpcResponse=send(remotingTransporter,serviceInfo.getHost(),serviceInfo.getPort());
//獲取響應結果
if(rpcResponse==null){
log.error("send request failure",new IllegalStateException("response is null"));
return null;
}
if(rpcResponse.getException()!=null){
log.error("response has exception",rpcResponse.getException());
return null;
}
return rpcResponse.getResult();
}
);
}
private RpcResponse send(RemotingTransporter remotingTransporter,String host,int port){
log.info("send begin: "+host+":"+port);
//客戶端線程爲1即可
EventLoopGroup group=new NioEventLoopGroup(1);
try {
//創建RPC連接
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline=channel.pipeline();
pipeline.addLast(new RemotingTransporterDecoder())
.addFirst(new RemotingTransporterEncoder())
.addLast(new RpcClientHandler(remotingTransporterMap));
}
});
ChannelFuture future=bootstrap.connect(host,port).sync();
Channel channel=future.channel();
log.info("invokeId: "+remotingTransporter.getInvokeId());
//寫入RPC請求對象
channel.writeAndFlush(remotingTransporter).sync();
channel.closeFuture().sync();
log.info("send end");
//獲取RPC響應對象
return (RpcResponse) remotingTransporterMap.get(remotingTransporter.getInvokeId()).getBodyContent();
}catch (Exception e){
log.error("client exception",e);
return null;
}finally {
group.shutdownGracefully();
//移除請求編號和響應對象直接的映射關係
remotingTransporterMap.remove(remotingTransporter.getInvokeId());
}
}
}