RPC之美团pigeon源码分析(一)服务方初始化和服务注册

工作以来在微服务实践中接触到了很多的RPC实现方式,一直没能做一个系统的分析和总结。本系列将以美团点评开源的pigeon框架为例,从服务方初始化处理和服务注册、端口绑定和消息处理、调用方服务监听和服务调用、熔断和降级多个方面来梳理RPC的来龙去脉。
pigeon版本:2.9.8
github地址:https://github.com/dianping/pigeon/tree/2.9.8
demo服务端:https://github.com/love4coding/rpcserver
demo客户端:https://github.com/love4coding/rpcclient

首先通过一张图来了解下调用方、服务方、注册中心三者之间的关系:


image.png

pigeon支持springXml服务配置和Annotation配置,demo中用的是spingXml配置。如下:

    <bean class="com.dianping.pigeon.remoting.provider.config.spring.ServiceBean" init-method="init">
        <property name="services">
            <map>
                <entry key="http://service.dianping.com/rpcserver/commonService_1.0.0" value-ref="commonService" />
            </map>
        </property>
    </bean>

services属性下的key是服务全局唯一的标识url(如果一个远程服务未特别设置,url默认是服务接口类名),value是引用的服务bean。
进入到ServiceBean.init():


image.png

ServerConfig:为pigeon服务配置,各个属性取默认值,可配置;其中protocol默认值为"default";
ProviderConfig:对应上文配置文件services标签里的每一个实例bean;

进入ServiceFactory,循环处理ProviderConfig

    /**
     * add the services to pigeon and publish these services to registry
     */
    public static void addServices(List<ProviderConfig<?>> providerConfigList) throws RpcException {
        if (providerConfigList != null && !providerConfigList.isEmpty()) {
                        for (ProviderConfig<?> providerConfig : providerConfigList) {
                addService(providerConfig);
            }
        }
    }
    public static <T> void addService(ProviderConfig<T> providerConfig) throws RpcException {
        publishPolicy.doAddService(providerConfig);
    }

具体实现在AbstractPublishPolicy.doAddService,添加服务实例并注册

    @Override
    public void doAddService(ProviderConfig providerConfig) {
        try {
            //对配置的url做合法性检查,如果为空,则取服务接口类名
            //url = providerConfig.getServiceInterface().getCanonicalName();
            checkServiceName(providerConfig);
            //添加服务配置,将服务方的service bean以url为key缓存ConcurrentHashMap<String, ProviderConfig<?>>中
            //将service bean的有效方法缓存到ConcurrentHashMap中
            //详细实现见代码片段1》
            ServicePublisher.addService(providerConfig);
            //ProviderBootStrap.init()启动http协议JettyHttpServer, 此处启动default协议NettyServer
            //详细逻辑见下篇端口绑定和消息处理
            ServerConfig serverConfig = ProviderBootStrap.startup(providerConfig);
            providerConfig.setServerConfig(serverConfig);
            //服务注册,详细实现见代码片段2》
            ServicePublisher.publishService(providerConfig, false);
        } catch (Throwable t) {
            throw new RpcException("error while adding service:" + providerConfig, t);
        }
    }

1》:在ServicePublisher.addService中将服务方的service bean以url为key添加到serviceCache map中,如果有版本号,则进行版本号处理

        private static ConcurrentHashMap<String, ProviderConfig<?>> serviceCache = new ConcurrentHashMap<String, ProviderConfig<?>>();
    public static <T> void addService(ProviderConfig<T> providerConfig) throws Exception {
        String version = providerConfig.getVersion();
        String url = providerConfig.getUrl();
        if (StringUtils.isBlank(version)) {// default version
            serviceCache.put(url, providerConfig);
        } else {
                        //版本号逻辑处理
            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) {
                        // 以最新版本作为服务方默认处理bean
                        serviceCache.put(url, providerConfig);
                    }
                }
            } else {
                serviceCache.put(urlWithVersion, providerConfig);
                // use this service as the default provider
                serviceCache.put(url, providerConfig);
            }
        }
        T service = providerConfig.getService();
        if (service instanceof InitializingService) {
            ((InitializingService) service).initialize();
        }
                //遍历服务方service bean的有效方法,以url为key添加到methods map缓存中
        ServiceMethodFactory.init(url);
    }

简单看下ServiceMethodFactory.init的实现

        private static Map<String, ServiceMethodCache> methods = new ConcurrentHashMap<String, ServiceMethodCache>();
    public static void init(String url) {
        getServiceMethodCache(url);
    }

    public static ServiceMethodCache getServiceMethodCache(String url) {
        ServiceMethodCache serviceMethodCache = methods.get(url);
        if (serviceMethodCache == null) {
            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);
             //遍历service bean的所有方法
                for (Method method : methodArray) {
             //ingoreMethods为java对象继承自Object.class和Class.class的方法,需要忽略掉
                    if (!ingoreMethods.contains(method.getName())) {
                        method.setAccessible(true);
                        serviceMethodCache.addMethod(method.getName(), new ServiceMethod(service, method));
               //isCompact默认为true,按url和方法名称生成id隐射到map中
                        if (isCompact) {
                            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);
                            if (lastId != null && !serviceId.equals(lastId)) {
                                throw new IllegalArgumentException("same id for service:" + url + ", method:"
                                        + method.getName());
                            }
                        }

                    }
                }
                methods.put(url, serviceMethodCache);
            }
        }
        return serviceMethodCache;
    }

以上代码主要是对每一个service bean和service bean中的方法进行map映射,便于pigeon服务接收调用方的消息后取对应的service bean进行消息处理。

2》:在ServicePublisher.publishService中将服务信息注册到注册中心zookeeper上,便于调用方监听和发起请求。

    public static <T> void publishService(ProviderConfig<T> providerConfig, boolean forcePublish)
            throws RegistryException {
        String url = providerConfig.getUrl();
                。。。
        List<Server> servers = ProviderBootStrap.getServers(providerConfig);
        int registerCount = 0;
        for (Server server : servers) {
                        //调用ZK客户端CuratorClient注册服务信息,并设置权重为0
            publishServiceToRegistry(url, server.getRegistryUrl(url), server.getPort(),
            RegistryManager.getInstance().getGroup(url), providerConfig.isSupported());
            registerCount++;
        }
                。。。
                //设置服务权重为1,服务Online
        ServiceOnlineTask.start();  
        providerConfig.setPublished(true);
        }
        //注册IP:PORT信息到zookeeper,初始化权重
    private synchronized static <T> void publishServiceToRegistry(String url, String registryUrl, int port, String group, boolean support)  {        
        String ip = configManager.getLocalIp();
        String serverAddress = ip + ":" + port;
                //初始化为0
        int weight = Constants.WEIGHT_INITIAL;
                //注册服务和权重信息
        RegistryManager.getInstance().registerService(registryUrl, group, serverAddress, weight);
        if (weight >= 0) {
            if (!serverWeightCache.containsKey(serverAddress)) {
                                //设置服务的APP名称和版本号
                RegistryManager.getInstance().setServerApp(serverAddress, configManager.getAppName());
                RegistryManager.getInstance().setServerVersion(serverAddress, VersionUtils.VERSION);
            }
            serverWeightCache.put(serverAddress, weight);
        }
        }

publishServiceToRegistry注册后服务信息如下:


11559640-e69a2feb6698bc0a.png

初始化权重信息:


11559640-a01659373b2206ea.png

ServiceOnlineTask执行后的服务权重信息:


11559640-8667f2477b89a05d.png

关于服务启动和注册到此结束,欢迎评论区留言。

转载请备注原文链接。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容