关于thrift的一些探索——thrift序列化技术

作者: dplord, 访问量 593





thrift的IDL,相当于一个钥匙。而thrift传输过程,相当于从两个房间之间的传输数据。

QQ20160426-3@2x

 

图-1

(因为Thrift采用了C/S模型,不支持双向通信:client只能远程调用server端的RPC接口,但client端则没有RPC供server端调用,这意味着,client端能够主动与server端通信,但server端不能主动与client端通信而只能被动地对client端的请求作出应答。所以把上图-1中的箭头,画为单向箭头更为直观)基于上图,Thrift的IDL文件的作用可以描述为,从房间A要传递一批数据给房间B,把数据装在箱子里,锁上锁,然后通过Transport Channel把带锁的箱子给房间B,而Thrift IDL就是一套锁跟钥匙。房间A跟房间B都有同样的一个thrift IDL文件,有了这个文件就可以生成序列化跟反序列化的代码,就像锁跟钥匙一样。而一旦没有了Thrift IDL文件,房间A无法将数据序列化好锁紧箱子,房间B没有钥匙打开用箱子锁好传输过来的数据。因此,IDL文件的作用就在此。

一、为什么要用Thrift序列化, 不用纯文本的协议

我能想到的用thrift提供的thrift binary protocol而不用json、xml等纯文本序列化协议的几个好处如下:

  • 序列化后的体积小, 省流量
  • 序列化、反序列化的速度更快,提高性能
  • 兼容性好,一些接口,涉及多种语言的int32、int64等等跟语言、机器、平台有关, 用纯文本可能有兼容性的问题
  • 结合thrift transport技术,可以RPC精准地传递对象,而纯文本协议大多只能传递数据,而不能完完全全传递对象

关于以上几点我个人认为的好处,下面作一下简单解答

1.1 序列化后的体积小, 省流量

给出测试的IDL Services_A.thrift内容如下,定义了一些数据格式,这个结构体数据复杂度一般,有list、也有list对象、也有一些基本的struct等等。

namespace php Services.test.A
namespace java Services.tets.A

struct student{
    1:required string studentName, #学生姓名
    2:required string sex,         #性别
    3:required i64 age,            #学生年龄
}
struct banji{
    1:required string banjiName, #班级名称
    2:required list<student> allStudents, #所有学生
}
struct school {
    1:required string schoolName,
    2:required i64    age,
    3:required list<string> zhuanye, #所有专业
    4:required list<banji> allBanji, #所有班级
}

分别把Services_A.thrift 序列化为json、跟thrift.bin文件,序列化到文件中。对比文件大小。

这里用php写了一个例子, 代码如下:

<?php
/**
 * Created by PhpStorm.
 * User: dengpan
 * Date: 16/4/21
 * Time: 12:39
 */

ini_set('memory_limit','10240M');

require_once __DIR__ . "/Thrift/ClassLoader/ThriftClassLoader.php";
use Thrift\ClassLoader\ThriftClassLoader;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TSocket;
use Thrift\Transport\TFramedTransport;
use Thrift\Transport\TPhpStream;
use Thrift\Transport\TPhpStreamMy;
use Thrift\Transport\TBufferedTransport;

$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift', __DIR__);
$loader->registerNamespace('Services', __DIR__);
$loader->registerDefinition('Services',  __DIR__);
$loader->register();

require "Types.php";


$school = [];
$school['schoolName'] = "hahhaha";
$school['age'] = 60;
$school['zhuanye'] = [
    '专业1',
    '专业2',
    '专业3',
];


$nameArr = ["张三", "李四", "王五", "王菲", "张韶涵", "王祖贤", "范冰冰", "新垣结衣", "詹姆斯", "诺维茨基"];
$sexArr = ["男", "女"];

$allBanji = [];
for($i = 0; $i < 1000; $i ++)
{
    $banji = [];
    $banji['banjiName'] = "计算机" + $i + "班";
    for($j = 0; $j < 1000; $j ++) {
        $banji['allStudents'][] = new student(
            [
            'studentName' => $nameArr[rand(0, count($nameArr) - 1)],
            'sex' => $sexArr[rand(0, count($sexArr) - 1)],
            'age' => rand(0, 6) + 18,
            ]
        );
    }
    $allBanji[] = new banji($banji);
}


$school['allBanji'] = $allBanji;
$sc = new school($school);


$str = json_encode($school);
file_put_contents("/tmp/json_1", $str);

$transport = new TBufferedTransport(new TPhpStreamMy(null, '/tmp/thrift_1.bin'), 1024, 1024);
$sc->write(new TBinaryProtocol($transport, true, true));

最终的结果, json文件跟thrift binary文件大小如下图:

QQ20160512-0@2x

thrift binary file是json文件大小的 0.63。为什么文件小这么多,我认为有以下几个原因,① json是文本的,而thrift是二进制的,文本文件跟二进制文件的区别,可以参考我的另一篇文章, 二进制文件跟普通文本文件的区别。② thrift序列化过程中,由于IDL在client、servo端都有一套,所以没有传输一些字段比如field name等等,只需要传递field id, 这样也是一些节省字节的方法。另外,提醒一下,序列化跟压缩不是一回事,比如上面的文件压缩为xz看看大小如下图:

QQ20160512-5@2x

压缩后体积变为原来的3.3%、4.4%。可以先序列化再压缩传输,压缩的compress-level要指定合理,不然压缩跟解压缩占据系统资源而且耗时大。

1.2序列化、反序列化的速度更快,提高性能

下面给出一个序列化反序列化测试

分别用① java Thrift binary protocol跟java fastjson库去序列以上对应格式为json, ②用C++ Thrift binary protocol与c++ jsoncpp序列化,对比速度,

java 测试代码如下:

public class ThriftDataWrite3 {
    private static final Random sRandom = new Random();
    public static void main(String[] args) throws IOException, TException{

        //构造school对象
        String[] nameArr = {"张三", "李四", "王五", "赵6", "王祖贤", "赵敏", "漩涡鸣人", "诺维茨基", "邓肯", "克莱尔丹尼斯", "长门", "弥彦", "威少"};
        int nameArrLength = nameArr.length;
        
        school sc = new school();
        sc.setSchoolName("哈哈哈哈哈哈");
        sc.setAge(12);
        List<String> l = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            l.add("专业" + i);
        }
        sc.setZhuanye(l);


        List<banji> allBanji = new ArrayList<banji>();
        for (int i = 0; i < 1000; i++) {
            banji bj = new banji();
            bj.setBanjiName("班级" + i);
            List allStuents = new ArrayList<student>();
            for (int j = 0; j < 1000; j ++) {
                allStuents.add(
                        new student(
                                nameArr[sRandom.nextInt(nameArrLength)],
                                ((sRandom.nextInt(2) == 0) ? "男" : "女"),
                                (sRandom.nextInt(10) + 18)
                        )
                );
            }
            bj.setAllStudents(allStuents);
            allBanji.add(bj);
        }
        sc.setAllBanji(allBanji);


        //①序列化为thrift binary protocol
        final long startTime = System.currentTimeMillis();
        TSerializer serializer = new TSerializer(new TBinaryProtocol.Factory());
        for (int i = 0; i < 200; i++) {
            byte[] bytes = serializer.serialize(sc);
            //serializer.toString(sc);
        }
        final long endTime = System.currentTimeMillis();
        System.out.println("thrift序列化200次时间为" + (endTime - startTime) + "ms");


        //②序列化为json
        final long endTime7 = System.currentTimeMillis();
        for (int i = 0; i < 200; i++) {
            JSON.toJSONString(sc);
        }
        final long endTime2 = System.currentTimeMillis();
        System.out.println("json序列化200次时间为" + (endTime2 - endTime7) + "ms");


        //准备待序列化的数据
        byte[] bytes = serializer.serialize(sc);
        String jsonStr = JSON.toJSONString(sc);



        //③反序列thrift binary data
        final long endTime3 = System.currentTimeMillis();
        TDeserializer tDeserializer = new TDeserializer();
        for (int i = 0; i < 200; i++) {
            school sc1 = new school();
            tDeserializer.deserialize(sc1, bytes);
//            System.out.println(sc1.toString());
        }
        final long endTime4 = System.currentTimeMillis();
        System.out.println("thrift反序列化200次时间为" + (endTime4 - endTime3) + "ms");



        //④反序列化json
        final long endTime5 = System.currentTimeMillis();
        for (int i = 0; i < 200; i++) {
            JSON.parseObject(jsonStr, sc.getClass());
//            System.out.println(JSON.parseObject(jsonStr, sc.getClass()).toString());
        }
        final long endTime6 = System.currentTimeMillis();
        System.out.println("json反序列化200次时间为" + (endTime6 - endTime5) + "ms");



    }
}

java的序列化thrift、json,反序列化thrift跟json的结果为:

thrift序列化200次时间为82482ms
json序列化200次时间为167954ms
thrift反序列化200次时间为42919ms
json反序列化200次时间为207896ms

其中可以看出java中thrift的序列化速度是fastjson 序列化json的2倍多,thrift反序列化的速度是fastjson反序列化json的接近5倍。

再看C++测试同样的Case, C++代码如下:

①  头文件thriftSerializeTest.h

//
// Created by 邓攀邓攀 on 16/5/11.
//

#ifndef UNTITLED_THRIFTSERIALIZETEST_H
#define UNTITLED_THRIFTSERIALIZETEST_H

//my own add header start
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include "service_a_types.h"
//my own add header end

using namespace std;


class thriftSerializeTest {
public:
    static school initializeSchool();
    static void jsonSerialize(school sc, string* str, int* time);
    static void thriftBinarySerialize(school sc,string* str, int* time);
    static void jsonDeserialize(string str, int* time);
    static void thriftBinaryDeserialize(string str, int* time);
};


#endif //UNTITLED_THRIFTSERIALIZETEST_H

② cpp 文件

//
// Created by 邓攀邓攀 on 16/5/11.
//

#include "thriftSerializeTest.h"
#include "service_a_types.h"
#include <sstream>
#include <sys/time.h>
#include "thrift/transport/TBufferTransports.h"
#include "thrift/protocol/TBinaryProtocol.h"
#include "json/json.h"


#define MAX_NUM 200

using apache::thrift::protocol::TBinaryProtocol;
using apache::thrift::transport::TMemoryBuffer;


school thriftSerializeTest::initializeSchool()
{
    school sc;
    vector<string> nameArr{"张三", "李四", "王五", "赵6", "王祖贤", "赵敏", "漩涡鸣人", "诺维茨基", "邓肯", "克莱尔丹尼斯", "长门", "弥彦", "威少"};
    int nameArrLength = nameArr.size();
    sc.__set_age(12);
    sc.__set_schoolName("哈哈哈哈哈哈");
    vector<string> v;
    for (int i = 0; i < 100; ++i) {
        std::ostringstream oss;
        oss << i;
        v.push_back("专业" + oss.str());
    }
    sc.__set_zhuanye(v);

    vector<banji> allBanji;
    for (int j = 0; j < 1000; ++j) {
        banji bj;
        std::ostringstream oss;
        oss << j;
        bj.__set_banjiName("班级" + oss.str());
        vector<student> allStudents;
        for (int i = 0; i < 1000; ++i) {
            student student1;
            student1.__set_age(18 + (rand() % 10));
            student1.__set_sex((rand() % 2) ? "男" : "女");
            student1.__set_studentName(nameArr.at(rand() % nameArr.size()));
            allStudents.push_back(student1);
        }
        bj.__set_allStudents(allStudents);
        allBanji.push_back(bj);
    }
    sc.__set_allBanji(allBanji);

    return sc;
}


void thriftSerializeTest::jsonSerialize(school sc, string *str, int *time) {
    Json::Value root;
    root["studentName"] = Json::Value(sc.schoolName);
    root["age"] = Json::Value((int)sc.age);
    for(auto v : sc.zhuanye)
    {
        root["zhuanye"].append(v);
    }

    for(auto v: sc.allBanji)
    {
        Json::Value banji;
        banji["banjiName"] = Json::Value(v.banjiName);
        for(auto k : v.allStudents)
        {
            Json::Value student;
            student["studentName"] = Json::Value(k.studentName);
            student["age"] = Json::Value((int)k.age);
            student["sex"] = Json::Value(k.sex);
            banji["allStudents"].append(student);
        }
        root["allBanji"].append(banji);
    }
    Json::FastWriter fw;
    struct timeval start, end;
    gettimeofday(&start, NULL);
    for (int i = 0; i < MAX_NUM; ++i) {
        fw.write(root);
    }
    gettimeofday(&end, NULL);
    int t = 1000*(end.tv_sec - start.tv_sec);
    *time = t;
    *str = fw.write(root);
}


void thriftSerializeTest::thriftBinarySerialize(school sc,string *str, int *time) {
    struct timeval start, end;
    gettimeofday(&start, NULL);
    for (int i = 0; i < MAX_NUM; ++i) {
        boost::shared_ptr<TMemoryBuffer> buffer(new TMemoryBuffer());
        boost::shared_ptr<TBinaryProtocol> binaryProtcol(new TBinaryProtocol(buffer));

        sc.write((binaryProtcol.get()));
        buffer->getBufferAsString();
    }
    gettimeofday(&end, NULL);
    int t = 1000*(end.tv_sec - start.tv_sec);


    *time = t;
    boost::shared_ptr<TMemoryBuffer> buffer(new TMemoryBuffer());
    boost::shared_ptr<TBinaryProtocol> binaryProtcol(new TBinaryProtocol(buffer));
    sc.write((binaryProtcol.get()));
    *str = buffer->getBufferAsString();
}


void thriftSerializeTest::jsonDeserialize(string str, int *time) {
    Json::Reader reader;
    Json::Value root;
    int num = MAX_NUM;

    struct timeval start, end;
    gettimeofday(&start, NULL);
    while (num > 0) {
        //确保成功解析
        if (reader.parse(str.c_str(), root)) {
            num --;
        }
    }
    gettimeofday(&end, NULL);
    int t = 1000*(end.tv_sec - start.tv_sec);
    *time = t;
}



void thriftSerializeTest::thriftBinaryDeserialize(string str, int *time) {
    struct timeval start, end;
    gettimeofday(&start, NULL);
    boost::shared_ptr<TMemoryBuffer> buffer(new TMemoryBuffer());
    boost::shared_ptr<TBinaryProtocol> binaryProtcol(new TBinaryProtocol(buffer));
    school sc;
    for (int i = 0; i < MAX_NUM; ++i) {
        buffer->resetBuffer((uint8_t*)str.data(), str.length());
        sc.read(binaryProtcol.get());
    }
    gettimeofday(&end, NULL);
    int t = 1000*(end.tv_sec - start.tv_sec);
    *time = t;
}

int main() {


    //获取school对象
    school sc = thriftSerializeTest::initializeSchool();
    string thriftStr, jsonStr;
    int time;


    //①序列化为thrift binary protocol
    thriftSerializeTest::thriftBinarySerialize(sc, &thriftStr, &time);
    cout << "thrift序列化" << MAX_NUM << "次时间为" << time << "ms\n";

    //②反序列thrift binary data
    thriftSerializeTest::thriftBinaryDeserialize(thriftStr, &time);
    cout << "thrift反序列化" << MAX_NUM << "次时间为" << time << "ms\n";


    //③jsoncpp序列化为json
    thriftSerializeTest::jsonSerialize(sc, &jsonStr, &time);
    cout << "jsoncpp序列化json" << MAX_NUM << "次时间为" << time << "ms\n";

    //④jsoncpp反序列化json
    thriftSerializeTest::jsonDeserialize(jsonStr, &time);
    cout << "jsoncpp反序列化json" << MAX_NUM << "次时间为" << time << "ms\n";

    return 0;
}

编译cpp的命令为:

g++ service_a_constants.cpp service_a_types.cpp  thriftSerializeTest.cpp -std=c++11 -I/home/dengpan/opt/thrift-0.9.3/include -L/home/dengpan/opt/thrift-0.9.3/lib64 -lthrift -ljsoncpp -O3

C++代码测试结果为:

thrift序列化200次时间为23000ms
thrift反序列化200次时间为48000ms
jsoncpp序列化json200次时间为330000ms
jsoncpp反序列化json200次时间为494000ms

其中可以看出c++中thrift的序列化速度是jsoncpp 序列化json的14.3倍,thrift反序列化速度是jsoncpp反序列化json的10.2倍。其中c++序列thrift比java快,c++ jsoncpp严重不如java的fastjson,可能选取更好的c++ json库,可能结果好看点,但是java的fastjson还是很优秀的,用了大量的ASM写法,应该比java、c++绝大多数的json库要好。具体的,有空看看其他的c++ json库来测试一下。读者有更好的库,也可以推荐一下。

thrift对比json,流量更小、序列化反序列化更快,甚至快上5-10倍,这两个特点,在移动端跟实时性要求很高的游戏上,显得非常重要,也吸引人们在这些场景去使用像thrift这样的RPC、序列化的工具。

1.3兼容性好

Thrift Types定义了一些基本的Base Type,分别在源代码各个语言中都有一一映射。但是每个语言中,不是所有的定义的类型都支持,Thrift的一些绝大多数语言都支持的base Type有void、bool、i16、i32、i64、double、string。然后thrift通过一一映射,将IDL里面的base Type与各种编程语言里面做了对应,这样可以保证兼容性。而且不会存在java提供的一个接口,一个json字段是i64的,到了C++调用http接口的时候,拿int去取这个字段,丢失了数字的高位。这种情况在跨语言调用,由于每个语言的int、long、short的位数与范围都不同,开发者涉及多语言需要对每个语言的类型范围、位数很清楚才可以避免这样的极端情况的丢失数据,而用了Thrift就不用担心了,它已经帮我们映射好了一些基准类型。后面的thrift code generator也是生成基于TBase类型的对象,用到的也都是thrift base types。关于thrift base type可以参考官方文档: Thrift Types。具体的每个语言支持哪些Types定义,可以看源代码,比如thrift-src中,看到lib/rb/ext/thrift_native.c 中TType constants相关的为:

QQ20160512-1@2x看到thrift跟ruby的类型支持情况,没看到binary, 说明ruby不支持binary类型。c++支持thrift的binary类型。其实基准类型,已经足够使用了,建议不要使用不属于i16、i32、i64、double、string、list、map之外的,非通用的TType,不然可能没事找事,碰到一些兼容问题。

1.4可传递对象

由于IDL的存在,可以在IDL里面定义一些表示层级关系,数据结构,把我们要传递的对象,翻译成IDL里面的struct, 然后再把IDL编译成对应文件,这样就可以传递对象了。json等文本协议,会丢失部分信息。比如php里面$a =[“name” => 123], json_encode后,跟 $a= new stdClass();$a->name = 123; json_encode之后,是一样的。一个是数组,一个是对象。类似的这种序列化中类型丢失或者改变的,其实还有其他的例子。要我们小心的去使用。(可能这个例子举得并不充分,因为php里面数组太灵活了,可以干绝大多数事情)。而Thrift,只要我们定义好IDL,就可以放心的去传递对象了。

二、序列化方法

任何一个Struct,Thrift code generator都为它生成了一个对应的class,该类都包含write和read方法,write就是serialie过程, read方法就是unserialize过程。由于Thrift是连带Client调用Service的代码整套生成的,因此想单独拿Thrift序列化一个对象官方没给什么例子,不过各种语言把struct序列化为binary的方法大同小异。我这里研究了下各种语言怎么把对象单独序列化为string,这里一并贴出来。

①   C++ 序列thrift对象为string

boost::shared_ptr<TMemoryBuffer> buffer(new TMemoryBuffer());
boost::shared_ptr<TBinaryProtocol> binaryProtcol(new TBinaryProtocol(buffer));

youThriftObj.write((binaryProtcol.get()));
buffer->getBufferAsString();

② java序列化thrift对象为string

TSerializer serializer = new TSerializer(new TBinaryProtocol.Factory());
serializer.toString(bb); //就是序列后之后的string

③ php序列化thrift对象为string

$memBuffer = new \Thrift\Transport\TMemoryBuffer();
$protocol = new TBinaryProtocol($memBuffer);
$bbb->write($protocol);
$str = $memBuffer->getBuffer();

④ php序列化thrift对象到File

由于php中,没有TFileTransport, 因此改写了一下TPhpStream, 改成TPhpStreamMy,这样可以比较方便序列化到文件跟从文件中反序列化,这里也一并给出,TPhpStreamMy的github gist地址。放到php Thrift lib的Thrift/Transport/TPhpStreamMy.php位置。

序列化thrift对象到file

$transport = new TBufferedTransport(new TPhpStreamMy(null, ‘your-thrift-binary-file-path’), 1024, 1024);
$yourThriftObj->write(new TBinaryProtocol($transport, true, true));

从文件中读取thrift对象

$transport = new TBufferedTransport(new TPhpStreamMy(‘your-thrift-binary-file-path’, null), 1024, 1024);
$yourThriftObj->read(new TBinaryProtocol($transport));

其他语言的thrift序列化过程,也是类似步骤。

三、反序列化方法

反序列化方法跟序列化方法步骤类似,主要是把write操作改为read操作即可,下面给出一些语言的反序列化方法:

① C++ 反序列string为Object

boost::shared_ptr<TMemoryBuffer> buffer(new TMemoryBuffer());
boost::shared_ptr<TBinaryProtocol> binaryProtcol(new TBinaryProtocol(buffer));
buffer->resetBuffer((uint8_t*)str.data(), str.length()); //str为thrift对象序列化之后的string
yourThriftObj.read(binaryProtcol.get());

②  java反序列byte[]为Object

TDeserializer tDeserializer = new TDeserializer();
tDeserializer.deserialize(yourThriftObj, bytes);  //bytes等于thrift对象序列化之后的byte[]

③ php反序列化string为Object

$memBuffer = new \Thrift\Transport\TMemoryBuffer($str); //str为thrift对象序列化之后的string
$protocol = new TBinaryProtocol($memBuffer);
$yourThriftObj->read($protocol);

其他语言的thrift反序列化过程,也是类似步骤。

四、序列化代码解读-字节探究

这里用一个更简单的thrift service完整例子,用c++实现thrift的THttpServer, php实现thrift的THttpClient, 用wireshark抓包,看看整个通信过程,与字节发包的情况。

示例thrift/service_b.thrift IDL如下:

struct student {
    1:optional string name,
    2:optional i64    age,
}

service My_Services {
    student getStudent(1:student st);
}

C++实现thrift THttpServer代码如下:

// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.

#include "My_Services.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/THttpServer.h>
#include <thrift/server/TNonblockingServer.h>



using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

using boost::shared_ptr;

class My_ServicesHandler : virtual public My_ServicesIf {
 public:
  My_ServicesHandler() {
    // Your initialization goes here

  }

  void getStudent(student& _return, const student& st) {
    // Your implementation goes here


    _return.__set_age(st.age * 2);
    _return.__set_name(st.name + st.name + "哈哈哈哈");

    printf("getStudents \n");
  }

};

int main(int argc, char **argv) {

  int port = 9090;
  shared_ptr<My_ServicesHandler> handler(new My_ServicesHandler());
  shared_ptr<TProcessor> processor(new My_ServicesProcessor(handler));
  shared_ptr<TServerTransport> serverTransport(new TServerSocket("127.0.0.1", port));
  shared_ptr<TTransportFactory> transportFactory(new THttpServerTransportFactory());
  shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());

  TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);


  server.serve();
  return 0;
}

server实现很简单,就是接受student对象,然后把student对象中的student.age = student.age * 2, student.name = student.name + student.name + “哈哈哈哈”,然后返回这个student对象。

php实现Php THttpClient如下:

<?php
// 引入客户端文件
require_once __DIR__ . "/../Thrift/ClassLoader/ThriftClassLoader.php";


require "My_Services.php";
require "Types.php";


use Thrift\ClassLoader\ThriftClassLoader;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TSocket;
use Thrift\Transport\TFramedTransport;

$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift', __DIR__ . "/../");
$loader->registerNamespace('Swoole', __DIR__ . "/../");
$loader->registerNamespace('Services', __DIR__ . "/../");
$loader->registerDefinition('Services', __DIR__ . "/../");
$loader->register();

$transport = new Thrift\Transport\THttpClient("127.0.0.1", 9090);
$protocol = new TBinaryProtocol($transport);

$client = new My_ServicesClient($protocol);


$transport->open();
$res = $client->getStudent(new student(
    [
        'name' => '邓攀',
        'age' => 1222,
    ]
));
var_dump($res);
$transport->close();

用wireshark抓包查看http通信过程,注意这个是本地127.0.0.1通信的,要在wireshark抓包指定loopback监听QQ20160512-2@2x

然后在wireshark中指定过滤器

http.content_type == “application/x-thrift” && http.request.method==”POST”

监听到请求,

QQ20160512-3@2x

可以看到监听到的请求,Content-Length是69B。为了方便跟踪,follow 这个tcp流,

QQ20160512-6@2x

可以看到http ressquest发送了226字节,http response收到了257字节。我把发送的数据、接收的数据,去除http头,就是纯二进制数据,写进了文件/tmp/thttp_request、/tmp/thttp_response中。文件大小与hexdump的文件值如下:

QQ20160512-8@2x下面来分析下这个文件的字节内容。以thttp_response文件为例,我们收到了69个字节,解析出来了name:邓攀邓攀哈哈哈哈,age:2444的数据。

五、提高一些脚本语言中的序列化性能

一些脚本语言的序列化性能不太好,再特别注意性能的场景下,建议可以用C/C++实现thrift的序列化,然后写成语言的一个模块、module的形式,给脚本语言去调用,这样可以极大的提高性能。也能利用到thrift的优点。

参考文章:

  1. 让Thrift支持双向通信--董的博客
  2. Thrift: The Missing Guide
  3. Thrift 官方文档

替换wordpress全站为相对路径

作者: dplord, 访问量 816





近来有时候在自己的电脑上,单机捣鼓wordpress, 甚至有时候我想离线写一篇文章,到时候传上去。发现wordpress所有资源都是带全路径的。因此研究把自己的wordpress, 整站替换为相对路径。这样于我而言有几个好处。

① 当网站是http的时候,请求的资源是http的、当网站是https的时候,请求的资源是https的。

②  部署的时候好部署,直接替换url之类的。

③  127.0.0.1上的wordpress跟我的域名https://dplord.com上的没有任何区别,都可以本地写文章,想本地覆盖服务器可以直接覆盖。这里记下来我替换wordpress全站为相对路径的方法。

④ 当访问https://dplord.com时候,原来的历史post里面是/wp-content/uploads/2015/12/xxxxx.jpg这样的资源,由于我在nginx开启了http的80端口强制301到443的https,使用relative path可以直接命中https资源。减少多次301跳转。节省服务器资源,同时也让客户端访问更快一点。

一、哪些资源需要被替换为相对路径

想了想,大概有这些可能需要被替换

① 引用的图片资源, 比如src=”/wp-content/uploads/2015/12/cropped-banner_cosmos_20151214.jpg” 替换为src=”/wp-content/uploads/2015/12/cropped-banner_cosmos_20151214.jpg”

② 各种目录、菜单栏、近期文章、文章详情、标签链接、评论链接为相对路径

二、更新wp_posts表, 处理旧文章(附件)里面的链接

由于有些历史的wp_post已经的一些包含src=”/wp-content/uploads/2015/12/cropped-banner_cosmos_20151214.jpg”的html已经存进数据库wp_posts表了。是静态写进去了。因此要先写段程序替换所有已经存在的posts的链接。

比如这里查询一下看看有哪些历史post包含了绝对路径,

select count(*) from wp_posts where (post_content like “%/wp-content%”) or (post_content like “%/wp-content%”) or (post_content like “%/wp-content%”) or (post_content like “%/wp-content%”);

5344626

为了方便,这里写了一个ruby程序,更新下数据表。代码如下:

wordpress_update_url.rb

require 'active_record'

ActiveRecord::Base.establish_connection(
    :adapter => 'mysql2',
    :host => '127.0.0.1',
    :username => 'your-db-username',
    :password => 'your-db-password',
    :database => 'your-db-database-name'
)

class Wp_posts < ActiveRecord::Base
  self.inheritance_column = nil
  self.table_name = "wp_posts"
end

replaceStr = ['/wp-content', '/wp-content', '/wp-content', '/wp-content']

res = Wp_posts.find_by_sql('select * from wp_posts where (post_content like "%http://www.dplord.com/wp-content%") or (post_content like "%http://dplord.com/wp-content%") or (post_content like "%https://www.dplord.com/wp-content%") or (post_content like "%https://dplord.com/wp-content%")')
res.each{|post|
  replaceStr.each{|str|
    if post.post_content.include? str
      post.post_content = post.post_content.gsub! str, "/wp-content"
    end
  }
  post.save
}

更新之后, 历史post里面都是相对路径了。

三、更新wp_options里面的siteurl跟home

wp_options这个表,存了一些选项值与配置相关的。原先写入数据库的历史post的siteurl  http://www.dplord.com的值就是存在这个表里面。

update wp_options set option_value=’/’ where option_name=’siteurl’ or option_name = ‘home';

更新完了之后,查看首页的html基本都是相对路径,但是剩下来一点点残余的。比如主题资源、插件里面的资源引用、首页header image地址等。

四、替换主题资源里面的绝对路径资源引用及其他

① 修改引用的绝对路径js资源

QQ20160605-0@2x

编辑 wp-includes/script-loader.php 第65行$scripts->base_url = $guessurl; 为$scripts->base_url = ”; (该行在wp_default_scripts函数里)

修改完了刷新,

QQ20160605-1@2x

发现已经改过来了。

② 修改wordpress自定义背景图的url

这里custom-background生成了一段html

QQ20160605-2@2x

这个在你的wordpress仪表盘,重新设置下background-image即可。

③修改引用的绝对路径css资源

QQ20160605-3@2x

修改wp-includes/script-loader.php function wp_default_styles()下的第6行$styles->base_url = $guessurl;为$styles->base_url = ”;

④ 修改wordpress顶部header-image

在后台重新选择一下图片即可。

chrome扩展开发小结

作者: dplord, 访问量 757





上次参加公司内部Hackthon做了一个chrome插件,这里记录下Chrome扩展开发小结。

一、Chrome扩展的结构

Chrome扩展就是一个目录,这个目录包含一个manifest.json,  就是一个chrome扩展了。具体的文件组织,只需要一个manifest.json, 其他的资源文件组织方式是只有的。

比如我做的是一个根据url,捕获url在发送http Request之前,在url里面添加一些Header实现的内部开发的dev插件。

我的manifest.json 声明如下:

{
  "manifest_version": 2,
  "name": "你的插件名字",
  "description": "你的插件描述",
  "version": "1.0",
  "permissions": [
    "webRequest",
    "webRequestBlocking",
    "storage",
    "http://www.example1.com/",
    "http://www.example2.com/"
  ],
  "browser_action": {
    "default_icon": "你的logo图",
    //右上角看到的logo
    "default_popup": "popup.html"
  },
  "icons": {
    "16": "你的logo图",
    //在chrome://extensions/ 页面看到的logo
    "48": "你的logo图",
    "128": "你的logo图"
  },
  "background": {
    "page": "background.html",
    "persistent": true
  },
  "options_page": "options.html"
  //设置页面
}

主要是声明一些资源文件、popup页面、background资源、options页面。

比如我开发的一个扩展的目录结构如下:

QQ20160604-7@2x

二、各种文件的具体作用

popup.html

有人看到,”default_popup”: “popup.html” 。popup.html是什么呢。比如一个chrome扩展Xmarks在chrome的菜单栏显示,点击这个扩展会弹出一个小框框,如下图红色框框区域,这个页面其实就是popup.html, 我们只要定义了popup.html页面,就可以在该html里面控制它的样式。

QQ20160604-5@2x

 

background.html

background.html是什么呢,顾名思义,是一个运行在后台的,其实我做的插件中background是没有页面的空html, 作用是有些执行在后台的js, 比如我的background.js ,需要在manifest.json声明background.html, 然后在background.html里面<script src=”js/background.js”></script>引用background.js这样,background.js就可以始终在后台运行了。

options.html

options.html顾名思义,是设置页面。从chrome://extensions/ 页面,点击你插件的选项,就到了设置页面,也是个html。

三、开始开发一个dev的网络插件

chrome插件其实都是标准的html 、js那套,就跟做网站一样,唯一注意的是资源的引用权限、资源的跨域问题啊,执行的先后顺序,popup.js、backgroud.js、options.js的作用时间以及什么时候回触发。

首先画好各种界面,然后组织好文件,引用各种js,温馨提示: chrome扩展里面用到的html都不能在html写<script>你的js代码</script>,必须要写到js文件里面,<script src=”your-js-file”></script>这样的引用。

各个js之间传递数据之类的,可以自定义存储方式啊,用html5的localStorge、indexeddb、websql那一套,我们采用的是在localStorge里面存放json文本的数据,各个js都操作这些数据,同一个chrome扩展下的localStorge是共享的。

先画好界面,设置好页面,引用好各种实现动作的js。

然后在js里面开始实现你的逻辑了。我们的核心逻辑就是before HTTP Request,加一个监听器,实现一些操作,这些可以看chrome扩展的API,比如捕获http请求、操作chrome tab、操作书签、添加道理, chrome都给js封装了接口。监听一个请求的代码如下:

background.js

chrome.webRequest.onBeforeSendHeaders.addListener(
    function (details) {
        //do-something
        //添加自定义HTTP头
        details.requestHeaders.push({name: "MySpecifiedHttpHeaderName", value: "MySpecifiedHttpHeaderValue"});
        return {requestHeaders: details.requestHeaders};
    },
    {urls: ["*://*.example.com"]},
    ["requestHeaders", "blocking"]
);

这样在启用你的插件后,匹配到*.example.com 就回运行这段代码在http header里面,添加一行里自定义的header “MySpecifiedHttpHeaderName: MySpecifiedHttpHeaderValue” 出去。注意这个捕获到*.example.com的请求的网络权限,在manifest.json 的permissions 里面申请好。

其他的设置界面等等都是跟正常的html js dom操作那一套一模一样,就不多提了。

是不是很简单,有需求的可以自定义插件玩玩儿啊。

最后插件开发好,发布的时候,需要在chrome://extensions/页面,点击打包扩展程序,选择你的扩展目录,打包成一个your-chrome-extension.crx扩展(其实就是个标准zip压缩包)。

东南亚黄袍佛国--泰王国初探

作者: dplord, 访问量 369





很长时间没有出行游历了,上溯一次还是2014年3月,期中开始完了,又拿到了深圳腾讯跟北京百度的实习,来北京考察一次将来的工作环境怎么样的考察游行顺带去拜访下理工圣地-Tshinghua University,早知道北京雾霾如此严重,就不来了,我来的那次是2014年4月多,天气正值一年最好的时候。已经很久很久,没有怎么出行了,毕业季本来准备N久去美国游玩儿,后来资金不足,只好作罢。这次趁着5.1小长假,决定去泰国游玩儿一下。看到一个秀逗的图,

mmexport1461827754044

 

虽然秀逗,参考性不大,但是目前没啥资金,也就只能考虑东南亚逛逛看看了。

一、总的行程安排

由于是第一次出境游,就只好选择跟团游,其实跟团游还是很上佳的选择,另外,如果自由行之类的,一定要做好功课、注意安全,资金花了多少倒是其次,安全第一。

我是在北京工作,去东南亚是往南方,往武汉中转一下会便宜得多,这个后面会提到。我选择的行程是2016-04-30 上午中国武汉天河机场到泰国甲米机场的12:40 ~ 16:00(中国时间)的航班, 返程是2016-05-05上午08:55~14:10(泰国时间)泰国甲米机场到武汉天河机场的航班。6天5晚,在泰国住5个晚上。我是携程上报的旅行团,我的旅游项目套餐链接为: http://vacations.ctrip.com/grouptravel/p3517648s477.html

总的开销为: 交给旅行社 3210 (2780人民币旅游套餐,430人民币泰国签证费用以及领队小费),其他的在那边5天吃喝玩儿,花了2400人民币。最后回国买了1800人民币的礼物,化妆品、吃的、小东西等等,带给亲友、同事等。

我们的旅行社是湖北康辉国际旅游社,这个还是一个很不错的旅行社,搜了下前十的中国旅行社名单如下:

1、中旅总社CTS
2、中国国旅
3、中青旅CYTS
4、康辉旅行社
5、春秋旅行社
6、广之旅
7、锦江旅行社
8、广东中旅
9、中信旅行社
10、众信国际旅行社

康辉旅行社排第四,还是很不错的。由于是在湖北康辉,所以价格啊还是比较便宜的,同等的价格在北京出发的起码贵大概2000以上。因为北京开办的旅行社,本来成本高、人力啊、公司大楼啊都比较贵,再加上北京这边土豪很多,一个东南亚的旅行套餐,卖个8k、1万5的觉得很OK,北京土豪不差钱,所以一般在北上广深高消费城市的像我这么穷的选择旅游啥的,选择在你的目的地顺路的一个还算交通便利的非北上广深的二线或者次一线城市,中转下,费用一下子降低很多,武汉就是个交通很发达、成本比较低的一个不错的中转城市的选择。我来去武汉2趟的硬卧火车才500。这点,当然土豪可以忽略啊。

地图查了查,从北京到武汉1200KM,,北京到甲米是3732km,7小时航班。

北京到甲米航班7个多小时

QQ20160507-0@2x北京到甲米3732km

tailand-2武汉到甲米2859km

tailand-1

 

后来旅游回来,发现我买的旅行套餐真的很不错,玩儿的挺好,中国的领导乔姐跟泰国的导游阿杨以及泰国的副导游黑马王子,都很不错的,全程住的酒店都挺不错,吃喝也还ok。以后还选择出境游啥的,可以再看看这个旅行社提供的服务,看了下各个国家的旅游都有,价钱也还可以,携程网上,湖北康辉官网提供的旅游套餐,http://vacations.ctrip.com/supplier-20737-11322-s364 , 有需要的可以瞄一瞄。

二、行程与风景

泰国是个旅游业很发达的国家,或者悲哀点说,泰国以前是个经济以传统农业为主,工业薄弱,近些年经济发展比较依赖旅游业。我们去购物啊,那边的导购、服务人员态度特别特别好,泰国被誉为”微笑国度”,服务态度可见一斑。,说『泰国每年2100万游客,中国游客占1/3以上,你们中国游客就是我们的爹、我们的妈,要是没有每年这么多游客,他们都要饿死了』,一半是客套话,一半也是发自真心。泰国6722万人口,而泰国2016预计游客总数为3200万。每人在泰国境内花5000人民币(包括住宿、机票等),对应2万5泰铢,除以2,可以给泰国国民每个人带来接近12500泰铢的人均GDP, 这个是很可观的。他们的工业、其他产业的确太薄弱了,不过泰国有5500多个旅游景点,国家也完成了各种服务于旅游业的基本设施的建设,比如7-eleven便利店、Lotus Market等等到处都是啊。

我们的行程上面的携程旅游套餐都写了,主要玩儿的几个不错的我觉得是

1 大小皮皮岛、帝王岛  2 割喉岛钟乳石参观  3  骑大象  4  情人沙滩等等  5  小皮皮岛浮潜 ,大部分是海景、沙滩景色。由于5.1假不多,只玩儿普吉岛这一带,如果有10天的,可以连带普吉岛、清迈、曼谷、芭提雅都玩儿一下。放几张亲自拍的图片。

清澈的海水

tailand-4

 

beautiful sunset of Rock Island

tailand-5人妖秀

tailand-6

 

泰国大象

tailand-7

 

情人沙滩

tailand-8

dplord with monkey and lizard(蜥蜴) on his shoulder

tailand-10

 

燕子洞(产燕窝的地方,泰国燕窝很有名的)

 

tailand-11酷酷的艄公

tailand-12

 

眼镜王蛇

IMG_20160504_133846_1

值得一提的是我们行程5个晚上的酒店档次都很高的,最后一晚住的是135平的带泳池的高规格酒店,还有厨房等等设施。去前台用英文问了工作人员看了价格表,我们那个房间,21000泰铢一晚(4200人民币一晚),住的酒店地址如下

2016_05_03_21.54.07

价格表在这里:

IMG_20160506_092641。前4晚住的酒店也都很大很不错,真不知道3200的旅行套餐扣掉250的落地签、扣掉5个晚上的酒店住宿、2趟飞机、所有的早中晚餐,会不会亏本。哈哈哈。我们的导游人很好很nice,全程就最后一天带我们去购物,也不强制购物,就觉得好的可以自己买一点,全程很开心。

所有的旅行300多张图片,我传到我的Facebook相册跟qq空间相册了,国外的朋友,可以访问Facebook相册地址: journey-on-Thailand_20160501 ,国内的朋友,可以看看qq空间相册,qq空间相册地址: journey-on-Thailand-20160501, 相册都是公开的,每个人都可以看到,里面的一些酒店、饮食,都有图片,想参考套餐质量的,可以看看该相册。

三、一些旅行须知跟小TIPS

  •  那边女生一般喊『shui jing jing』(音同水晶晶),男生一般喊『lao ma ma』(音同老妈妈)
  • 泰国为小费制国家,一般给给小费别人挺开心的,给20泰铢就行(人民币4块钱大概)
  • 去泰国可以买『泰国7天happy卡』,是那边移动运行商发行的 ,大概50块钱,泰国本地跟中国淘宝网上、或者中国机场都可以买,就是7天的卡,不限制流量使用,可以打国内电话,7天后作废。我买的淘宝网上32块钱的,在景区绝大多数地方网速都很不错。不过3G信号只支持WCDMA的,不支持WCDMA的手机(中国移动LTE 3G定制版手机)可以借个WCDMA的手机啥的。当然2G GSM信号还是都支持的。
  • 超过100ml的液体不能放在带上飞机的包里面
  • 那边用银行卡直接支付现金是最划算的方式,国内取泰铢的比率也不是很高,那边去拿人民币兑换泰铢比率比国内低一点点,银行卡直接支付是最划算的。带1000-2000 人民币的泰铢供吃饭,给小费就行了。很多大店支持支付宝、微信支付的。银联卡更是支持,银联卡可以直接在他们的ATM取泰铢,取20000人民币以内的等值泰铢,扣100泰铢的手续费(人民币20大概)。

四、泰国文化与旅行感想

泰国人很友好,他们全民信佛,乐乐呵呵开开心心的。我此次泰国一行没碰到那种性格很不友好的本地人。也许跟他们生活在热带有关,寒带的人,貌似就要冷漠一点。泰国国王拉玛九世(https://zh.wikipedia.org/zh/普密蓬·阿杜德)1946年继位迄今,广受泰国人民爱戴,是发自内心的爱戴,每个地方碰到的人都对泰国国王赞不绝口那种。泰国实行以国王为元首的民主政治制度,就是君主立宪制。

佛教是泰国的国教,全民信佛。而且普及力度比我们国家信佛要大的多。泰国是小乘佛教,我们中国是大乘佛教。小乘佛教讲究修今生,开开心心过好每一天乐乐呵呵就好,修行是为了让自己过好、让这辈子过得更好。而大乘佛教讲究普渡天下苍生、讲究无穷无尽的轮回中要修成正果,要为下辈子积善行德。我们中国的佛教,讲究庄严,一切跟佛法有关的,都是肃穆庄严神圣的。而泰国,我们去看人妖秀的时候,那些”美女”装扮成华美的佛教服装、跟菩萨一样的类似法器、皇冠啊等等,公然把佛教元素拿到娱乐化的表演之上,这些在我们国内近乎不可能的,国内没有人去把庄严的佛教元素拿到娱乐化场合。我们的神话故事,商纣王亵渎神灵女娲,导致商朝国运断送,上天灭亡商朝,可以类比管窥一二。所以看到的泰国人,各种阶层的,都挺开心的其实。我们做了2次SPA,给我们做SPA的2个员工,做SPA需要2个小时,2个小时,我都睡着了,她们不断的在用泰语交流,很欢快的说了整整2个小时。虽然给人按摩很累,但我感觉她们每个人真的挺开心的。

旅游业对泰国非常重要,国家都大力发展,因为就像他们说的,『你们游客就是我们的爹、我们的妈,没你们我们都饿死了』,工业薄弱的情况下发展旅游业是一个很好的方向,就像泰国的天然的乳胶树产的乳胶是世界最好的,如果按乳胶原材料出口卖给其他国家,基本卖不了多少钱,但是把乳胶加工为非常优质、健康、适合睡眠的乳胶床垫,就可以卖到1万4RMB一个床垫了,我去现场体验了一下,的确舒张性好、无压迫、健康舒适,国内家具大卖场的很好的床垫也就7千可以买到一个好的,这个很好的乳胶床垫就可以卖到1万4,当然乳胶床垫、乳胶枕头真的很好很值得为自己的睡眠、健康投资,现在很多中老年人有脖子酸痛、肩周炎、腰椎间盘突出,这个更我们的硬板床压迫身体也有关系,本来我也准备买一个好的乳胶床垫送给我父母的,但是目前手头不多,还是等有机会带我父母一起去一趟泰国,给他们现场买一个把。泰国的服务业很好,好像一个人服务中犯了大错就会永远不能从事服务业。而且服务业普遍给小费,演出、斗蛇、做spa、坐船、住酒店、跟人妖合影….都要给小费。给了小费,人家觉得服务受到认可,就会很开心,相比之下,国内的服务业,对比同样去旅游的时候享受到的服务,远远没有泰国的服务业那么好。泰国被称为”微笑国度”,就是形容他们的服务业的。

泰国是个私有制的资本主义国家,只要有钱,土地啊、小岛啊之类的都可以买,都可以自己建设,小岛你可以私人买下来,可以开景点也可以自己随便用。大量的矿脉、土地、宝石矿、景点都属于王室,而王室其实不等同于政府的,导致政府很穷,税收也不多,建设国家就没有那么多的资本了。而且对国家的掌控力远远没有我们国家强。我们国家绝大多数,政府很高效、强力,而且对国家掌控力很强,要做一个大的建设工程、铁路建设、基建建设,一下子可以马上执行。而且所有的土地、景点、小岛、矿石等等属于国家政府,这些产生的钱用来建设国家的。所以我们这些年发展还可以,而且有继续发展下去的动力。说说泰国人妖,人妖我了解到的是,穷苦人家的孩子小时候2-3岁就被培养为人妖,用来演出赚钱改善生活之类的。我看人妖表演的时候,都有点难过。为什么每个人出生的时候不能有基本的选择正常有尊严的生活的权利。wikipedia上看到,人妖一般只能活到40岁左右。而我们国家其实公平的多,我们国家希望每个人过上基本有尊严的生活。当然上层社会的生活我也没看到,但是绝大多数我看到的人是公平的。而泰国他们都乐乐呵呵开开心心的,貌似接受着国家中人跟人之间的差异化。也许他们觉得,有些人天生就是高贵的国王,有些人天生就是给人服务娱乐的,有些人天生就是劳作的,有些人天生就是做人妖给别人演出、逗开心的,等等。这也许也是稍弱国家的无奈吧。具体的我没在那边生活很久,也只能从自己的视角管窥一二,但是无论怎么说,我希望这个星球上的任何人,过上基本有尊严快快乐乐的生活,大家生而平等,并都享有努力后改造现有生活的机会。愿,世界将每个人,温柔以待。愿,尘世中的所有人,有根有依,找到自己内心的皈依。May there be enough clouds in everyone’s  life to make a beautiful sunset.

Next Station,Russia 或者 北欧 或者 日本。

 

 

opensuse安装gdb debug等包

作者: dplord, 访问量 185





今天用gdb调试一个segmentation fault的时候,本机的gdb需要安装debug包,国内的各大开源镜像站,ustc、hust 、tsinghua.tuna以及mirrors.souhu.com mirrors.163.com mirrors.aliyun.com 都没有opensuse的debug源,其他的update oss non-oss等源都有。debug官方源(http://download.opensuse.org/debug/distribution/leap/42.1/repo/oss/)实在是太慢了,基本不可用。不知道为什么都不同步debug源,可能觉得debug源一般人用不到把。找了半天找到了一个可用的debug国内源,记录一下: 浙大Mirrors 的opensuse debug源是可用的,http://mirrors.zju.edu.cn/opensuse/debug/distribution/leap/42.1/repo/oss/。

zypper ar http://mirrors.zju.edu.cn/opensuse/debug/distribution/leap/42.1/repo/oss/ zju-debug添加后,sudo zypper ref刷新源。

然后安装opensuse debug包:

zypper install krb5-debuginfo-1.12.1-19.9.x86_64 libapparmor1-debuginfo-2.10-3.2.x86_64 libbz2-1-debuginfo-1.0.6-30.4.x86_64 libcom_err2-debuginfo-1.42.11-10.2.x86_64 libcurl4-debuginfo-7.37.0-5.2.x86_64 libfreetype6-debuginfo-2.5.5-8.2.x86_64 libgcrypt20-debuginfo-1.6.1-18.7.x86_64 libgpg-error0-debuginfo-1.13-3.6.x86_64 libidn11-debuginfo-1.28-4.1.x86_64 libjpeg62-debuginfo-62.1.0-31.1.x86_64 libkeyutils1-debuginfo-1.5.9-4.3.x86_64 libldap-2_4-2-debuginfo-2.4.41-9.1.x86_64 libltdl7-debuginfo-2.4.2-16.6.x86_64 liblzma5-debuginfo-5.0.5-3.5.x86_64 libmcrypt-debuginfo-2.5.8-117.1.x86_64 libopenssl1_0_0-debuginfo-1.0.1i-4.1.x86_64 libpcre1-debuginfo-8.33-3.5.x86_64 libpng16-16-debuginfo-1.6.8-2.2.x86_64 libsasl2-3-debuginfo-2.1.26-8.1.x86_64 libselinux1-debuginfo-2.3-3.5.x86_64 libssh2-1-debuginfo-1.4.3-10.1.x86_64 libxml2-2-debuginfo-2.9.1-8.1.x86_64 libxslt1-debuginfo-1.1.28-8.1.x86_64 libz1-debuginfo-1.2.8-6.4.x86_64

就可以使用gdb debug了。