程序性能分析是一个非常庞大的话题,自下而上涉及底层CPU、网络、I/O,上层的业务逻辑复杂度、算法性能等。同时性能分析也是容易被大家忽略的点,往往线上出现问题、服务超时才会着手排查。工欲善其事,必先利其器,本文介绍两种Golang性能分析工具:pprof和torch。

1、pprof
pprof是Golang官方自带的程序性能分析包,可以在net/http/pprofruntime/pprof这两个地方引用,net/http/pprof实际也调用了runtime/pprof,并在http端口上暴露。
(1)服务程序
引入方式如下:

import _ "net/http/pprof"

demo代码如下

package main

import "net/http"
import _ "net/http/pprof"

func main() {
    http.ListenAndServe(":8080", http.DefaultServeMux)
}

然后访问该链接 http://127.0.0.1:8080/debug/pprof/
可以看到当前web服务的状态,包括CPU占用情况和内存使用情况

(2)应用程序
这种场景下需要引入runtime/pprof包,对CPU运行信息进行采样存储,比如

package main

import "runtime/pprof"
import "os"
import "log"

func main() {
    f, err := os.Create("cpu.prof")
    if err != nil {
        log.Fatal(err)
    }
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()
}

下一步需要使用pprof文件做出性能分析图

go tool pprof test.exe cpu.prof

然后输入web命令生成svg文件,svg用浏览器打开可以看到调用链。如果提示"profile is empty"是由于程序使用了很少的内存,无法进行采样导致,可以修改runtime.MemProfileRate降低采样率,运行前加上环境变量GODEBUG="memprofilerate=1"

- 阅读剩余部分 -

GraphQL是一种用于API的查询语言也是一个满足你数据查询的运行时,GraphQL对你的API中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,查询总是返回可预测的结果。

1、REST VS GraphQL
GraphQL目前被认为是革命性的API工具,因为它可以让客户端在请求中指定希望得到的数据,而不像传统的REST那样只能呆板地在服务端进行预定义。下面通过一个例子对比两种API:
(1)REST的核心思想就是资源,每个资源都能用一个URL来表示,你能通过一个GET请求访问该URL从而获取该资源。根据当今大多数API的定义,你很有可能会得到一份JSON格式的数据响应,整个过程大概是这样:

GET /books/1
{
  "title": "Black Hole Blues",
  "author": { 
    "firstName": "Janna",
    "lastName": "Levin"
  }
  // ... more fields here
}

在REST中,一个资源的种类与你获取它的方式是耦合的,比如上面这个例子中的API就可以称之为“book端点”(book endpoint)。

- 阅读剩余部分 -

通常使用Golangencoding/json标准库可以方便的编码/解析JSON数据,但是前提需要定义struct数据结构。特别是解析未知结构的JSON数据时,原有方法很难满足需求了,本文主要介绍动态解析JSON格式。

1、传统方法
比如User数据结构如下:

type User struct {
    Name string `json:"name"`
    Age int     `json:"age"`
}

在定义struct字段的时候,可以在字段后面添加tag,来控制encode/decode的过程:是否要 decode/encode 某个字段,JSON 中的字段名称是什么。字段名首字母控制字段的可见性,若要输出到JSON,首字母需要大写。
三种tag:
-:不要解析这个字段
omitempty:当字段为空(默认值)时,不要解析这个字段。比如 false、0、nil、长度为 0 的 array,map,slice,string
FieldName:当解析 json 的时候,使用这个名字

举例来说吧:

// 解析的时候忽略该字段。默认情况下会解析这个字段,因为它是大写字母开头的
Field int   `json:"-"`
// 解析(encode/decode) 的时候,使用 `other_name`,而不是 `Field`
Field int   `json:"other_name"`
// 解析的时候使用 `other_name`,如果struct 中这个值为空,就忽略它
Field int   `json:"other_name,omitempty"`

- 阅读剩余部分 -

Hadoop streaming是和hadoop一起发布的实用程序,它允许用户创建和执行使用任何程序或者脚本编写的map或者reduce的mapreducejobs。如果你不熟悉Java或者C++,你可以使用Python/shell/PHP标准输入输出流非常方便的创建Hadoop任务。

1、Streaming原理
Hadoop Streaming 使用了 Unix 的标准输入输出作为 Hadoop 和其他编程语言的开发接口,因此在其他的编程语言所写的程序中,只需要将标准输入作为程序的输入,将标准输出作为程序的输出就可以了。mapper和 reducer都是能够从stdin逐行(line by line)读取输入的可执行文件,然后把处理完的结果发送到stdout。这个实用工具将会创建 一个Map/Reduce作业,并将作业提交到适当的集群,监控作业的运行进度直到作业运行完成。用法如下:

Usage: $HADOOP_HOME/bin/hadoop jar \
$HADOOP_HOME/contrib/streaming/hadoop-*-streaming.jar [options]
options:
(1)-input:输入文件路径
(2)-output:输出文件路径
(3)-mapper:用户自己写的mapper程序,可以是可执行文件或者脚本
(4)-reducer:用户自己写的reducer程序,可以是可执行文件或者脚本
(5)-file:打包文件到提交的作业中,可以是mapper或者reducer要用的输入文件,如配置文件,字典等。
(8)-D:作业的一些属性(以前用的是-jonconf)

2、Python Mapper
相必大家对Hadoop原理已经了解了,下面以Python为例编写streaming MR任务。Mapper主要功能是解析原始数据,输出预期的Key=>value,在Python里面直接print即可。

import json
import sys

# 从标准输入中读数据
for line in sys.stdin:
    if not line:
        continue
    val = line.strip()
    if val:
        testlist= val.split("\t")
        if len(testlist) == 3:
            detail = {
                'v1': testlist[0],
                'v2': int(testlist[1])
            }
            # 标准输出数据
            print "%s\t%s" % (testlist[0], json.dumps(detail))

- 阅读剩余部分 -

Hadoop pipes 是 Hadoop MapReduce 的 C++ 的接口代称。不同于使用标准输入和输出来实现 map 代码和reduce 代码之间的 Streaming。Pipes 使用套接字 socket 作为 tasktracker 与 C++版本函数的进程间的通讯,与 Streaming 不同,Pipes 是 Socket 通讯,Streaming 是标准输入输出。

1、Pipes架构设计
Hadoop-pipes-arch.jpg

Hadoop pipes允许用户用C++编写五个基本组件:mapper,reducer,partitioner,combiner,recordReader,这五个组件可以是Java编写的,也可以是C++编写的。

2、Mapper编写

#include "Pipes.hh"
#include "TemplateFactory.hh"
#include "StringUtils.hh"

class UBCMapper : public HadoopPipes::Mapper {
public:
    UBCMapper(HadoopPipes::TaskContext &context) {
    }
    void map(HadoopPipes::MapContext &context) {
        // 获取input中的一行数据
        std::string line = context.getInputValue();
        std::vector<std::string> split = HadoopUtils::splitString(line, "\t");
        if (split.size() == 8) {
            // 转化成新的keys-value
            context.emit(split[3], split[2]);
        }
    }
};

HadoopUtils提供了丰富的工具函数,包括字符串分割、字符串数值互转等。这里需要注意的是,字符串数值互转只支持32位长度,如果数值字符串长度较长,使用HadoopUtils转换会溢出,推荐使用boost将字符转为int64_t:

// string=>uint64_t
boost::lexical_cast<uint64_t>(str)
// uint64_t=>string
 std::stringstream ss;
ss << count;
std::cout<<ss.str();

- 阅读剩余部分 -