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

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

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

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

static void module_init() {

    application_instance = -;
application_id = -; int i = ; do {
application_id = serviceRegister(SKYWALKING_G(grpc), SKYWALKING_G(app_code)); if(application_id == -) {
sleep();
} i++;
} while (application_id == - && i <= ); if (application_id == -) {
sky_close = ;
return;
} char *ipv4s = _get_current_machine_ip(); char hostname[] = {};
if (gethostname(hostname, sizeof(hostname)) < ) {
strcpy(hostname, "");
} char *l_millisecond = get_millisecond();
long millisecond = zend_atol(l_millisecond, strlen(l_millisecond));
efree(l_millisecond); i = ;
do {
application_instance = serviceInstanceRegister(SKYWALKING_G(grpc), application_id, millisecond, SKY_OS_NAME,
hostname, getpid(),
ipv4s);
if(application_instance == -) {
sleep();
}
i++;
} while (application_instance == - && i <= ); if (application_instance == -) {
sky_close = ;
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() + );
bzero(uuid, uuid_str.size() + );
strncpy(uuid, uuid_str.c_str(), uuid_str.size() + );
} 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 = ; 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 -; }

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

if (status.ok()) {
std::cout << "size:" << reply.serviceinstances_size() << std::endl;
for (int i = ; 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");
}

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

因之前服务端维护的同学走了,只好自己硬着头皮看代码,发现注册入口代码在RegisterServiceHandler::doServiceInstanceRegister中:

@Override
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);

再跟进去:

@Override 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索引serviceinstanceinventory去拿。

为了证实上述逻辑无误,从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() + );
bzero(uuid, uuid_str.size() + );
strncpy(uuid, uuid_str.c_str(), uuid_str.size() + );
}

马上注册成功了:

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解决问题。

Skywalking Php二:代码分析

故障演练利器之ChaosBlade介绍

全球智能DNS解析实践

一次线上Mysql死锁分析

Skywalking Php注册不上问题排查的更多相关文章

  1. sql2000不能远程注册服务器上sql2000的解决方法

    1. 开始——cmd——telnet Ip 1433  看1433端口是否打开 2.在服务器上查询分析器中输入select @@version查看sql2000的版本,版本号在8.0.2039以下的都 ...

  2. BTrace:线上问题排查工具

    BTrace简介 GitHub地址:BTrace 下载地址:v1.3.11.3 官方使用教程:Btrace使用教程 使用场景 BTrace 是一个事后工具,所谓事后工具就是在服务已经上线了,但是发现存 ...

  3. 记一次线上bug排查-quartz线程调度相关

    记一次线上bug排查,与各位共同探讨. 概述:使用quartz做的定时任务,正式生产环境有个任务延迟了1小时之久才触发.在这一小时里各种排查找不出问题,直到延迟时间结束了,该任务才珊珊触发.原因主要就 ...

  4. Java线上问题排查思路及Linux常用问题分析命令学习

    前言 之前线上有过一两次OOM的问题,但是每次定位问题都有点手足无措的感觉,刚好利用星期天,以测试环境为模版来学习一下Linux常用的几个排查问题的命令. 也可以帮助自己在以后的工作中快速的排查线上问 ...

  5. 【转】又一次线上 OOM 排查经过

    又一次线上OOM排查经过 最近线上一个服务又出现了频繁Full GC的情况,导致提供的业务经常超时.问题出现非常不稳定,经过两周的时候,终于又捕捉到了一次Full GC,于是联系运维做Heap Dum ...

  6. Netty源码分析--Channel注册(上)(五)

    其实在将这一节之前,我们来分析一个东西,方便下面的工作好开展. 打开启动类,最开始的时候创建了一个NioEventLoopGroup 事件循环组,我们来跟一下这个. 这里bossGroup, 我传入了 ...

  7. 线上问题排查神器 Arthas

    线上问题排查神器 Arthas 之前介绍过 BTrace,线上问题排查神器 BTrace 的使用,也说它是线上问题排查神器.都是神器,但今天这个也很厉害,是不是更厉害不好说,但是使用起来非常简单.如果 ...

  8. JVM 线上故障排查基本操作--CPU飙高

    JVM 线上故障排查基本操作 CPU 飚高 线上 CPU 飚高问题大家应该都遇到过,那么如何定位问题呢? 思路:首先找到 CPU 飚高的那个 Java 进程,因为你的服务器会有多个 JVM 进程.然后 ...

  9. java:线上问题排查常用手段(转)

    出处:java:线上问题排查常用手段 一.jmap找出占用内存较大的实例 先给个示例代码: import java.util.ArrayList; import java.util.List; imp ...

随机推荐

  1. SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  2. three.js 制作逻辑转体游戏(下)

    上一篇已经对绕非定轴转动有所了解,这篇郭先生继续说一说逻辑转体游戏的制作,这部分我们同样会遇到一些小问题,首先是根据数据渲染陷阱和目标区域,然后是对可以转动的判定,最后是获胜的判定. 1. 根据数据渲 ...

  3. 个人项目wc

    github地址:https://github.com/YTFFBX/wc 1.题目描述 Word Count1. 实现一个简单而完整的软件工具(源程序特征统计程序).2. 进行单元测试.回归测试.效 ...

  4. 欢迎来到 C# 9.0(Welcome to C# 9.0)【纯手工翻译】

    翻译自 Mads Torgersen 2020年5月20日的博文<Welcome to C# 9.0>,Mads Torgersen 是微软 C# 语言的首席设计师,也是微软 .NET 团 ...

  5. go语言之反射

    ---恢复内容开始--- 一 :并发基础 1 并发和并行 并发和并行是两个不同的概念: 并行意味着程序在任意时刻都是同时运行的: 并发意味着程序在单位时间内是同时运行的 详解: 并行就是在任一粒度的时 ...

  6. fragment没有getWindowManager 关于fragment下的报错解决方法

    其实很简单:只需要在getWindowManager().getDefaultDisplay().getMetrics(metric) 前面加上getactivity()即可.

  7. stf-多设备管理平台搭建

    项目地址: https://github.com/openstf/stf 安装.使用命令 # 安装stfbrew install rethinkdb graphicsmagick zeromq pro ...

  8. Hihocoder 1116 计算

    这题最开始的时候看到线段树吧,没找到好的做法 想了下既然是乘积和 (-) (--) (---) 在脑子里就是这种线条位于各个位置,然后各种长度代表连续的乘积个数 然后把所有情况累加起来,但是并不好算 ...

  9. 前端通过jqplot绘制折线图

    首先需要下载jqplot需要的js与css文件,我已近打包好了,需要的可以下载 接下来导入其中关键的js与css如下, <link href="css/jquery.jqplot.mi ...

  10. Python pymsql模块

    pymsql pymysql这款第三方库可以帮助我们利用python语言与mysql进行链接 基本使用 首先要下载pymysql pip install pymsql 以下是pymysql的基本使用 ...