`
54wangyong
  • 浏览: 13989 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

从源码分析dubbox服务消费端有时找不到提供者原因

阅读更多

问题描述:Dubbox2.8.4版本,用redis作为注册中心时,消费端有时会报提供者不存在的问题。

 

 在排查中,看监控中心有如下日志,通过监控中心的日志可以看出,它会删除过期的key,是不是因为删除过期的key而导致的了?【日志中有:Delete expired key:

 

[18/04/16 05:47:43:043 CST] DubboRegistryExpireTimer-thread-1  WARN redis.RedisRegistry:  [DUBBO] Delete expired key: /dubbo/xxx.RestPayCallbackChannelService/providers -> value: rest://192.168.1.71:8888/xxx.RestPayCallbackChannelService?accepts=500&anyhost=true&application=jumore-pay-provider&default.service.filter=security&dubbo=2.8.4&generic=false&interface=xxx.RestPayCallbackChannelService&methods=payCallback&organization=jumore&owner=programmer&pid=54132&revision=api&serialization=kryo&server=jetty&side=provider&threads=500&timestamp=1460958038148, expire: Mon Apr 18 17:47:10 CST 2016, now: Mon Apr 18 17:47:43 CST 2016, dubbo version: 2.0.0, current host: 192.168.23.225
[18/04/16 05:47:43:043 CST] DubboRedisSubscribe  INFO redis.RedisRegistry:  [DUBBO] redis event: /dubbo/xxx.RestPayCallbackChannelService/providers = unregister, dubbo version: 2.0.0, current host: 192.168.23.225

  为了考虑性能问题,我需要在提供者方进行timeout处理。所以在使用注解时,我对服务提供方都加入了timeout处理。这个也是dubbo推荐的用法。Provider上尽量多配置Consumer端的属性,让Provider实现者一开始就思考Provider服务特点、服务质量的问题。

 

  类似于这样,我在发布服务时

@Service(protocol = {"dubbo"}, version = 0.0.1, timeout = 3000)
public class PayQueryChannelServiceImpl implements XXXService

     其它的也没有什么特殊处理

 

  我们首先来说明下,我们在用redis作为注册中心,redis的注册中心类 是:com.alibaba.dubbo.registry.redis.RedisRegistry.  RedisRegistry注册中心在初始化时的一个处理也就是RedisRegistryUrl url)这个构建方法的最后一点:

 

this.expirePeriod = url.getParameter(Constants.SESSION_TIMEOUT_KEY,
            Constants.DEFAULT_SESSION_TIMEOUT);
        this.expireFuture = expireExecutor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                try {
                    deferExpired(); // 延长过期时间
                } catch (Throwable t) { // 防御性容错
                    logger.error(
                        "Unexpected exception occur at defer expire time, cause: " + t.getMessage(),
                        t);
                }
            }
        }, expirePeriod / 2, expirePeriod / 2, TimeUnit.MILLISECONDS);

 

  通过上面的代码我们可以看出,dubbo服务的每个url在注册到注册中心时,都会开启一个定时任务进行一些操作。定时任务的间隔时间,是通过dubbo服务的url的中的参数session值来规定的,一般这个session值都是在dubbo:register中可以自己定义,如果没有定义,通过上面的程序可以看出,它给的有一个默认的session超时时间为60000,这样我的程序因为没有定义这个session,所以上面的expirePeriod的值为60000

  而定时任务也就是延迟30s后,每隔30s会执行一次。而定时任务中的线程运行时,执行的是deferExpired()这个方法,而这个方法,就是对redis的过期时间进行延长处理。我们可以参看具体的代码实现:

 

 

private void deferExpired() {
        for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
            JedisPool jedisPool = entry.getValue();
            try {
                Jedis jedis = jedisPool.getResource();
                try {
                    for (URL url : new HashSet<URL>(getRegistered())) {
                        if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
                            String key = toCategoryPath(url);
                            if (jedis.hset(key, url.toFullString(),
                                String.valueOf(System.currentTimeMillis() + expirePeriod)) == 1) {
                                jedis.publish(key, Constants.REGISTER);
                            }
                        }
                    }
                    if (admin) {
                        clean(jedis);
                    }
                    if (!replicate) {
                        break;//  如果服务器端已同步数据,只需写入单台机器
                    }
                } finally {
                    jedisPool.returnResource(jedis);
                }
            } catch (Throwable t) {
                logger.warn("Failed to write provider heartbeat to redis registry. registry: "
                            + entry.getKey() + ", cause: " + t.getMessage(), t);
            }
        }
    }

 

  这个方法中获取所有的redis连接池,因为redis有可能部署多台。Dubbo服务在发布时,可以进行集群的。这里我就不针对这个功能具体介绍了,可以参看dubbo的文档。

然后处理当前jvm中的RedisRegistry类中注册的dubbo服务的url,然后执行

url.getParameter(Constants.DYNAMIC_KEY, true)

 

  因为我在发布服务时,没有设置dynamic属性,所以这会默认是true。然后执行redishset命令,来将已经注册的服务的值替换成

String.valueOf(System.currentTimeMillis() + expirePeriod)  

 

   也就是当前时间+过期时间。如果执行hset命令时,插入的这个hash结构是个新值,就会发布redisregister服务。以便其它的订阅者能够知道注册中心有新的服务发布;

其实针对我的问题,上面的代码都不是关键,是关键的是

if (admin) {
          clean(jedis);
}

 

  这里面的代码是,当admin=true时,会进行clean。这里我们先不关心admin的值在什么时候设置成true的,后面再介绍。我们首先看下clean是怎么处理业务的。

 

 

// 监控中心负责删除过期脏数据
    private void clean(Jedis jedis) {
        Set<String> keys = jedis.keys(root + Constants.ANY_VALUE);
        if (keys != null && keys.size() > 0) {
            for (String key : keys) {
                Map<String, String> values = jedis.hgetAll(key);
                if (values != null && values.size() > 0) {
                    boolean delete = false;
                    long now = System.currentTimeMillis();
                    for (Map.Entry<String, String> entry : values.entrySet()) {
                        URL url = URL.valueOf(entry.getKey());
                        if (url.getParameter(Constants.DYNAMIC_KEY, true)) {
                            long expire = Long.parseLong(entry.getValue());
                            if (expire < now) {
                                jedis.hdel(key, entry.getKey());
                                delete = true;
                                if (logger.isWarnEnabled()) {
                                    logger.warn("Delete expired key: " + key + " -> value: "
                                                + entry.getKey() + ", expire: " + new Date(expire)
                                                + ", now: " + new Date(now));
                                }
                            }
                        }
                    }
                    if (delete) {
                        jedis.publish(key, Constants.UNREGISTER);
                    }
                }
            }
        }
    }

 

面主要的功能就是将redis里面的匹配dubbo/*  所有的key的数据取出来。因为dubbo服务在放入到redis时,就是一个hash结构的,具体的结构说明如下:

 

使用RedisKey/Map结构存储数据。

Key为服务名和类型。

Map中的KeyURL地址。

Map中的Value为过期时间,用于判断脏数据,脏数据由监控中心删除。(注意:服务器时间必需同步,否则过期检测会不准确)

 

所以上面的程序就是取出map中的value,然后与当前的时间比较是否过期。过期了就执行redishdel删除对应的数据。(就是这里会造成有时dubbo的消费端会找不到服务的问题)

 

疑问1:从RedisRegistry中的代码看,它在执行clean之前,会将所有的redis里面的服务的过期时间延长处理,而在clean方法中怎么还是会被删除了?

 

其实不然,在deferExpired()方法中获取url时,是直接调用getRegistered()方法。这也就是说明,当我们所有的服务都是用同一个RedisRegistry类时,getRegistered()方法获取的才是所有的dubbo服务。而实际上是,dubbo在发布服务时,看下debbug类的调用顺序:



 

 

也就是通过RedisRegistryFactory.getRegistryURL)来进行注册的。而这个对象里面的方法是:

 

public Registry getRegistry(URL url) {
        return new RedisRegistry(url);
}

 

每次都会新new一个对象出来。这样就造成了同一个RedisRegistry类中在调用getRegistered()方法时,不会获取其它的dubbo服务,即使是现一个jvm中,也不会获取。

 

而在redis删除过期的key时,通过前面的clean方法的代码可以看出,它在删除key时,所有的dubbo服务都是从redis中获取到的。这样我们就知道了,并不是在同一个线程里执行的redis过期时间先延长,再进行过期时间判断从redis中删除的。

 

疑问2:那么即使,所有的redis过期时间延长和删除redis不是在同一个线程中执行的,那么它自己在发布服务时,也是会进行new RedisRegistry(url),然后自己的线程里面会对url的过期时间延长啊,还是不会删除啊?

这个可不是绝对的啊~~。因为针对多线程,而且前面我也说过了,RedisRegistry类的deferExpired()方法是在一个定时任务执行,那么你说会存在这种情况吗?

 

看看下面的日志信息:我将删除同一个redis key的信息摘录出来了:

 

 

Line 57: [18/04/16 04:30:39:039 CST] DubboRegistryExpireTimer-thread-1  WARN redis.RedisRegistry:  [DUBBO] Delete expired key: /dubbo/xxx.member.MemberBindChannelService/providers -> value: dubbo://192.168.1.71:20881/xxx.member.MemberBindChannelService?anyhost=true&application=jumore-pay-provider&default.service.filter=security&dubbo=2.8.4&generic=false&interface=xxx.member.MemberBindChannelService&methods=bindReqParam&organization=jumore&owner=programmer&pid=54132&revision=api&side=provider&timeout=3000&timestamp=1460958037541&version=0.0.1, expire: Mon Apr 18 16:30:08 CST 2016, now: Mon Apr 18 16:30:39 CST 2016, dubbo version: 2.0.0, current host: 192.168.23.225
	Line 136: [18/04/16 04:31:09:009 CST] DubboRegistryExpireTimer-thread-1  WARN redis.RedisRegistry:  [DUBBO] Delete expired key: /dubbo/xxx.member.MemberBindChannelService/providers -> value: dubbo://192.168.1.71:20881/xxx.member.MemberBindChannelService?anyhost=true&application=jumore-pay-provider&default.service.filter=security&dubbo=2.8.4&generic=false&interface=xxx.member.MemberBindChannelService&methods=bindReqParam&organization=jumore&owner=programmer&pid=54132&revision=api&side=provider&timeout=3000&timestamp=1460958037541&version=0.0.1, expire: Mon Apr 18 16:30:38 CST 2016, now: Mon Apr 18 16:31:09 CST 2016, dubbo version: 2.0.0, current host: 192.168.23.225
	Line 205: [18/04/16 04:31:39:039 CST] DubboRegistryExpireTimer-thread-1  WARN redis.RedisRegistry:  [DUBBO] Delete expired key: /dubbo/xxx.member.MemberBindChannelService/providers -> value: dubbo://192.168.1.71:20881/xxx.member.MemberBindChannelService?anyhost=true&application=jumore-pay-provider&default.service.filter=security&dubbo=2.8.4&generic=false&interface=xxx.member.MemberBindChannelService&methods=bindReqParam&organization=jumore&owner=programmer&pid=54132&revision=api&side=provider&timeout=3000&timestamp=1460958037541&version=0.0.1, expire: Mon Apr 18 16:31:08 CST 2016, now: Mon Apr 18 16:31:39 CST 2016, dubbo version: 2.0.0, current host: 192.168.23.225
	Line 273: [18/04/16 04:32:09:009 CST] DubboRegistryExpireTimer-thread-1  WARN redis.RedisRegistry:  [DUBBO] Delete expired key: /dubbo/xxx.member.MemberBindChannelService/providers -> value: dubbo://192.168.1.71:20881/xxx.member.MemberBindChannelService?anyhost=true&application=jumore-pay-provider&default.service.filter=security&dubbo=2.8.4&generic=false&interface=xxx.member.MemberBindChannelService&methods=bindReqParam&organization=jumore&owner=programmer&pid=54132&revision=api&side=provider&timeout=3000&timestamp=1460958037541&version=0.0.1, expire: Mon Apr 18 16:31:38 CST 2016, now: Mon Apr 18 16:32:09 CST 2016, dubbo version: 2.0.0, current host: 192.168.23.225

 

它们中的这些日志信息:

expire: Mon Apr 18 16:30:08 CST 2016, now: Mon Apr 18 16:30:39 CST 2016,
expire: Mon Apr 18 16:30:38 CST 2016, now: Mon Apr 18 16:31:09 CST 2016,
expire: Mon Apr 18 16:31:08 CST 2016, now: Mon Apr 18 16:31:39 CST 2016,
expire: Mon Apr 18 16:31:38 CST 2016, now: Mon Apr 18 16:32:09 CST 2016,

 看看上面的日志已经说明:MemberBindChannelServiceurl的定时任务在Mon Apr 18 16:30:39 CST 2016时被删除

  这个如果按照前面分析的逻辑key应该删除不了啊?如果过期时间是Mon Apr 18 16:30:08 CST 2016,那它放入这个key的时间应该是Mon Apr 18 16:29:08 CST 2016,而在删除key的时间是Mon Apr 18 16:30:39 CST 2016,在这个期间,放入key的那个线程在Mon Apr 18 16:29:38 CST 2016时应该会将key加上60s,将失效时间变成Mon Apr 18 16:30:38 CST 2016这个时间才对啊?

   为什么了?经过检查,最后发现,是因为我的监控中心服务器与提供服务器的时间不一致。

也就是监控中心服务的时间,比服务提供者服务器的时间快上近一分钟导致的。

 

 

问题3:而为什么启动监控中心时,RedisRegistry对象中的admin属性就会变成true

这是因为,在启动监控中心时,我们一般都会如下配置:

 

 

通过注册中心发现监控中心服务:
<dubbo:monitor protocol="registry" />
或:
dubbo.properties
dubbo.monitor.protocol=registry

 这样的话,监控中心在启动时就会调用registry名称对应的容器。

 

 



 

 

而这个的具体启动的类我们可以通过监控中心的这个容器配置查看,它里面的内容是:

registry=com.alibaba.dubbo.monitor.simple.RegistryContainer

 

而这个类在启动时,注册的服务url



 

 

协议是以admin 开始的,注册的interface*,详细的注册的url是:

 

admin://192.168.23.225?category=providers,consumers&check=false&classifier=*&group=*&interface=*&version=*

 

 



 

 

 

而在调用RedisRegistry doSubscribe时,会根据interface*来将admin的值改在true

 

 下面一篇准备分析服务端设置超时后,客户端不起作用,依然是dubbo默认的超时时间1s

  • 大小: 65.8 KB
  • 大小: 6.5 KB
  • 大小: 58.3 KB
  • 大小: 45.5 KB
分享到:
评论

相关推荐

    dubbox框架源码和使用实例

    4. **创建服务消费者**:在消费者端,使用 @Reference 注解引用服务,指定服务接口、版本和调用方式。 5. **启动服务**:启动服务提供者和消费者,服务提供者会自动将服务注册到注册中心,消费者通过注册中心获取...

    Dubbox源码及资源

    2. **服务消费者(Consumer)**:服务消费者是调用服务的实体,它从注册中心获取服务提供者的地址,并发起远程调用。Dubbox允许消费者动态地发现服务并进行负载均衡。 3. **服务注册与发现(Registry)**:服务注册...

    dubbox 源码jar包约束文件管理中心

    4. **Dubbo-admin**:服务治理的重要工具,提供了可视化的界面,方便用户查看服务状态、管理服务提供者和消费者、配置服务参数等。 5. **Dubbo-monitor**:监控服务性能,收集调用日志和统计信息,为服务的健康检查...

    dubbox服务例子

    本示例包含服务提供者(Service Provider)和服务消费者(Service Consumer)的完整代码,旨在帮助开发者更好地理解和实践 Dubbox 的用法。 首先,我们来看服务提供者(UProvider1 和 UProvider2)。服务提供者是...

    已经打包好的dubbox示例

    【标题】"已经打包好的dubbox示例"所涉及的知识点主要集中在使用dubbox这一框架构建服务提供者(Provider)和服务消费者(Consumer)的实践中。dubbox是基于Dubbo的扩展版本,它提供了更多的特性,如RESTful支持、...

    Dubbox的详细配置和案例

    3. **配置服务消费者**:在服务消费者的配置文件中添加服务消费者配置,引用服务提供者的服务。 4. **运行和测试**:启动服务消费者,通过调用接口验证服务是否正常工作。 5. **注册到注册中心**:将服务提供者...

    dubbox2.8.4

    - **配置服务消费者**:在服务消费者端引用服务提供者的接口,并配置相应的消费参数。 - **运行与测试**:通过Maven命令进行编译、打包、部署和测试,确保服务正常运行。 4. **实战演练** - **创建服务提供者...

    dubbox 源码

    源码分析可以从 `com.alibaba.dubbo.config.ApplicationConfig` 和 `com.alibaba.dubbo.config.ServiceConfig` 开始,理解如何配置服务提供者,并通过 `com.alibaba.dubbo.rpc.service.GenericService` 实现通用服务...

    dubbox-dubbox-2.8.4 源码

    源码中,我们可以看到`ServiceRegistry`接口及其实现类,它们负责服务提供者和服务消费者的注册与订阅操作。同时,`RegistryFactory`是创建注册中心实例的关键,它根据配置动态选择合适的注册中心实现。 二、RPC...

    dubbox生产者和消费者示例代码(含依赖包).zip

    2. **服务消费者代码(Consumer)**:消费者通过 `@Reference` 注解注入服务,实现对服务提供者的调用。同样,也需要配置 Spring 配置文件,设置服务消费者的引用参数。 3. **Spring 配置文件**:XML 文件,用于...

    dubbox部署说明

    Dubbox提供了一个服务治理平台,它包括服务提供者(Provider)、服务消费者(Consumer)、注册中心(Registry)等关键部分。Zookeeper作为一个流行的分布式协调服务,常被用作Dubbox的服务注册与发现中心。 1. **...

    dubbox2.8.4.rar

    由于在阿里云库中找不到这个特定版本的 rar 包,这可能是由于某些原因,如版本更新或者资源迁移,导致的。不过,你已经找到了这个稀缺的资源,这对于需要这个版本的开发者来说非常宝贵。 Dubbox 主要包含以下几个...

    dubbox-dubbox-2.8.4a源码

    例如,core模块提供了基础的RPC功能,provider模块负责服务提供者的实现,consumer模块关注服务消费者的逻辑,registry模块处理服务注册与发现,而container模块则用于容器化部署。 3. **服务注册与发现** dubbox-...

    dubbox编译、环境搭建、服务提供和服务消费及学习资料

    dubbox编译、环境搭建、服务提供和服务消费 1 1 Dubbo源码构建 1 1.1 资源准备 1 1.2 配置maven设置 1 1.3 在eclipse中构件 3 2 搭dubbo-admin、dubbo-monitor控制台 16 2.1 配置zookeeper 16 2.2 配置dubbo-admin和...

    Dubbox框架搭建的Demo

    Dubbox是阿里巴巴开源的RPC框架,它提供了一种简单的方式来实现服务提供者和服务消费者之间的远程方法调用。 **步骤一:项目初始化** 1. 使用Maven创建一个新的Java项目,Maven是一个项目管理和综合工具,它可以...

    dubbox2.84

    4. **运行示例**:Dubbox通常会提供一些示例项目,你可以通过运行这些示例来测试你的环境是否配置正确,例如启动服务提供者和服务消费者。 5. **阅读文档**:压缩包中的文档会详细介绍如何配置和运行这些示例,以及...

    dubbox介绍

    5. **服务消费者**根据软负载均衡算法选择一个服务提供者进行调用,如果调用失败,则尝试另一个提供者。 6. **服务消费者和提供者**记录调用次数和时间,定期(例如每分钟)将统计数据发送至**监控中心**。 ##### ...

    分布式服务框架 dubbox

    - 服务元数据:包括服务接口、版本、分组、权重等信息,帮助服务消费者找到合适的服务实例。 2. **远程调用(RPC)**: - Dubbo 提供基于 HTTP、Hessian 和 Protocol Buffers 等多种协议的 RPC 实现,使得服务间...

    dubbox-admin2.8.4

    【标签】"dubbo" 指的是Dubbo,这是一个由阿里巴巴开源的高性能、轻量级的Java RPC框架,它致力于提供一个面向接口的、透明化的服务调用方案,使得服务消费者和服务提供者可以在不关心对方细节的情况下进行通信,极...

Global site tag (gtag.js) - Google Analytics