百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Skywalking Php注册不上问题排查

toyiye 2024-04-07 14:12 24 浏览 0 评论

Skywalking是一款分布式追踪应用,具体介绍可以参考 skywalking。

最近公司的一个Php应用在Skywalking后台查不到数据了:

登录到某台服务器上发现注册不上,启动时就报错了:

先来整理下Skywalking php的整个流程,php扩展在系统启动时注册应用和实例,然后在每次请求拦截相关调用,将相关调用情况保存下来;注册相关代码在skywalking.c的module_init中:

static void module_init() {

    application_instance = -100000;
    application_id = -100000;

    int i = 0;

    do {
        application_id = serviceRegister(SKYWALKING_G(grpc), SKYWALKING_G(app_code));

        if(application_id == -100000) {
            sleep(1);
        }

        i++;
    } while (application_id == -100000 && i <= 1);

    if (application_id == -100000) {
        sky_close = 1;
        return;
    }

    char *ipv4s = _get_current_machine_ip();

    char hostname[100] = {0};
    if (gethostname(hostname, sizeof(hostname)) < 0) {
        strcpy(hostname, "");
    }

    char *l_millisecond = get_millisecond();
    long millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
    efree(l_millisecond);

    i = 0;
    do {
        application_instance = serviceInstanceRegister(SKYWALKING_G(grpc), application_id, millisecond, SKY_OS_NAME,
                                                       hostname, getpid(),
                                                       ipv4s);
        if(application_instance == -100000) {
            sleep(2);
        }
        i++;
    } while (application_instance == -100000 && i <= 3);


    if (application_instance == -100000) {
        sky_close = 1;
    php_error(E_WARNING, "skywalking: register service error");
        return;
    }
  
    php_error(E_WARNING, "skywalking: register service success");
}

可以看到,注册应用是调用serviceRegister函数注册,然后调用serviceInstanceRegister来注册实例的,后者会调用GreeterClient::serviceInstanceRegister以下函数完成注册:


int serviceInstanceRegister(int applicationid, long registertime, char *osname, char *hostname, int processno,
                                char *ipv4s) {
        ServiceInstances request;
        ServiceInstance *s = request.add_instances();

        if (uuid == NULL) {
            std::string uuid_str = boost::uuids::to_string(boost_uuid);
            uuid = (char *) malloc(uuid_str.size() + 1);
            bzero(uuid, uuid_str.size() + 1);
            strncpy(uuid, uuid_str.c_str(), uuid_str.size() + 1);
        }

        s->set_serviceid(applicationid);
        s->set_instanceuuid(std::string(uuid));
        s->set_time(registertime);

        KeyStringValuePair *os = s->add_properties();
        KeyStringValuePair *host = s->add_properties();
        KeyStringValuePair *process = s->add_properties();
        KeyStringValuePair *ipv4 = s->add_properties();
        KeyStringValuePair *language = s->add_properties();

        os->set_key("os_name");
        os->set_value(osname);
        host->set_key("host_name");
        host->set_value(hostname);
        process->set_key("process_no");
        process->set_value(std::to_string(processno));
        ipv4->set_key("ipv4");
        ipv4->set_value(ipv4s);
        language->set_key("language");
        language->set_value("php");

        ServiceInstanceRegisterMapping reply;

        ClientContext context;

        Status status = stub_->doServiceInstanceRegister(&context, request, &reply);

        if (status.ok()) {
            for (int i = 0; i < reply.serviceinstances_size(); i++) {
                const KeyIntValuePair &kv = reply.serviceinstances(i);
//                std::cout << "Register Instance:"<< std::endl;
//                std::cout << kv.key() << ": " << kv.value() << std::endl;

                if (kv.key() == uuid) {
                    return kv.value();
                }
            }
        }

        return -100000;

    }

通过gdb的断点,发现注册应用是成功的,注册实例失败了,然后在GreeterClient::serviceInstanceRegister加上相应的日志:

if (status.ok()) {
            std::cout << "size:" << reply.serviceinstances_size() << std::endl;
            for (int i = 0; i < reply.serviceinstances_size(); i++) {
                const KeyIntValuePair &kv = reply.serviceinstances(i);
                std::cout << "Register Instance:"<< std::endl;
                std::cout << kv.key() << ": " << kv.value() << std::endl;

                if (kv.key() == uuid) {
                    return kv.value();
                }
            }
        }else{
                printf("instance register error");
        }

这里也把UUID打印出来了,发现响应里每次都是空的,所以导致失败。

客户端已经没有线索了,只好从服务端入手,因为服务端是Java实现的,不大方便调试,因此在本地搭了个环境想调试下,哪知服务端跑起来了,Php客户端死活编译不上,因为Skywalking依赖protobuf、grpc等组件,这些组件之间有版本依赖关系的,官方文档也没有说明,一时陷入困境。


通过看代码,发现注册入口代码在RegisterServiceHandler::doServiceInstanceRegister中:

public void doServiceInstanceRegister(ServiceInstances request,
        StreamObserver<ServiceInstanceRegisterMapping> responseObserver) {

        ServiceInstanceRegisterMapping.Builder builder = ServiceInstanceRegisterMapping.newBuilder();

        request.getInstancesList().forEach(instance -> {
            ServiceInventory serviceInventory = serviceInventoryCache.get(instance.getServiceId());

            JsonObject instanceProperties = new JsonObject();
            List<String> ipv4s = new ArrayList<>();

            for (KeyStringValuePair property : instance.getPropertiesList()) {
                String key = property.getKey();
                switch (key) {
                    case HOST_NAME:
                        instanceProperties.addProperty(HOST_NAME, property.getValue());
                        break;
                    case OS_NAME:
                        instanceProperties.addProperty(OS_NAME, property.getValue());
                        break;
                    case LANGUAGE:
                        instanceProperties.addProperty(LANGUAGE, property.getValue());
                        break;
                    case "ipv4":
                        ipv4s.add(property.getValue());
                        break;
                    case PROCESS_NO:
                        instanceProperties.addProperty(PROCESS_NO, property.getValue());
                        break;
                }
            }
            instanceProperties.addProperty(IPV4S, ServiceInstanceInventory.PropertyUtil.ipv4sSerialize(ipv4s));

            String instanceName = serviceInventory.getName();
            if (instanceProperties.has(PROCESS_NO)) {
                instanceName += "-pid:" + instanceProperties.get(PROCESS_NO).getAsString();
            }
            if (instanceProperties.has(HOST_NAME)) {
                instanceName += "@" + instanceProperties.get(HOST_NAME).getAsString();
            }

            int serviceInstanceId = serviceInstanceInventoryRegister.getOrCreate(instance.getServiceId(), instanceName, instance.getInstanceUUID(), instance.getTime(), instanceProperties);

            if (serviceInstanceId != Const.NONE) {
                logger.info("register service instance id={} [UUID:{}]", serviceInstanceId, instance.getInstanceUUID());
                builder.addServiceInstances(KeyIntValuePair.newBuilder().setKey(instance.getInstanceUUID()).setValue(serviceInstanceId));
            }
        });

        responseObserver.onNext(builder.build());
        responseObserver.onCompleted();
    }

关键是这行代码来生成实例id的:

int serviceInstanceId = serviceInstanceInventoryRegister.getOrCreate(instance.getServiceId(), instanceName, instance.getInstanceUUID(), instance.getTime(), instanceProperties);

再跟进去:

public int getOrCreate(int serviceId, String serviceInstanceName, String uuid, long registerTime,
        JsonObject properties) {
        if (logger.isDebugEnabled()) {
            logger.debug("Get or create service instance by service instance name, service id: {}, service instance name: {},uuid: {}, registerTime: {}", serviceId, serviceInstanceName, uuid, registerTime);
        }

        int serviceInstanceId = getServiceInstanceInventoryCache().getServiceInstanceId(serviceId, uuid);

        if (serviceInstanceId == Const.NONE) {
            ServiceInstanceInventory serviceInstanceInventory = new ServiceInstanceInventory();
            serviceInstanceInventory.setServiceId(serviceId);
            serviceInstanceInventory.setName(serviceInstanceName);
            serviceInstanceInventory.setInstanceUUID(uuid);
            serviceInstanceInventory.setIsAddress(BooleanUtils.FALSE);
            serviceInstanceInventory.setAddressId(Const.NONE);

            serviceInstanceInventory.setRegisterTime(registerTime);
            serviceInstanceInventory.setHeartbeatTime(registerTime);

            serviceInstanceInventory.setProperties(properties);

            InventoryStreamProcessor.getInstance().in(serviceInstanceInventory);
        }
        return serviceInstanceId;
    }

这里的逻辑就比较清晰了,先从缓存中拿实例ID:

getServiceInstanceInventoryCache().getServiceInstanceId(serviceId, uuid);

拿不到则加入后台任务处理生成ID。


再跟进getServiceInstanceId方法,

 if (Objects.isNull(serviceInstanceId) || serviceInstanceId == Const.NONE) {
            serviceInstanceId = getCacheDAO().getServiceInstanceId(serviceId, uuid);
            if (serviceId != Const.NONE) {
                serviceInstanceNameCache.put(ServiceInstanceInventory.buildId(serviceId, uuid), serviceInstanceId);
            }
        }

从缓存中拿不到则从DAO中拿,

GetResponse response = getClient().get(ServiceInstanceInventory.INDEX_NAME, id);
            if (response.isExists()) {
                return (int)response.getSource().getOrDefault(RegisterSource.SEQUENCE, 0);
            } else {
                return Const.NONE;
            }

后者从ES索引service_instance_inventory去拿。

为了证实上述逻辑无误,从ES中读取数据试下,果然实例ID都注册在ES里面:

再从客户端证实下,既然实例ID是写入ES的,那么用以前的ID肯定是能注册成功的,因此修改客户端代码,将UUID写死注册试下:

int serviceInstanceRegister(int applicationid, long registertime, char *osname, char *hostname, int processno,
                                char *ipv4s) {
        ServiceInstances request;
        ServiceInstance *s = request.add_instances();
        uuid= "7e22c317-e2e2-4f81-a53d-fe011013e0a3";
        if (uuid == NULL) {
            std::string uuid_str = boost::uuids::to_string(boost_uuid);
            uuid = (char *) malloc(uuid_str.size() + 1);
            bzero(uuid, uuid_str.size() + 1);
            strncpy(uuid, uuid_str.c_str(), uuid_str.size() + 1);
        }

马上注册成功了:

7e22c317-e2e2-4f81-a53d-fe011013e0a3
size:1
Register Instance:
7e22c317-e2e2-4f81-a53d-fe011013e0a3: 3386041
PHP Warning:  skywalking: register service success in Unknown on line 0
PHP Warning:  skywalking: hook redis handler success in Unknown on line 0
PHP Warning:  skywalking: hook session handler success in Unknown on line 0


再回到这个问题,原因已经知道了,如何解决呢,有两个办法:

1、加大注册时等待时间,如等待到100秒;

2、记录最近一次注册成功的UUID并且持久化,下次启动时直接用上次的;


因为2涉及到改代码,因此先用方案1解决问题。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码