服务端P75 P99是什么意思?

February 22nd, 2019 by JasonLe's Tech 59 views

服务端的监控有很多指标,常见的QPS和延时,通常延时我们会用p95、p99来标记。很多工作多年的服务端工程师平常会查看延时,但是这两个指标是什么意思都不清楚。

首先,这是一个统计的概念。如果知道某数在一个有序排列的集合中,处于什么位置,我们就对整个数据集合就有了概念。 首先,它衡量的是一组数据,对于某个接口,准确统计它的流量时非常有用,它可以取出一些偶然得到的异常值。 99th百分点是统计时所采用的最高值,超过的1%的数据将被舍弃。
这样可以将瞬间的毛刺(尖峰)去掉,使统计平均更具真实意义。

在服务端中,假如1min中采集了1000词请求的延时,那么99标记的就是降序排完之后990个的延时,这样极端的个别lantency异常的点就不会被统计到,从而让统计更加直观。

curl分析请求耗时

December 12th, 2018 by JasonLe's Tech 53 views

最近在和一个厂商对接,某个请求的响应特别慢,因此我就希望有一种方法能够分析到底请求的哪一步耗时比较长,好进一步找到问题的原因。在网络上搜索了一下,发现了一个非常好用的方法, curl 命令就能帮你分析请求的各个部分耗时。

curl 命令提供了 -w 参数,这个参数在 manpage 是这样解释的:

-w, –write-out
Make curl display information on stdout after a completed transfer. The format is a string that may contain plain text mixed with any number of variables. The format
can be specified as a literal “string”, or you can have curl read the format from a file with “@filename” and to tell curl to read the format from stdin you write
“@-“.
The variables present in the output format will be substituted by the value or text that curl thinks fit, as described below. All variables are specified as %{vari‐
able_name} and to output a normal % you just write them as %%. You can output a newline by using \n, a carriage return with \r and a tab space with \t.

它能够按照指定的格式打印某些信息,里面可以使用某些特定的变量,而且支持 \n 、 \t 和 \r 转义字符。提供的变量很多,比如 status_code 、 local_port 、 size_download 等等,这篇文章我们只关注和请求时间有关的变量(以 time_ 开头的变量)。

先往文本文件 curl-format.txt 写入下面的内容:

time_namelookup: %{time_namelookup}\n
time_connect: %{time_connect}\n
time_appconnect: %{time_appconnect}\n
time_redirect: %{time_redirect}\n
time_pretransfer: %{time_pretransfer}\n
time_starttransfer: %{time_starttransfer}\n
———-\n
time_total: %{time_total}\n

time_namelookup :DNS 域名解析的时候,就是把 https://zhihu.com 转换成 ip 地址的过程
time_connect :TCP 连接建立的时间,就是三次握手的时间
time_appconnect :SSL/SSH 等上层协议建立连接的时间,比如 connect/handshake 的时间
time_redirect :从开始到最后一个请求事务的时间
time_pretransfer :从请求开始到响应开始传输的时间
time_starttransfer :从请求开始到第一个字节将要传输的时间
time_total :这次请求花费的全部时间

curl -w “@curl-format.txt” -o /dev/null -s -L “https://business.smartcamera.api.io.mi.com/common/device/preUpload”
time_namelookup: 0.004
time_connect: 0.016
time_appconnect: 0.151
time_redirect: 0.000
time_pretransfer: 0.151
time_starttransfer: 0.157
———-
time_total: 0.157
%

可以看到 time_appconnect 和 time_redirect 都不是 0 了,其中 SSL 协议处理时间为 0.151s-0.016s=135ms 。

 

http://man.linuxde.net/curl

Java Executor 框架使用的思考

April 18th, 2018 by JasonLe's Tech 90 views

最近在使用Java的线程池做一些线程频繁创建销毁的事情,我的目标是利用已有的云Queue服务,将我们的服务接入这个Queue,目前我们使用的是一个基于内存Queue的异步队列框架,这就导致当进程因为某种原因导致崩溃后,内存Queue中的数据全部丢失,这是我们所不能容忍的。

因此我们在云Queue上创建两个Queue,在处理高峰时刻将一部分数据导入backup queue中,当主 queue中的Message消费完毕后,主动去消费backup queue 中的数据。开始我使用一种按需分配的方式,即先使用Excutor创建一个pool后,只有一个线程去轮询主Queue中是否有数据,云Queue中有数据就创建一个线程加入到pool中执行,最后自到触发RejectedExecutionException,这个时候我将主Queue中的数据放到backup Queue中执行。

另外一种比较土的方案就是Server一启动,直接启动400个线程加入到pool中执行业务逻辑,长轮询主Queue是否有数据,有就去消费,没有就去消费Backup Queue中的数据。

从直觉上,大家都会认为第一种方案资源消耗会更优,但是在实际的工程实现中,必然存在设计和实现的折中,我的每个task执行逻辑会有多大五六个返回值,有的返回值需要将消息推送给用户,如果我按照第一个方案去做,那么必然有一刻处理峰值,会将大量的Message都导入backup Queue,只要进入backup Queue中后,就无法给用户实时推送消息,换句话说,消息就丢失了,虽然我很想用第一种方案去实现复杂逻辑,但是在实际的实现中会导致我的backup queue会分成很多种情况,这就导致实现复杂度和queue的复杂度提升更快。经过半个月的讨论,我终于敲定使用简单粗暴的方式去实现我的功能。

以上是我的一个吐槽。

 

https://blog.csdn.net/pfnie/article/details/52755769

Python 调用 C++ DLL

January 28th, 2018 by JasonLe's Tech 96 views

update 2018-2-4

早些时候用Pyqt写了一个app,最近需要加入一个License的认证模块,然而这个认证模块是拿C++写的,并只给出了dll、lib和header。。。网上查找了一些资料,一种方法是采用ctypes 去加载dll;另外一种就是编写一个PyObject 然后编译成pyd模块供python调用。

这里我采用第一种方式,他们提供给我的模块是_stdll的方式,因为然后我又把这个dll包了一层,以__cdecl方式导出。

#ifdef LICENSEDLL_EXPORTS
#define LICENSEDLL_API extern "C" __declspec(dllexport)
#else
#define LICENSEDLL_API __declspec(dllimport)
#endif

LICENSEDLL_API bool  verify(char *module, char *errorMsg);
.....

然后我将这个工程编译生成LicenseDLL.dll。下面就是python方面的任务了,我引入ctypes,然后利用CDLL加载LicenseDLL.dll,利用dll.verify.argtypes,我声明verify函数具有两个参数,其中pinfo为100个字节的数组,并将其传入verify函数。该函数返回后,写入ErrorInfo,使用QString(str(ErrorInfo.raw))将其转换成QString对象,完成对于dll的调用。

def CheckLicenseInfo():
    from ctypes import *
    dll = CDLL(app.g_pwd + os.sep + 'LicenseDLL.dll')
    dll.verify.argtypes = [c_char_p,c_char_p]
    pinfo = c_char * 100
    ErrorInfo = pinfo()

    if not dll.verify('DESIGNAPP', ErrorInfo):
        QMessageBox.critical(None, 'Error', QString(str(ErrorInfo.raw)))
        exit()

但是这种方式有个最大的问题:由于直接读dll,可能会存在dll依赖链的问题,比如我操作的时候就遇到Windows Error 126和127的问题,126应该就是dll依赖缺失,需要使用depandency的问题,127有可能是dll本身的问题,没有把里面的函数符号导出。127比较复杂,有时候遇到挺懵逼的….. 

编写python扩展也是一种可行的方式,可以编译成pyd的形式,在python下直接import。因为python要调用C++的模块,那么必然涉及到python对象向C++传值和传地址的问题,也涉及到C++返回值向python传值的问题,直接上源码来一步一步说明吧。

编写代码首先要引入Python.h Python.lib 默认用安装包安装的是没有python_d.lib的,当然也要引入需要wapper的头文件,我根据官方提供的spamError例子代码修改为我的代码,总的框架代码有PyMODINIT_FUNC initXXXXX(void),static PyMethodDef LicenseMethods[]={…},还有常规的warpper代码static PyObject *
xxxxxxxxxx(PyObject *self, PyObject *args)。PyMODINIT_FUNC initXXXXX(void)为固定格式,把XXXXX换成我们编写的模块即可。

#include "Python.h"
#include "LicenseProve.h"

static PyObject *LicenseError;

static PyObject *isHavingLicense(PyObject *self, PyObject *args)
{
    const char *name;
	char *err;
    if (!PyArg_ParseTuple(args, "s|s", &name,&err))
        return NULL;

    std::string modelName(name);
    std::string errorMsg(err);

    bool ret = Allone::License::CLicenseProve::getInstance()->isHavingLicense(modelName,errorMsg);

	//if(ret == false)
	//	return Py_False;
    //return Py_True;
    return Py_BuildValue("(iss)",(int)ret,modelName.c_str(),errorMsg.c_str());
}

static PyObject *getLicenseType(PyObject *self, PyObject *args)
{
    size_t ret = Allone::License::CLicenseProve::getInstance()->getLicenseType();
    return PyInt_FromSize_t(ret);
}

static PyObject *getLicenseInfo(PyObject *self, PyObject *args){...}
static PyObject *getRegisterInfo(PyObject *self, PyObject *args){...}
static PyObject *getAppendInfo(PyObject *self, PyObject *args){...}

static PyMethodDef LicenseMethods[] = {
    {"isHavingLicense",  isHavingLicense, METH_VARARGS,"Having License in this Computer?"},
	{"getLicenseType", getLicenseType, METH_NOARGS,"Get License type"},
	{"getLicenseInfo", getLicenseInfo, METH_NOARGS,"Get License Info"},
	{"getRegisterInfo", getRegisterInfo, METH_NOARGS,"Get License Register Info"},
	{"getAppendInfo", getAppendInfo, METH_NOARGS,"Get License Append Info"},
    {NULL, NULL, 0, NULL}        /* Sentinel */
};

PyMODINIT_FUNC initLicense(void)
{
    PyObject *m;

    m = Py_InitModule("License", LicenseMethods);
    if (m == NULL)
        return;

    LicenseError = PyErr_NewException("License.error", NULL, NULL);
    Py_INCREF(LicenseError);
    PyModule_AddObject(m, "error", LicenseError);
}

在LicenseMethods需要加入我们编写的函数,双引号里面的是python调用的函数名,第二个参数是c++函数里面的函数名,第三个是声明传入参数类型,METH_VARARGS为可变参数,METH_NOARGS是没有参数。这个实质上就是一个参数表,连接python和c++端的实现。c++端的实现都写成static PyObject *
xxxxxxxxxx(PyObject *self, PyObject *args),我们需要的参数都是通过args以Tuple传入,调用PyArg_ParseTuple(args, “s|s”, &name,&err),可以拿到传入的值,”s|s”表示元祖里每个参数的类型,常用类型可以在官方wiki中找到,s就是代表string,i就是代表integer等等。后面就是根据指定的类型,传入到name和error中,C++中可以使用参数传值,但是python的参数传入的都是值,因此我将修改的的值都放置到一个tuple中返回给python,也就是使用Py_BuildValue返回,参数和PyArg_ParseTuple类似,第一个是每个参数的类型,后面是参数值。如果返回值简单,我们也可以直接返回Py_False等。

在PyMODINIT_FUNC initLicense(void)中,大致也分为Py_InitModule初始化,PyErr_NewException注册,在异常处理这一块,我没有细看,但是这个应该是处理各种C++发生的异常,由于python不用管理内存,但是在C++中我们就必须处理,因此Py_INCREF用来管理引用计数,最后还需要将error和注册的python模块连接起来。

在python中,只需要使用import License,直接引入即可,看下面代码:

def CheckLicenseInfo2():
    error = ""
    # print(License.isHavingLicense('DESIGNAPP',error))
    ret = License.isHavingLicense('DESIGNAPP',error)
    err = ret[2]
    if not ret[0]:
        QMessageBox.critical(None, 'Error', QString(str(err)))
        exit()

    print(License.getLicenseType())
    print(License.getLicenseInfo())
    print(License.getRegisterInfo())
    print(License.getAppendInfo())

由于isHavingLicense返回的是tuple,因此我们只要指明索引,就可以找到需要的内容。更多的参数类型可以查看C:\Python27\include,下面都是各种的传入传出函数和宏,满足我们的需要。

这种pyd的方式实质上也是dll,但是比直接调用ctype读取dll有更好的兼容性,在实际生产中推荐使用pyd调用C++函数函数。

 

参考:

http://www.cnblogs.com/night-ride-depart/p/4907613.html

https://docs.python.org/2/library/ctypes.html

http://wolfprojects.altervista.org/dllforpyinc.php

http://icejoywoo.github.io/2015/10/30/intro-of-extending-cpython.html

https://docs.python.org/2/c-api/arg.html

https://docs.python.org/2/extending/extending.html?highlight=meth_varargs

http://blog.csdn.net/mkc1989/article/details/38943927

golang ide环境配置

January 20th, 2018 by JasonLe's Tech 90 views

最近golang和区块链火起来了,觉得自己有必要也跟一次风了。

golang的背景啊,优缺点啊就不讲了,网上大把资料,只要有C/C++的基础,这个golang程序猿就可以快速胜任。我就说一下配置过程和调试过程中的坑。golang的安装非常简单,只需要将其解压到制定目录,设置好GOROOT和GOPATH即可,GOROOT是golang的可执行文件,而GOPATH从我理解就是Go Project的所在目录,我们平时go get所下载的工程都会放到$GOPATH/src中。

由于刚开始接触,我手动设置了go编程环境,也使用go run 运行了hello world,但是一直无法调试go程序,在网上找了半天debugger,发现delver可以用来调试go程序,我习惯使用git+make install安装程序,安装完毕之后,我将其安装到go所在目录,方便直接命令行调用。

spider@ubuntu:/usr/lib/go-1.9.2$ ls
api  AUTHORS  bin  blog  CONTRIBUTING.md  CONTRIBUTORS  doc  favicon.ico  lib  LICENSE  misc  PATENTS  pkg  README.md  robots.txt  src  test  VERSION
spider@ubuntu:/usr/lib/go-1.9.2$ cd bin/
spider@ubuntu:/usr/lib/go-1.9.2/bin$ ls
dlv  go  godoc  gofmt

只要用过gdb的童靴,这个就非常简单了,比如break,continue,print 等,如下所示:

spider@ubuntu:/tmp$ dlv debug test.go
Type 'help' for list of commands.
(dlv) l
> _rt0_amd64_linux() /usr/lib/go-1.9.2/src/runtime/rt0_linux_amd64.s:8 (PC: 0x4571b0)
Warning: debugging optimized function
     3:	// license that can be found in the LICENSE file.
     4:	
     5:	#include "textflag.h"
     6:	
     7:	TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
=>   8:		LEAQ	8(SP), SI // argv
     9:		MOVQ	0(SP), DI // argc
    10:		MOVQ	$main(SB), AX
    11:		JMP	AX
    12:	
    13:	// When building with -buildmode=c-shared, this symbol is called when the shared
(dlv) b main.main
Breakpoint 1 set at 0x4a08d8 for main.main() ./test.go:12
(dlv) c
> main.main() ./test.go:12 (hits goroutine(1):1 total:1) (PC: 0x4a08d8)
Warning: debugging optimized function
     7:	   return n
     8:	  }
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
=>  12:	func main() {
    13:	    var i int
    14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) n > main.main() ./test.go:13 (PC: 0x4a08ef)
Warning: debugging optimized function
     8:	  }
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
    12:	func main() {
=>  13:	    var i int
    14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) > main.main() ./test.go:14 (PC: 0x4a08f8)
Warning: debugging optimized function
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
=>  14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) > main.main() ./test.go:15 (PC: 0x4a0913)
Warning: debugging optimized function
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
    14:	    for i = 0; i < 10; i++ { =>  15:	       fmt.Printf("%d\t", fibonacci(i))
    16:	    }
    17:	}
(dlv) p i
0
(dlv) n
0	> main.main() ./test.go:14 (PC: 0x4a0a02)
Warning: debugging optimized function
     9:	  return fibonacci(n-2) + fibonacci(n-1)
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
=>  14:	    for i = 0; i < 10; i++ { 15: fmt.Printf("%d\t", fibonacci(i)) 16: } 17: } (dlv) > main.main() ./test.go:15 (PC: 0x4a0913)
Warning: debugging optimized function
    10:	}
    11:	
    12:	func main() {
    13:	    var i int
    14:	    for i = 0; i < 10; i++ { =>  15:	       fmt.Printf("%d\t", fibonacci(i))
    16:	    }
    17:	}
(dlv) p i
1

另外我们也可以使用dlv attach pid的方式进行调试,当然我们必须开启/proc/sys/kernel/yama/ptrace_scope to 0。然而我gdb、dlv再怎么强大,还是没有IDE方便,尤其是断点调试,经过调研VSCode、Gogland、Atom满足我们需求,由于之前我一直是用JetBrain系产品进行开发,毫无疑问,我是用Gogland作为IDE。我着重说一下他的调试,当debug某个程序时候,Gogland会打印信息:

GOROOT=/usr/lib/go-1.9.2 #gosetup
GOPATH=/home/spider/go #gosetup
/usr/lib/go-1.9.2/bin/go build -o /tmp/___go_build_main_go -gcflags "-N -l" -a /home/spider/go/src/awesomeProject/main.go #gosetup
/opt/GoLand/plugins/intellij-go-plugin/lib/dlv/linux/dlv --listen=localhost:41151 --headless=true --api-version=2 --backend=default exec /tmp/___go_build_main_go -- #gosetup

可以看到第一行、第二行是打印GOROOT和GOPATH,第三行是打印进行编译,添加-gcflags “-N -l”,是为了去掉编译优化,方便调试,这行执行完毕会把程序放到/tmp/___go_build_main_go中,第四行就是使用gogland中的dlv插件的debug server,后端就是我们编译的/tmp/___go_build_main_go程序。

 

参考

https://golang.org/doc/editors.html