Pigeon服务的注册与发现

上一篇:Pigeon的一次调用服务端发生了什么
前面介绍了Pigeon请求调用的过程,那么Pigeon的服务是如何被调用方感知的呢,我们知道通常RPC都会有一个注册中心,以保存服务测的信息(ip,port等等),这样客户端就可以通过注册中心去拿到服务的信息,来完成请求的调用。那一起来看下Pigeon服务的注册和发现机制是如何实现的。

服务的注册

首先,我们来看下服务的注册机制。
在Pigeon中主要使用了@Reference和@Service注解,用来标志服务调用方接口和服务提供方实现类,而对这两个注解进行拦截处理的类是AnnotationBean类,这里是通过实现了Spring的BeanPostProcessor接口来完成处理的(Pigeon相关的@Service类扫描和其BeanDefinition的封装实通过实现了BeanFactoryPostProcessor接口实现的),看下postProcessAfterInitialization方法实现:

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		// 先判断要注入相关属性的Bean是否在指定的注解扫描包下
		Class<?> beanClass = AopUtils.getTargetClass(bean);
		if (beanClass == null || !isMatchPackage(beanClass.getName())) {
			return bean;
		}

		// 判断类定义中是否存在@Service注解
		Service service = beanClass.getAnnotation(Service.class);
		if (service != null) {
			// 如果未自定义接口,则用当前beanClass
			Class serviceInterface = service.interfaceClass();
			if (void.class.equals(service.interfaceClass())) {
				serviceInterface = ServiceConfigUtils.getServiceInterface(beanClass);
			}
			if (serviceInterface == null) {
				serviceInterface = beanClass;
			}

			// 初始化ProviderConfig和ServerConfig,完成服务提供者配置和服务器配置的初始化
			ProviderConfig<Object> providerConfig = new ProviderConfig<Object>(serviceInterface, bean);
			providerConfig.setService(bean);
			providerConfig.setUrl(service.url());
			providerConfig.setVersion(service.version());
			providerConfig.setSharedPool(service.useSharedPool());
			providerConfig.setActives(service.actives());

			ServerConfig serverConfig = new ServerConfig();
			serverConfig.setPort(getDefaultPort(service.port()));
			serverConfig.setSuffix(service.group());
			serverConfig.setAutoSelectPort(service.autoSelectPort());
			providerConfig.setServerConfig(serverConfig);

			// 注册服务提供者,启动服务器,发布服务,完成pigeon提供方调用初始化
			ServiceFactory.addService(providerConfig);
		}

		// 解析bean中方法和属性是否包含Reference,完成bean作为服务调用方的依赖注入。
		postProcessBeforeInitialization(bean, beanName);
		return bean;
	}

这里我们关系的是注册过程,即ServiceFactory.addService(),进去看下它的最终触发的实现

public void doAddService(ProviderConfig providerConfig) {
        try {
            // 检查服务名
            checkServiceName(providerConfig);
            // 发布指定版本服务,同时解析服务方法
            ServicePublisher.addService(providerConfig);
            // 启动netty RPC服务器,作为服务提供方供调用方调用服务
            ServerConfig serverConfig = ProviderBootStrap.startup(providerConfig);
            // 更新serverConfig
            providerConfig.setServerConfig(serverConfig);
            // 实际发布服务,会将服务注册到注册中心,供调用方发现调用
            ServicePublisher.publishService(providerConfig, false);
        } catch (Throwable t) {
            throw new RpcException("error while adding service:" + providerConfig, t);
        }
    }

其中在添加解析服务中,主要进行了以下流程:
判断是否配置了版本, 如果配置了,生成带版本的urlWithVersion,更新key=urlWithVersion的服务,同时如果大于key=url的对应服务版本,会用新版本覆盖默认url版本
如果服务实现了InitializingService接口,调用实现的initialize方法
调用ServiceMethodFactory.init(url)方法,用来初始化调用ServiceMethodFactory的ServiceMethodCache:遍历ServicePublisher的所有服务提供类,建立ServiceMethodCache,存储该类下所有满足要求的方法和方法id的映射关系
首先会忽略掉Object和Class的所有方法
过滤方法后,判断是否需要压缩,根据url+"#"+方法名的方式进行hash。
代码如下

public static <T> void addService(ProviderConfig<T> providerConfig) throws Exception {
    if (logger.isInfoEnabled()) {
        logger.info("add service:" + providerConfig);
    }
    String version = providerConfig.getVersion();
    String url = providerConfig.getUrl();
    // 默认版本,直接以url为key
    if (StringUtils.isBlank(version)) {
        serviceCache.put(url, providerConfig);
    } else {
        // urlWithVersion = url + "_" + version
        String urlWithVersion = getServiceUrlWithVersion(url, version);
        if (serviceCache.containsKey(url)) {
            // 如果已经存在,覆盖服务
            serviceCache.put(urlWithVersion, providerConfig);
            ProviderConfig<?> providerConfigDefault = serviceCache.get(url);
            String defaultVersion = providerConfigDefault.getVersion();
            // 如果默认服务存在默认版本,并且小于当前版本,用当前版本服务更新默认服务版本
            if (!StringUtils.isBlank(defaultVersion)) {
                if (VersionUtils.compareVersion(defaultVersion, providerConfig.getVersion()) < 0) {
                    serviceCache.put(url, providerConfig);
                }
            }
        } else {
            // 将当前版本设为指定版本服务和默认版本服务
            serviceCache.put(urlWithVersion, providerConfig);
            // use this service as the default provider
            serviceCache.put(url, providerConfig);
        }
    }
    // 如果服务实现了InitializingService接口,调用实现的initialize方法
    T service = providerConfig.getService();
    if (service instanceof InitializingService) {
        ((InitializingService) service).initialize();
    }
    // 解析接口自定义方法,根据方法名,参数等相关信息记录方法
    ServiceMethodFactory.init(url);
}

// ServiceMethodFactory.init(url);方法实现如下:
public static void init(String url) {
    getServiceMethodCache(url);
}
// 具体调用了
public static ServiceMethodCache getServiceMethodCache(String url) {
    // 是否存在指定url的ServiceMethodCache
    ServiceMethodCache serviceMethodCache = methods.get(url);
    if (serviceMethodCache == null) {
        // 获取指定url的providerConfig
        Map<String, ProviderConfig<?>> services = ServicePublisher.getAllServiceProviders();
        ProviderConfig<?> providerConfig = services.get(url);
        if (providerConfig != null) {
            Object service = providerConfig.getService();
            Method[] methodArray = service.getClass().getMethods();
            serviceMethodCache = new ServiceMethodCache(url, service);
            // 遍历指定url的所有服务方法
            for (Method method : methodArray) {
                // 忽略掉Object和Class的所有方法
                if (!ingoreMethods.contains(method.getName())) {
                    method.setAccessible(true);
                    serviceMethodCache.addMethod(method.getName(), new ServiceMethod(service, method));

                    if (isCompact) {
                        // 压缩url,方法名等调用所需信息
                        int id = LangUtils.hash(url + "#" + method.getName(), 0, Integer.MAX_VALUE);
                        ServiceId serviceId = new ServiceId(url, method.getName());
                        ServiceId lastId = CompactRequest.PROVIDER_ID_MAP.putIfAbsent(id, serviceId);
                        // 检查如果存在相同id服务方法,抛异常
                        if (lastId != null && !serviceId.equals(lastId)) {
                            throw new IllegalArgumentException("same id for service:" + url + ", method:"
                                    + method.getName());
                        }
                    }

                }
            }
            // 更新缓存
            methods.put(url, serviceMethodCache);
        }
    }
    return serviceMethodCache;
}

服务的启动主要是指netty服务端接口监听开启,请求处理线程池初始化及启动(处理请求调用链的线程池),代码如下:
在这里插入图片描述
这里我们重点看下服务的发布方法ServicePublisher.publishService主要逻辑
在这里插入图片描述
如果服务需要注册,这里调用了publishServiceToRegistry方法去注册服务,通过在ZK上创建一个持久节点完成注册,然后通知服务变化。

服务的发现

我们来看下调用方又是如何获得服务节点信息的

服务节点信息获取是在调用方获取serviceProxy动态代理时完成的,客户端动态代理的实现我们前面一篇已经说过,这里只看发现的部分
在这里插入图片描述
这里实现了对客户端的注册和服务发现

这一步主要将调用方相关信息注册到注册中心中,大致实现如下
1.获取服务地址列表,这里主要从注册中心zk获取,具体获取地址如/DP/SERVER/com.dianping.pigeon.demo.EchoService,获取到的实际值是ip:port,如172.23.51.30:6354,如果有多个ip:port,会以逗号分割
遍历服务列表,从注册红心获取每个服务地址的权重,如通过/DP/WEIGHT/172.23.51.30:6354获取,获取到ip,port,weight后,会封装成一个HostInfo
2.通过RegistryEventListener发布providerAdded和serverInfoChanged事件,对于providerAdded事件:
会导致ClientManager.InnerServiceProviderChangeListener触发进行客户端注册,首先会初始化一个ConnectInfo,包含serviceName, host, port, weight等信息,然后会进行以下两步:
进一步会触发相关的ClusterListener调用addConnect,这里主要包含:
触发NettyClientFactory#createClient调用,进一步创建NioClientSocketChannelFactory,建立和服务端的netty连接,并建立心跳检测
触发WeightFactorMaintainer#providerAdded,进一步调用addWeight方法,更新weights和weightFactors成员。
在RegistryManager中添加服务引用地址,更新referencedServiceAddresses和referencedAddresses
触发WeightFactorMaintainer#providerAdded,进一步调用addWeight方法,更新weights和weightFactors成员。这里和前面一步有重复

public Set<HostInfo> registerClients(InvokerConfig invokerConfig) {
		String remoteAppkey = invokerConfig.getRemoteAppKey();
		String serviceName = invokerConfig.getUrl();
		String group = RegistryManager.getInstance().getGroup(serviceName);
		String vip = invokerConfig.getVip();

		logger.info("start to register clients for service '" + serviceName + "#" + group + "'");
		String localHost = null;
		if (vip != null && vip.startsWith("console:")) {
			localHost = configManager.getLocalIp() + vip.substring(vip.indexOf(":"));
		}
		// 从注册中心获取服务地址
		String serviceAddress = getServiceAddress(invokerConfig);
		String[] addressArray = serviceAddress.split(",");
		Set<HostInfo> addresses = Collections.newSetFromMap(new ConcurrentHashMap<HostInfo, Boolean>());
		for (int i = 0; i < addressArray.length; i++) {
			if (StringUtils.isNotBlank(addressArray[i])) {
				// addressList.add(addressArray[i]);
				String address = addressArray[i];
				int idx = address.lastIndexOf(":");
				if (idx != -1) {
					String host = null;
					int port = -1;
					try {
						host = address.substring(0, idx);
						port = Integer.parseInt(address.substring(idx + 1));
					} catch (RuntimeException e) {
						logger.warn("invalid address:" + address + " for service:" + serviceName);
					}
					if (host != null && port > 0) {
						if (localHost != null && !localHost.equals(host + ":" + port)) {
							continue;
						}
						try {
						 	// 从注册中心获取相应服务权重
							int weight = RegistryManager.getInstance().getServiceWeight(address, serviceName, false);
							addresses.add(new HostInfo(host, port, weight));
						} catch (Throwable e) {
							logger.error("error while registering service invoker:" + serviceName + ", address:"
									+ address + ", env:" + configManager.getEnv(), e);
							throw new ServiceUnavailableException("error while registering service invoker:"
									+ serviceName + ", address:" + address + ", env:" + configManager.getEnv(), e);
						}
					}
				} else {
					logger.warn("invalid address:" + address + " for service:" + serviceName);
				}
			}
		}
		final String url = serviceName;
		long start = System.nanoTime();
		if (enableRegisterConcurrently) {
			final CountDownLatch latch = new CountDownLatch(addresses.size());
			// 并发发布providerAdded和serverInfoChanged时间给RegistryEventListener
			for (final HostInfo hostInfo : addresses) {
				Runnable r = new Runnable() {

					@Override
					public void run() {
						try {
							RegistryEventListener.providerAdded(url, hostInfo.getHost(), hostInfo.getPort(),
									hostInfo.getWeight());
							RegistryEventListener.serverInfoChanged(url, hostInfo.getConnect());
						} catch (Throwable t) {
							logger.error("failed to add provider client:" + hostInfo, t);
						} finally {
							latch.countDown();
						}
					}

				};
				registerThreadPool.submit(r);
			}
			try {
				latch.await();
			} catch (InterruptedException e) {
				logger.info("", e);
			}
		} else {
		 	//顺序执行
			for (final HostInfo hostInfo : addresses) {
				RegistryEventListener.providerAdded(url, hostInfo.getHost(), hostInfo.getPort(), hostInfo.getWeight());
				RegistryEventListener.serverInfoChanged(url, hostInfo.getConnect());
			}
		}
		long end = System.nanoTime();
		logger.info("end to register clients for service '" + serviceName + "#" + group + "', cost:"
				+ ((end - start) / 1000000));
		return addresses;
	}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值