一起写RPC框架(十)RPC服务提供端二--服务的编织和注册

首先先进行服务的编织,将一个服务的一些基本信息编织成一个类,发送给注册中心,订阅者在注册中心取到的编织信息就可以调用该方法,这是整体的思路,我们在网络篇说过,所有的数据传输走RemotingTransporter,核心的传输主体要实现CommonCustomBody接口,接下来,我们就定义Provider发送给注册的类:


编织的服务信息基本信息应该有

1)服务的IP地址

2)端口号

3)服务名,这个应该是唯一的

4)是否是VIP服务,如果是VIP服务则当启动NettyServer需要在port-2的端口上监听

5)是否支持降级

6)降级服务的方法路径,这边降级做的比较简单,其实就是一个mock方法

7)降级服务的基本描述(其实并不是必要的,不过却可以用来做统计)

8)服务的权重,这个是很必要的,应该每个系统的线上实例肯定不止一台,而每一台实例的性能也不一样,有些服务器的性能好一点,内存大一点,可以设置大一点,最大100,最小1,这样在服务端调用该实例的时候,默认是使用加权随机负载均衡的算法,去随机访问服务提供端的

9)连接数,该连接数表示的是一个Consumer实例与一个Provider实例之间连接数,一般情况下,一个连接就够用了,特殊情况下,可以设置多个链接



好了,假如将上述的9个属性发送给注册中心,注册中心就可以确定唯一的服务了,但是一个简单的服务与生俱来是不具备这个属性的,但是如果你想让一个普通的服务例如上上节说的那个HelloServiceMock的服务有此属性的话,最最好用的方法就是让其绑定我们自定义的Annotation了,这样就可以很自然地付上这些属性了


Annotation定义很简单,与这个9个属性一一对应就可以了

package org.laopopo.client.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 
 * @author BazingaLyn
 * @description 服务提供端提供服务的annotation
 * @time 2016年8月19日
 * @modifytime
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Documented
public @interface RPCService {

	public String serviceName() default "";

	public int weight() default 50;

	public String responsibilityName() default "system";

	public int connCount() default 1;
	
	public boolean isVIPService() default false;
	
	public boolean isSupportDegradeService() default false;
	
	public String degradeServicePath() default "";
	
	public String degradeServiceDesc() default "";
	
	public long maxCallCountInMinute() default 10000;
	
}
注释就不加了,很容易理解,使用的时候就很简单了,如下:

package org.laopopo.example.demo.service;

import org.laopopo.client.annotation.RPCService;

/**
 * 
 * @author BazingaLyn
 * @description Demo
 * @time 2016年8月19日
 * @modifytime
 */
public class HelloSerivceImpl implements HelloSerivce {

	@Override
	@RPCService(responsibilityName="xiaoy",
				serviceName="LAOPOPO.TEST.SAYHELLO",
				isVIPService = false,
				isSupportDegradeService = true,
				degradeServicePath="org.laopopo.example.demo.service.HelloServiceMock",
				degradeServiceDesc="默认返回hello")
	public String sayHello(String str) {
		return "hello "+ str;
	}

}
这样做就可以使得这个方法绑定上这些特殊的属性了,接下来,我们只需要读取这些annotation的属性值,编织成注册信息类发送给注册中心,Provider端的功能就算完成了大半了


我们先看看如何使用启动Provider的某个实例,写个main函数:

package org.laopopo.example.generic.test_3;

import org.laopopo.client.provider.DefaultProvider;
import org.laopopo.common.exception.remoting.RemotingException;
import org.laopopo.example.demo.service.ByeServiceImpl;
import org.laopopo.example.demo.service.HelloSerivceImpl;
import org.laopopo.remoting.netty.NettyClientConfig;
import org.laopopo.remoting.netty.NettyServerConfig;

public class ProviderTest {
	
public static void main(String[] args) throws InterruptedException, RemotingException {
		
		DefaultProvider defaultProvider = new DefaultProvider(new NettyClientConfig(), new NettyServerConfig());
		
		
		defaultProvider.registryAddress("127.0.0.1:18010") //注册中心的地址
					   .monitorAddress("127.0.0.1:19010") //监控中心的地址
					   .serviceListenPort(8899) //暴露服务的地址
					   .publishService(new HelloSerivceImpl(),new ByeServiceImpl()) //暴露的服务
					   .start(); //启动服务
		
	}
	

}
DefaultProvider是Provider接口的具体的实现类,启动一个Provider最最规范的方式就是这样了,至少给其设定如上代码的几个参数:


1)注册中心地址,(其实可以没有,有一种场景就是服务消费者知道服务提供的地址,直接调用,不需要去注册中心上订阅,不过这也是在特殊的情况下使用)

2)监控中心的地址(也是非必要的,不过最好设置上)

3)服务暴露的端口号,这个是必要的,否则消费者如何去与你建立连接进行服务消费活动呢

4)要提供的服务,一切都准备好了,全部是这个服务准备的,如果你不设置你要提供的服务,不是逗别人玩吗

5)start方法启动整个Provider的实例


好了,我们整理一下整个流程,先讲上述5点钟必要的三点,暴露端口,提供服务,和start的方法,我们先看Provider.java这个最底层的接口类,我们上面讲过,服务提供者发布要提供的服务,需要调用Provider的publishService的方法

一起写RPC框架(十)RPC服务提供端二--服务的编织和注册_第1张图片

我们看着它的实现类,在DefaultProvider中,方法也很简单,将其用户传递的参数赋值给了全局变量obj

一起写RPC框架(十)RPC服务提供端二--服务的编织和注册_第2张图片

相同的,暴露的端口号也是赋值给DefaultProvider的全局变量exposePort的,所以真正去编制服务应该是在start方法中进行的

一起写RPC框架(十)RPC服务提供端二--服务的编织和注册_第3张图片

我们先关注编制的方法:

this.publishRemotingTransporters = providerController.getLocalServerWrapperManager().wrapperRegisterInfo(this.getExposePort(), this.obj);
我们看具体的实现类:

/**
	 * 
	 * @param listeningAddress 该服务暴露的网络地址 例如172.30.53.58::8989
	 * @param controller 全局限流工具
	 * @param obj 暴露的方法的实例
	 * @return
	 */
	public List wrapperRegisterInfo(int port, Object... obj) {

		List remotingTransporters = new ArrayList();
		
		//基本判断,如果暴露的方法是null或者是0,则说明无需编织服务
		if (null != obj && obj.length > 0) {
			
			for (Object o : obj) {
				
				//默认的编织对象
				DefaultServiceWrapper defaultServiceWrapper = new DefaultServiceWrapper();
				
				List serviceWrappers = defaultServiceWrapper.provider(o).create();
				
				if(null != serviceWrappers  && !serviceWrappers.isEmpty()){
					for(ServiceWrapper serviceWrapper : serviceWrappers){
						
						PublishServiceCustomBody commonCustomHeader = new PublishServiceCustomBody();
						
						commonCustomHeader.setConnCount(serviceWrapper.getConnCount());
						commonCustomHeader.setDegradeServiceDesc(serviceWrapper.getDegradeServiceDesc());
						commonCustomHeader.setDegradeServicePath(serviceWrapper.getDegradeServicePath());
						commonCustomHeader.setPort(port);
						commonCustomHeader.setServiceProviderName(serviceWrapper.getServiceName());
						commonCustomHeader.setVIPService(serviceWrapper.isVIPService());
						commonCustomHeader.setWeight(serviceWrapper.getWeight());
						commonCustomHeader.setSupportDegradeService(serviceWrapper.isSupportDegradeService());
						commonCustomHeader.setMaxCallCountInMinute(serviceWrapper.getMaxCallCountInMinute());
						
						RemotingTransporter remotingTransporter =  RemotingTransporter.createRequestTransporter(LaopopoProtocol.PUBLISH_SERVICE, commonCustomHeader);
						remotingTransporters.add(remotingTransporter);
					}
				}
			}
		}
		return remotingTransporters;
		
	}
可以很清晰的看到这个方法最最核心的方法就是编制了一个List这个对象,这个对象就是要发给注册中心的服务订阅类,也就是上面我们说的那9个属性,具体这个9个属性的编织是如下这个方法完成的

List serviceWrappers = defaultServiceWrapper.provider(o).create();

当我们编织List的时候,我们迭代了List这个对象而完成的,ServiceWrapper的实现也很简单:

一起写RPC框架(十)RPC服务提供端二--服务的编织和注册_第4张图片

我们接着关注defaultServiceWrapper.provider(o).create()这个方法,provider方法:

@Override
public ServiceWrapperWorker provider(Object serviceProvider) {
	//如果proxy的对象是null,实例对象无需编织,直接返回
		if(null  == globalProviderProxyHandler){
			this.serviceProvider = serviceProvider;
		}else{
		  Class globalProxyCls = generateProviderProxyClass(globalProviderProxyHandler, serviceProvider.getClass());
	          this.serviceProvider = copyProviderProperties(serviceProvider, newInstance(globalProxyCls));
		}
	return this;
}
其实这个方法是把你要提供的服务做一个定制化的编织,跟Spring的AOP很类似,对类的功能进行了增强,可以完成更多更加强大的功能,加入我们不需要对其进行编织,就原样返回就可以了,详细的代码可以查看源码,这边不是关键

我们接着看create的方法

@Override
		public List create() {
			
			List serviceWrappers = new ArrayList();
			
			//读取对象的方法注解
			RPCService rpcService = null;
			
			for (Class cls = serviceProvider.getClass(); cls != Object.class; cls = cls.getSuperclass()) {
				Method[] methods = cls.getMethods();
				if(null != methods && methods.length > 0){
					
					for(Method method :methods){
						rpcService = method.getAnnotation(RPCService.class);
						if(null != rpcService){
							
							//服务名
							String serviceName = StringUtil.isNullOrEmpty(rpcService.serviceName())?method.getName():rpcService.serviceName();
							//负责人
							String responsiblityName = rpcService.responsibilityName();
							//方法weight
							Integer weight = rpcService.weight();
							//连接数 默认是1 一个实例一个1链接其实是够用的
							Integer connCount = rpcService.connCount();
							//是否支持服务降级
							boolean isSupportDegradeService = rpcService.isSupportDegradeService();
							//是否是VIP服务,如果是VIP服务,则默认是在port-2的端口暴露方法,与其他的方法使用不同的
							boolean isVIPService = rpcService.isVIPService();
							//暴露的降级方法的路径
							String degradeServicePath = rpcService.degradeServicePath();
							//降级方法的描述
							String degradeServiceDesc = rpcService.degradeServiceDesc();
							//每分钟调用的最大调用次数
							Long maxCallCount = rpcService.maxCallCountInMinute();
							if(maxCallCount <= 0){
								throw new RpcWrapperException("max call count must over zero at unit time");
							}
							ServiceFlowControllerManager serviceFlowControllerManager = providerController.getServiceFlowControllerManager();
							serviceFlowControllerManager.setServiceLimitVal(serviceName, maxCallCount);
							//如果是支持服务降级服务,则需要根据降级方法的路径去创建这个实例,并编制proxy
							if(isSupportDegradeService){
								Class degradeClass = null;
								try {
									degradeClass = Class.forName(degradeServicePath);
									Object nativeObj = degradeClass.newInstance();
									if(null  == globalProviderProxyHandler){
										this.mockDegradeServiceProvider = nativeObj;
									}else{
										Class globalProxyCls = generateProviderProxyClass(globalProviderProxyHandler, nativeObj.getClass());
							            this.mockDegradeServiceProvider = copyProviderProperties(nativeObj, newInstance(globalProxyCls));
									}
								} catch (Exception e) {
									logger.error("[{}] class can not create by reflect [{}]",degradeServicePath,e.getMessage());
									throw new RpcWrapperException("degradeService path " + degradeServicePath +"create failed" ); 
								} 
								
							}
							
							String methodName = method.getName();
							Class[] classes = method.getParameterTypes();
							List[]> paramters = new ArrayList[]>();
							paramters.add(classes);
							
							ServiceWrapper serviceWrapper = new ServiceWrapper(serviceProvider,
													   mockDegradeServiceProvider,
													   serviceName,
												           responsiblityName,
													   methodName,
													   paramters,
													   isSupportDegradeService,
													   degradeServicePath,
													   degradeServiceDesc,
													   weight,
													   connCount,
													   isVIPService,
													   maxCallCount);
							//放入到一个缓存中,方便以后consumer来调取服务的时候,该来获取对应真正的编织类
							providerController.getProviderContainer().registerService(serviceName, serviceWrapper);
							
							serviceWrappers.add(serviceWrapper);
						}
					}
				}
			}
			return serviceWrappers;
		}
这个就是如何编织服务的代码了,说来也是简单,因为已经获取到对象实例了,所以只需要使用简单的反射,获取到它方法级的annotation就可以很easy的获取到那9个属性了,当然当中还有一些其他的操作:

1)如果该方法可以降级,则根据mock的路径把降级的类初始化好

2)通过反射获取到方法名,因为很简单,不管你的服务名昵称serviceName多么的炫酷,追根究底消费者端仍旧需要method的名字然后反射去调用

3)为单位时间内限流做了初始化操作(限流下几个小节一起分析吧~)


好了,总之,服务就是在create方法中编织好的


发送编织好的服务就变得相对很简单了,在start方法中,有一个

try {
	// 发布任务
	this.publishedAndStartProvider();
	logger.info("provider start successfully");
    } catch (Exception e) {
	logger.error("publish service to registry failed [{}]",e.getMessage());
    }
这个方法就是发布服务到注册中心的方法,看看具体的实现,也很简单

public void publishedAndStartProvider() throws InterruptedException, RemotingException {

		// stack copy
		List transporters = defaultProvider.getPublishRemotingTransporters();
		
		if(null == transporters || transporters.isEmpty()){
			logger.warn("service is empty please call DefaultProvider #publishService method");
			return;
		}
		
		String address = defaultProvider.getRegistryAddress();

		if (address == null) {
            logger.warn("registry center address is empty please check your address");
            return;
		}
		String[] addresses = address.split(",");
		if (null != addresses && addresses.length > 0 ) {
			
			for (String eachAddress : addresses) {
				
				for (RemotingTransporter request : transporters) {
					
					pushPublishServiceToRegistry(request,eachAddress);

				}
			}
		}
	}

获取到注册中心的地址,使用我们网络篇的一些API,就可以很方便地将其发送出去了,当然因为可能注册中心是多个,所以每一个provider实例需要将服务信息发送给每一个注册中心,每个注册中心收到服务消息的时候,都会反馈一个ACK,告诉provider端,服务已经注册成功,如果没有成功,则provider重新发送一次

private void pushPublishServiceToRegistry(RemotingTransporter request, String eachAddress) throws InterruptedException, RemotingException {
		logger.info("[{}] transporters matched", request);
		messagesNonAcks.put(request.getOpaque(), new MessageNonAck(request, eachAddress));
		RemotingTransporter remotingTransporter = defaultProvider.getNettyRemotingClient().invokeSync(eachAddress, request, 3000);
		if(null != remotingTransporter){
			AckCustomBody ackCustomBody = serializerImpl().readObject(remotingTransporter.bytes(), AckCustomBody.class);

			logger.info("received ack info [{}]", ackCustomBody);
			if(ackCustomBody.isSuccess()){
				messagesNonAcks.remove(ackCustomBody.getRequestId());
			}
		}else{
			logger.warn("registry center handler timeout");
		}
	}


相关详细的代码可以查看源码:

https://github.com/BazingaLyn/laopopo-rpc/tree/master/laopopo-client/src/main/java/org/laopopo/client/provider


本节END~本小节简单的分析了Provider服务注册的过程~希望大家一起看看代码,发现错误的地方,我们一起纠正~


下一个小节,分析provider如何处理Consumer发送过来的远程调用的请求的~



你可能感兴趣的:(一起写RPC)