nodejs 的一次 apache benchmarking 测试

和php的select模型存在同样的问题,每秒并发数量500直接被reset掉。时间长了,很多连接并不会到达。

var express = require('express')
var app = express()

// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
  res.send('hello world')
})

app.listen(8000)
$ ab -c 100 -n 5000 http://127.0.0.1:8000/
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        
Server Hostname:        127.0.0.1
Server Port:            8000

Document Path:          /
Document Length:        11 bytes

Concurrency Level:      100
Time taken for tests:   22.598 seconds
Complete requests:      5000
Failed requests:        0
Total transferred:      1050000 bytes
HTML transferred:       55000 bytes
Requests per second:    221.26 [#/sec] (mean)
Time per request:       451.961 [ms] (mean)
Time per request:       4.520 [ms] (mean, across all concurrent requests)
Transfer rate:          45.38 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0  438 2755.9      0   19671
Processing:     0   14   3.2     15      19
Waiting:        0   14   3.2     15      19
Total:          0  452 2755.4     15   19687

Percentage of the requests served within a certain time (ms)
  50%     15
  66%     16
  75%     17
  80%     17
  90%     18
  95%   1004
  98%  19669
  99%  19679
 100%  19687 (longest request)
$ ab -c 500 -n 5000 http://127.0.0.1:8000/
This is ApacheBench, Version 2.3 <$Revision: 1807734 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
apr_socket_recv: Connection reset by peer (54)
Total of 4734 requests completed

Table object

表对象映射到数据库表的属性,常见的有值类型的映射,如属性id映射到表的主键id。

或者表的外键关系映射tag_id映射到tag表。但在一部分的场景里面,我们查询表数据时并不会连表查询。

/**
 * @Transfer(Resource)
 */
class ResourceTableObject
{
    /**
     * @var integer
     * @Transfer('tag', 'id')
    */
    private $tagID;

/**
* @var UserTableObject
*/
private $owner;
}

Resource表数据

.

id owner_id
1 10

使用装配器装配

/**
 * @var ResourceTableObject $resourceObject
 */
$resourceObject = $assembler->assemble($tableHashObject, ResourceTableObject::class);

节省了装配器代码

class ResourceTableObjectAssembler
{
    public function assemble($tableHashObject)
    {
        $resourceTableObject = new ResourceTableObjectAssembler();
        $resourceTableObject->setID((int)$tableHashObject['id']);
        $resourceTableObject->setTagID((int)$tableHashObject['tag_id']);
        // 现实中肯定还有若干行字段的装配
        return $resourceTableObject;
    }
}

表对象生成器

通过表的结构直接生成PHP的代码(类)。

上面的表解构对应的JSON结构

{
"tag_id": 1,
"owner": {
"id": 10,
"name": "cj"
}
}

在数据库中可能存在用表来保存一个树形关系,此时需要装配器可以自行递归出表对象。

class TagTableObject
{
/**
* @var TagTableObject
*/
private $parent;
}

装配器检查到表对象的某个属性类型为自己的时候。如果装配的并且是一个集合,那么尝试递归出整棵树。

State object transfer

不同层级间对象转换通常需要Assembler完成此类工作,如应用层DTO对象和领域模型的转换。或者是表对象到领域模型的准换。

/**
 * @Transfer(Resource)
 */
class ResourceDTO
{
    /**
     * @Transfer('tag', 'id')
     */
    private $tagID;
}

class Tag
{
    private $id;
}

class Resource
{
    /**
     * @var Tag $tag
     */
    private $tag;
}

使用

$resource = $transfer->to($resource, ResourceDTO::class);

ResourceTableObject

$resource = $transfer->to(ResourceDTO::class, $resourceTableObject);

使用异常而不是字符串,拒绝使用字符串传递状态。

最近在Code Review的时候发线一个现状。在开发接口时需要给client返回业务状态,我们通常需要一个message描述这个状态的文字。

例如,一个常见的HTTP接口请求

POST /plan HTTP/1.1
HOST t-plain-api.gaodun.com
User-Agent: curl/7.47.0

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status": 1,
    "message": "此标杆计划已存在!",
    "result": {}
}

这是我们期望看到的一个状态响应。

但是我却在一段代码中看到了如下的响应。

POST /plan HTTP/1.1
HOST t-plain-api.gaodun.com
User-Agent: curl/7.47.0

HTTP/1.1 200 OK
Content-Type: application/json

{
    "status": 0,
    "info": "",
    "result": {
        "data": "此标杆计划已存在!"
    }
}

正确的做法应该是抛出一个异常,Http的中间件检查到异常后输出正确的json。看具体的PlanDAO这段代码。

class PlanDAO
{
    public function addPlan($params)
    {
        $res = $this->planDAO->getPlanByName($params);
        if ($res) return '此标杆计划已存在!';
        $res = $this->planDAO->addPlan($params);
        $res ? $res = 1 : $res = 0;
        return $res;
    }
}

addPlan返回一个string作为业务的状态描述,正确的做法如下:

定义一个异常,设置异常信息和状态码。我把状态码设置成1.非0表示异常。status = Exception::code,而message = Exception::message

class PlanNotExists extends \Exception
{
    /**
     * @var Plan
     */
    private $plan;

    public function __constructor($plan)
    {
        parent::__constructor("Plan {$plain->id()} has exists", 1);
        $this->plan = $plan;
    }

    public function plan()
    {
        return $this->plan;
    }
}

class PlanService
{
    public function addPlan($params)
    {
        $this->planDAO->addPlan($params);
    }
}

抛出异常。

class PlanDBALDAO
{
    public function addPlan($params)
    {
        $plan = $this->getPlanByName($params);
        if (!$plan) {
            throw new PlanNotExists($plan);
        }
        ... # database insert statement
    }
}

什么是inbound营销?

https://www.hubspot.com/inbound-marketing

和国内常说的内容营销很像。作者把inbound营销分位四个步骤。吸引、转换、成交和成就他人。

发布内容吸引内容的听众,和他们交流转换成潜在用户,成交的目的是你的产品真的能帮到客户。

作者给出了这举个步骤的方法,可以参考。

https://medium.com/@laurenholliday_/build-an-empire-11-side-projects-to-generate-revenue-for-your-business-ea0bdaabd826

这篇文章介绍用业余项目吸引客户,和内容营销一样的策略。内容提供知识的价值,而业余项目解决具体的问题。

里面提到免费,这就是我们常说的入口。

VIM Dev

plug

  1. Install
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
  1. Config
" ~/.vimrc
" Specify a directory for plugins
" - For Neovim: ~/.local/share/nvim/plugged
" - Avoid using standard Vim directory names like 'plugin'
call plug#begin('~/.vim/plugged')

" Make sure you use single quotes

" Shorthand notation; fetches https://github.com/junegunn/vim-easy-align
Plug 'junegunn/vim-easy-align'

" Any valid git URL is allowed
Plug 'https://github.com/junegunn/vim-github-dashboard.git'

" Multiple Plug commands can be written in a single line using | separators
Plug 'SirVer/ultisnips' | Plug 'honza/vim-snippets'

" On-demand loading
Plug 'scrooloose/nerdtree', { 'on':  'NERDTreeToggle' }
Plug 'tpope/vim-fireplace', { 'for': 'clojure' }

" Using a non-master branch
Plug 'rdnetto/YCM-Generator', { 'branch': 'stable' }

" Using a tagged release; wildcard allowed (requires git 1.9.2 or above)
Plug 'fatih/vim-go', { 'tag': '*' }

" Plugin options
Plug 'nsf/gocode', { 'tag': 'v.20150303', 'rtp': 'vim' }

" Plugin outside ~/.vim/plugged with post-update hook
Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' }

" Unmanaged plugin (manually installed and updated)
Plug '~/my-prototype-plugin'

" Initialize plugin system
call plug#end()

vim.php

call plug#begin('~/.vim/plugged')


Plug 'StanAngeloff/php.vim'

call plug#end()

function! PhpSyntaxOverride()
  " Put snippet overrides in this function.
  hi! link phpDocTags phpDefine
  hi! link phpDocParam phpType
endfunction

augroup phpSyntaxOverride
  autocmd!
  autocmd FileType php call PhpSyntaxOverride()
augroup END

CommandT

  1. Install
$ apt install vim-nox ruby-dev
$ cd ~/.vim/bundle/command-t
$ rake make
  1. Map shortcut
// vim ~/.vimrc

map <C-P> :CommandT<CR>