一次由于curl调用https接口导致的502

背景描述

最近在调研百度人脸识别的服务,百度的人脸识别是免费的,但是有QPS的限制,QPS免费的最大值是5,也就是峰值在每秒5次,都是可以免费使用的。我从百度平台下载的SDK,但是到了第一步就被卡主了。

当我使用检测接口的时候,频繁出现 502。在之前,本地运行PHP的时候,也会偶尔出现502,但是并没有这么高。这次变态的浮现率是100%.

查了下nginx日志*173 upstream prematurely closed connection while reading response header from upstream, client: 127.0.0.1, server: sdeno-api, request: "GET /facev1/test HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000"

在网上查了一下,说什么的都有,大多数是在说nginx的问题。没有一个能够解决问题。

发现问题

我发现,出现http 502 的接口使用了curl,所以很可能是curl代码有问题。我自己有写了一段代码,调用百度首页http://www.baidu.com,咦?正常返回啊。完全没有问题。后来百度查了很多,最终发现一个不起眼的问题 php curl 调用https出现502问题。我验证了一些,我曹,果然只有https接口是不正常的。

解决办法

后来找到了一个靠谱的方法,那就是使用sudo重启php-fpm.

$ brew services stop php53
$ sudo brew services start php53

其实除了这样,还可以考虑下重装或者升级PHP的版本。我把本地的php5.3卸载了,重新安装了php5.6也解决了问题。

$ brew uninstall php53
$ brew install php56

用 iPhone 轻松下载微博小视频

我经常喜欢看一些比较污的视频,这些视频基本上来自于微博。iPhone和Android有一个很大的不同,就是下载视频比较难。之前使用Pro recorder,但是这个APP有数量限制,一旦到了规定的数量,需要购买会员。下面和大家分享一下如何使用iPhone下载微博上面的小视频另一种方法。

下载Wordflow

打开App Store,搜索workflow,并进行下载

下载workflow插件

使用iPhone的自带浏览器Safari 打开连接 http://workflow.sspai.com/#/main/workflow ,然后找到插件,进行下载

点击获取该workflow,这个时候会询问是否打开workflow,然后选择确认。

选择get workflow 进行安装。

下载小视频准备

打开微博你想要下载的小视频,拷贝连接

返回workflow的主界面,点击刚刚下载的插件。

这个时候,复制的链接已经自动粘贴到了workflow里面。

开始下载

点击开始进行下载,这个时候会有一系列的操作自动运行。

选择Save Video进行保存操作。

查看视频

进行iPhone相册,查看下载的小视频。

workflow中文推荐站

PHP设计模式-原型模式

原型模式

原型设计模式是通过使用一种克隆技术复制实例化的对象。新对象是通过复制原型而进行创建的。原型设计模式的目的是通过克隆以减少实例化对象的开销。

何时使用原型模式

原型模式要求你创建某个原型对象的多个实例,这个时候就可以使用原型模式。比如,在关于进化的研究,通常使用果蝇作为研究对象。果蝇很快繁殖,基本上一个小时就能进行产卵,和其他的生物相比,找到和记录变异的几率更大。如果换做大象的话(比如大象的孕育期长达22个月),那么整个研究过程将会消耗很长的时间。因此,只需要完成两个实例化(一个雄性和一个雌性),然后就可以跟进克隆多个变异,而不需要由具体类另外创建实例。

克隆函数

使用原型模式,首先就要了解如何使用PHP的__clone()。

abstract class CloneMe {
    public $name;
    abstract function __clone();
}

class Person extends CloneMe {

    public function __construct() {
        $this->name = "Original";
        echo 'hello';
    }

    public function display() {
        echo "\n$this->name\n";
    }

    public function __clone() {}

}
$worker = new Person();
$worker->display();

$slacker = clone $worker;
$slacker->display();

定义了一个抽象类CloneMe,然后定义一个Person类进行实现。定义一个变量$worker进行实例化,然后使用关键字clone进行对象的克隆。

输出结果:
hello
Original

Original

不过需要注意的是,__clone()不能直接调用,会出现报错Fatal error: Cannot call __clone() method on objects - use 'clone $obj' instead

克隆不会启动构造函数

上面的输出结果可能已经见到了,clone是不会启动构造函数的。其实这个也是比较理解的。举个例子,现在有一个人A,那么这个人肯定会有手脚,那么手脚就算是默认构造函数进行的初始化,如果要根据A克隆一个人B,那么B不用再造手脚,而是克隆之后,就自带了A的手脚,当然,你也可以把B的手脚砍掉(比如$slacker->name=”Tyler Teng”)。

abstract class CloneMe {
    public $name;
    abstract function __clone();
}

class Person extends CloneMe {

    public function __construct() {
        $this->name = "Original";
        echo 'hello';
    }

    public function display() {
        echo "\n$this->name\n";
    }

    public function __clone() {
    }

}
$worker = new Person();
$worker->display();

$slacker = clone $worker;
$slacker->name = "Tyler Teng";
$slacker->display();

输出结果
hello
Original

Tyler Teng

研究果蝇

抽象类和具体实现

简单定义三个属性:眼睛的颜色,翅膀震动次数,眼睛个数

abstract class IPrototype {
    public $eyeColor;
    public $wingBeat;
    public $uniyEyes;
    abstract function __clone();
}

除了一些基本的属性,还需要区别雄性和雌性

include_once 'IPrototype.php';
class FemaleProto extends IProtoType {

    const gender = "FEMALE";
    public $fecundity;

    public function __construct() {
        $this->eyeColor = "red";
        $this->wingBeat = "220";
        $this->unitEyes = "760";
    }

    public function __clone() {}

}
include_once "IPrototype.php";
class MaleProto extends IPrototype {

    const gender = "MALE";

    public $mated;

    public function __construct() {
        $this->eyeColor = "red";
        $this->wingBeat = "220";
        $this->unitEyes = "760";
    }

    public function __clone() {}

}
客户端

我们定义一个Client类,首先从具体类中实例化$fly1和$fly2,$c1Fly、$c2Fly和$updateCloneFly则分别是这两个类实例的克隆

function __autoload($class_name) {
    include_once realpath(__DIR__) . '/' .  $class_name . '.php';
}
class Client {
    private $fly1;
    private $fly2;

    private $c1Fly;
    private $c2Fly;
    private $upDatadCloneFly;

    public function __construct() {
        $this->fly1 = new MaleProto();
        $this->fly2 = new FemaleProto();

        $this->c1Fly = clone $this->fly1;
        $this->c2Fly = clone $this->fly2;

        $this->upDatadCloneFly = clone $this->fly2;
        $this->c1Fly->mated = "true";
        $this->c2Fly->fecundity = "186";
        $this->upDatadCloneFly->eyeColor = "purple";
        $this->upDatadCloneFly->wingBeat = "220";
        $this->upDatadCloneFly->unitEyes = "750";
        $this->upDatadCloneFly->fecundity = "92";

        $this->showFly($this->c1Fly);
        $this->showFly($this->c2Fly);
        $this->showFly($this->upDatadCloneFly);
    }

    public function showFly(IProtoType $fly) {
        echo "Eye color : " . $fly->eyeColor . "\n";
        echo "Wing Beats/second : " . $fly->wingBeat . "\n";
        echo "Eye units : " . $fly->unitEyes . "\n";
        $genderNow = $fly::gender;
        echo "Gender : " . $genderNow . "\n";
        if ($genderNow == "FEMALE") {
            echo "Numbers of eges : " . $fly->fecundity .  "\n";
        } else {
            echo "Mate : " . $fly->mated . "\n";
        }
    }
}

$woker = new Client();

输出
Eye color : red
Wing Beats/second : 220
Eye units : 760
Gender : MALE
Mate : true
Eye color : red
Wing Beats/second : 220
Eye units : 760
Gender : FEMALE
Numbers of eges : 186
Eye color : purple
Wing Beats/second : 220
Eye units : 750
Gender : FEMALE
Numbers of eges : 92

总结

作为被克隆的类,默认构造函数不应该做太多的初始化,否则结果往往不灵活,而且是过度的耦合设计。构造函数不应完成具体的工作,一种做法是忽略模式类中的构造函数,除非你有充分的理由包含这些构造函数;另外一种做法是,允许在需要的时间调用,让客户端负责实例化和克隆的有关事务。

参考文献

  • Learning PHP设计模式

附件

PHP7内存管理之垃圾回收

回收过程

在自动GC机制中,在zval断开value指向的时候如果发现refcount=0的时候,则会直接释放value,这就是自动回收GC的过程。发生断开的两种情况为修改变量与函数返回的时候,修改变量的时候,会断开原有的value指向,函数返回的时候,则会释放局部变量,也就是把所有局部变量的refcount计数-1。
此外,当使用unset函数的时候,也会主动销毁这个变量。

垃圾回收

虽然有了自动GC机制,但是有一种情况是没办法解决的,那就是因为变量因为循环引用而无法回收造成的内存泄露,这种情况通常是循环引用。简单来讲,循环引用就是引用自身,这种情况一般只会发生在数组或者对象的身上。比如定义了$a = array() ,插入一个新元素,这个元素对数组自身进行引用$a[] = &$a,当所有的外部引用都断开了,但是数据的refcount仍然大于0而得不到释放,但是事实上,这个变量没有在使用的价值了。

<?php
$a = array();
$a[] = &$a;
unset($a);
?>

在unset之前,变量a是有两次引用的,一个来自$a,一个来自$a[1]

unset($a)之后,减少了一次引用的recount,这个时候,已经没有了外部的引用,但是还有一个内部还有一个元素指向该引用。

像这种因为循环指向没办法释放的变量称之为垃圾。PHP引入了另外的一种机制来进行垃圾回收。
– 如果一个变量的value的refcount减少到0,说明这个value可以释放,那么这就不属于垃圾
– 如果一个变量的value减少之后大于0,那么这个value还不能被释放,那么这个value就是垃圾。
所以,判断一个变量是不是垃圾,要看value的refcount是否减少到了0。

目前垃圾回收只会出现在array和object两种类型中,当一个value被视为垃圾的时候,PHP会将这个value收集起来,等到打到了规定的数量,启动垃圾回收机制,进行统一的释放。

回收的时机

前面说了,PHP垃圾回收并不是产生一个垃圾value,就进行释放,而是把value收集起来统一释放,以为value的分析和释放,也是有性能消耗的。
在php.ini中,zend.enable_gc用来设置是否启动垃圾回收机制。绝大多数都是默认开启的,因为每个都有可能在写程序的时候,出现内存垃圾,如果把这个配置关闭了,那么就有可能造成所谓的垃圾泄露。
除了zend.enable_gc以为,还会配合zend/zend_gc.c里面的变量GC_ROOT_BUFFER_MAX_ENTRIES实现垃圾回收,默认GC_ROOT_BUFFER_MAX_ENTRIES的值是10001,GC_ROOT_BUFFER_MAX_ENTRIES[0]是用来保存一些header的数据,GC_ROOT_BUFFER_MAX_ENTRIES[1]~GC_ROOT_BUFFER_MAX_ENTRIES[10000]用来收集垃圾的数据。如果你想强制执行垃圾回收,也可使用函数gc_collect_cycles()实现。

参考文献

  • PHP7内核剖析
  • PHP手册

关于limit_req和limit_conn的区别

request和connection区别

在nginx里面,limit_req和limit_conn都是用来限流的但是两者不在一个层次上,在此之前,需要先清楚request和connect的区别。
request是请求,是http层面的。connection是链接,指的是tcp层面。所以,从含义上面可以知道两者不在一个层次。
我们在打开一个网页的时候,请求过程一般就是先经过tcp三次握手,然后在进行http请求。也就是一个connection建立之后,可以有很多request。

一个connection建立,只要服务器处理的过来,可以处理任意多的request都是没有问题的

好了,现在知道区别了。

limit_conn

http {
    limit_conn_zone $binary_remote_addr zone=one:20m;
    limit_conn one 1; #最多可以进行1个connection
    client_body_buffer_size 256M;
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$request_filename"';
    sendfile        on;

    keepalive_timeout  65;
    include servers/*.conf;
}

上面的配置文件的含义很明白,nginx只接受最多一个connect,我们使用ab命令查看下

$ ab -n10  http://sdeno-api/info/php #默认通过一个connect送10个request

从截图看来,在一个并发下,处理10个request下完全没有问题

接下来我们使用2个并发,2个请求试下,也就是两个用户,每个用户发送一个request

$ ab -n2 -c2 http://sdeno-api/info/php

从截图中可以看到,由于nginx设置了至多限制一个并发,所以导致两个用户的请求只能有一个被处理掉,另外一个返回http 503

limit_req

下面更改下nginx.conf配置文件

http {
    limit_conn_zone $binary_remote_addr zone=one:20m;
    limit_req_zone $binary_remote_addr zone=req_one:20m rate=1r/s;
    limit_conn one 20;
    limit_req zone=req_one burst=5;
    client_body_buffer_size 256M;
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$request_filename"';
    sendfile        on;

    keepalive_timeout  65;
    include servers/*.conf;
}

上面的配置的含义是请求速率限制为1r/s,然后再缓存5个request,然后再依次处理请求(令牌桶算法)

$ ab -n10 -c10 http://sdeno-api/info/php

我们使用压测命令,10个并发,10个请求,根据配置的文件,当有请求过来,nginx先处理一个请求,然后将5个请求缓存下来(burst=5),根据设置的速率1r/s进行处理,也就是一共能够处理6个请求,其余的请求则被丢掉。

接下来我们再继续更改下nginx配置文件

$ ab -n10 -c10 http://sdeno-api/info/php
http {
    limit_conn_zone $binary_remote_addr zone=one:20m;
    limit_req_zone $binary_remote_addr zone=req_one:20m rate=1r/s;
    limit_conn one 20;
    limit_req zone=req_one burst=5 nodelay;
    client_body_buffer_size 256M;
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$request_filename"';
    sendfile        on;

    keepalive_timeout  65;
    include servers/*.conf;
}

咦?增加了nodelay好像和不加有点不同。这是因为请求过来的时候,会爆发出一个峰值的处理能力,处理的总的请求数是burst+1,其余的请求丢弃。

总结

  • request和connect是完全不同层面的含义,一个属于http,一个属于tcp

  • limit_req zone=req_zone burst=5 依照在limti_req_zone中配置的rate来处理请求,同时设置了一个大小为5的缓冲队列,在缓冲队列中的请求会等待慢慢处理,超过了burst缓冲队列长度和rate处理能力的请求被直接丢弃,表现为对收到的请求有延时

  • limit_req zone=req_zone burst=5 nodelay 依照在limti_req_zone中配置的rate来处理请求,同时设置了一个大小为5的缓冲队列,当请求到来时,会爆发出一个峰值处理能力,对于峰值处理数量之外的请求,直接丢弃。

参考文献

使用Nginx实现流量限流

Nginx是高性能的http服务器,同时也可以作为一个反向代理的服务器,甚至还可以作为一个IMAP/pop3/SMTP服务器。Nginx除了负责请求的负载均衡和分发等工作外,自带的限流模块也可以帮助运维人员限制流量的速率。

更改配置

开启Nginx的限流功能

http {
    #定义每个IP的session空间大小
    limit_conn_zone $binary_remote_addr zone=one:20m;
    #与limit_conn_zone类似,定义每个允许发起的请求速率
    limit_req_zone $binary_remote_addr zone=req_one:20m rate=1r/s;
    #定义每个IP发起的并发连接数
    limit_conn one 10;
    #缓存还没来得及处理的请求
    limit_req zone=req_one burst=100;

    #rewrite_log on;
    #error_log /var/log/nginxrewrite.log notice;
        client_body_buffer_size 256M;
        include       mime.types;
        default_type  application/octet-stream;

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$request_filename"';
        sendfile        on;

        keepalive_timeout  65;
        include servers/*.conf;
}

进行验证

我们使用Linux或者mac自带的ab命令进行验证,并且实时查看access.log。在Nginx配置文件中,我们设置的请求速率是每秒1个请求,那我们则需要设置每秒超多1个请求才行

$ ab -n 20 http://sdeno-api/info/php

从截图的日志中,我们可以看出Nginx限流模块确实可以限制请求速率。

其他

由于nginx的版本问题,旧版本会limit_conn_zone->limit_zone

基于以太坊实现局域网多节点挖矿

上一篇文章简要介绍了本地实现私有链挖矿和转账,现在这篇文章主要实现局域网下实现多个节点实现挖矿

前提,已经安装了go-ethereum,如果没有安装请移步基于以太坊创建私有链进行挖矿、交易

机器:Ubuntu(两个节点),Mac(一个节点)

创建创世节点

创建节点json文件

$ mkdir my_eth2
$ cd my_eth2
$ vim genesis.json
{
  "config": {
        "chainId": 10,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
  "coinbase"   : "0x0000000000000000000000000000000000000000",
  "difficulty" : "0x20000",
  "extraData"  : "",
  "gasLimit"   : "0x2fefd8",
  "nonce"      : "0x0000000000000042",
  "mixhash"    : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp"  : "0x00",
  "alloc"      : {}
}

生成节点(以下使用节点1代指)

$ geth --datadir data00 init genesis.json
WARN [01-25|17:04:25] No etherbase set and no accounts found as default
INFO [01-25|17:04:25] Allocated cache and file handles         database=/home/ubuntu/my_eth2/data00/geth/chaindata cache=16 handles=16
INFO [01-25|17:04:25] Writing custom genesis block
INFO [01-25|17:04:25] Successfully wrote genesis state         database=chaindata                                  hash=5e1fc7…d790e0
INFO [01-25|17:04:25] Allocated cache and file handles         database=/home/ubuntu/my_eth2/data00/geth/lightchaindata cache=16 handles=16
INFO [01-25|17:04:25] Writing custom genesis block
INFO [01-25|17:04:25] Successfully wrote genesis state         database=lightchaindata                                  hash=5e1fc7…d790e0

启动节点1

$ geth --datadir ./data00 --networkid 5201314 console

创建账号

> personal.newAccount("123")
"0x0b514e769e4e1990f8fb0f0f9d876d7f2b9fa5ba"

本地创建第二个节点(以下使用节点2代指)

新开窗口,创建节点2

$ geth --datadir data01 init genesis.json
WARN [01-25|17:07:33] No etherbase set and no accounts found as default
INFO [01-25|17:07:33] Allocated cache and file handles         database=/home/ubuntu/my_eth2/data01/geth/chaindata cache=16 handles=16
INFO [01-25|17:07:33] Writing custom genesis block
INFO [01-25|17:07:33] Successfully wrote genesis state         database=chaindata                                  hash=5e1fc7…d790e0
INFO [01-25|17:07:33] Allocated cache and file handles         database=/home/ubuntu/my_eth2/data01/geth/lightchaindata cache=16 handles=16
INFO [01-25|17:07:33] Writing custom genesis block
INFO [01-25|17:07:33] Successfully wrote genesis state         database=lightchaindata                                  hash=5e1fc7…d790e0

运行节点2

$ geth --datadir data01 --networkid 5201314 --ipcdisable --port 61910 --rpcport 8200 console

创建节点2的账号

 > personal.newAccount("123")
"0x3babf1eeb8d5d29acc4d1f6408529b36b4e6f880"

在Mac上创建新节点,以下使用(节点3代指)

创世节点的json文件要和Ubuntu一致

初始化节点3

$ geth --datadir data00 init genesis.json
WARN [01-25|17:14:10] No etherbase set and no accounts found as default
INFO [01-25|17:14:10] Allocated cache and file handles         database=/Users/feilong/my_chain2/data00/geth/chaindata cache=16 handles=16
INFO [01-25|17:14:10] Writing custom genesis block
INFO [01-25|17:14:10] Successfully wrote genesis state         database=chaindata                                      hash=5e1fc7…d790e0
INFO [01-25|17:14:10] Allocated cache and file handles         database=/Users/feilong/my_chain2/data00/geth/lightchaindata cache=16 handles=16
INFO [01-25|17:14:10] Writing custom genesis block
INFO [01-25|17:14:10] Successfully wrote genesis state         database=lightchaindata                                      hash=5e1fc7…d790e0

运行节点3

$ geth --datadir data00 --networkid 5201314 --ipcdisable --port 61911 --rpcport 8200 console #使用61911端口,保证networkid一致

创建账号

> personal.newAccount("123")
"0xf81b1d6c0e0835790c7e4af8a02301a67e5a0dcb"

节点1和节点2建立联系

节点2运行 > admin.nodeInfo.enode 查看node信息

> admin.nodeInfo.enode
"enode://d5bb9fecc8e997905220b5e8c0db8396880bd5326143614b33f81ead534fc4d8282cbdda620fb81eaea66c359c3acd7d590f64981099b3cc063fddae9ac376d9@192.168.164.210:61910"

节点1添加节点2

> admin.addPeer("enode://d5bb9fecc8e997905220b5e8c0db8396880bd5326143614b33f81ead534fc4d8282cbdda620fb81eaea66c359c3acd7d590f64981099b3cc063fddae9ac376d9@192.168.164.210:61910")
true

节点1和节点2运行> net

> net
{
  listening: true,
  peerCount: 1, #说明添加成功
  version: "5201314",
  getListening: function(callback),
  getPeerCount: function(callback),
  getVersion: function(callback)
}

节点1和节点3建立联系

节点3运行 > admin.nodeInfo.enode 查看node信息

>  admin.nodeInfo.enode
"enode://34dcd9b7e64b24a25fe25b6e2aab6fc10525a439b2174ad79bd55bbf867f98060f7eef26c83223ae665372afa819ffd5c9c49a039c4e5e9c4e72be35a3b65aa8@192.168.164.210:61911"

节点1添加节点3

> admin.addPeer("enode://34dcd9b7e64b24a25fe25b6e2aab6fc10525a439b2174ad79bd55bbf867f98060f7eef26c83223ae665372afa819ffd5c9c49a039c4e5e9c4e72be35a3b65aa8@192.168.164.210:61911")
true

分别查看节点1和节点3链接情况

节点1

> net
{
  listening: true,
  peerCount: 2, ##节点1连接两个节点
  version: "5201314",
  getListening: function(callback),
  getPeerCount: function(callback),
  getVersion: function(callback)
}

节点3

> net
{
  listening: true,
  peerCount: 1,
  version: "5201314",
  getListening: function(callback),
  getPeerCount: function(callback),
  getVersion: function(callback)
}

节点挖矿测试

使用任一节点挖矿,然后观察其他两个控制台,发现都会有同步的数据,说明节点2和节点3也是连接的状态(由于电脑性能原因,挖矿的时候需要等percentage到达100之后才会开始)

遇到的坑

  • 要保证创世节点的json文件一致
  • 保证在统一局域网内,使用Telnet命令测试
  • 节点2和节点3的端口注意不要重复

基于以太坊创建私有链进行挖矿、交易

要说2018年什么最火,无疑就是区块链。比特币的疯狂上涨,每个比特币超过了1万美金。随之而来的就是区块链的技术。
以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。本文主要是通过以太坊,创建私有链,实现挖矿和交易。

安装golang

克隆项目
$ git clone https://github.com/golang/go.git
安装go 1.4

golang 是自编译,所以如果安装版本 >=1.5 需要先编译1.4版本,然后再安装其他版本

$ cp -r go/ $HOME/go1.4 #复制一份文件夹,用于编译1.4版本
$ cd $HOME/go1.4
$ git checkout release-branch.go1.4
$ cd src
$ ./make.bash # 进行编译

编译之后,开始安装go 1.9版本

$ cd $HOME/install/go
$ git checkout release-branch.go1.9
$ cd src/
$ ./all.bash #安装1.9版本
##### Building Go bootstrap tool.
cmd/dist

##### Building Go toolchain using /home/test/go1.4.
bootstrap/cmd/internal/dwarf
bootstrap/cmd/internal/objabi
bootstrap/cmd/internal/src
bootstrap/cmd/internal/sys
bootstrap/cmd/internal/obj
bootstrap/cmd/internal/obj/arm
bootstrap/cmd/internal/obj/arm64
bootstrap/cmd/internal/obj/mips
bootstrap/cmd/internal/obj/ppc64
bootstrap/cmd/internal/obj/s390x
... ##各种编译安装信息
##### API check
Go version is "go1.9.2", ignoring -next /home/test/install/go/api/next.txt

ALL TESTS PASSED

---
Installed Go for linux/amd64 in /home/test/install/go
Installed commands in /home/test/install/go/bin
*** You need to add /home/test/install/go/bin to your PATH.

配置环境变量

$ export PATH=$PATH:/home/test/install/go/bin

查看安装版本

$ go version
go version go1.9.2 linux/amd64
克隆go-ethereum
$ git clone https://github.com/ethereum/go-ethereum.git
安装以太坊
$ make geth

build/env.sh go run build/ci.go install ./cmd/geth
>>> /home/test/install/go/bin/go install -ldflags -X main.gitCommit=5d4267911a7791bfa60f275a97347372fbf0ce99 -v ./cmd/geth
github.com/ethereum/go-ethereum/common/hexutil
github.com/ethereum/go-ethereum/crypto/sha3
github.com/ethereum/go-ethereum/common
...
github.com/ethereum/go-ethereum/vendor/github.com/gizak/termui
github.com/ethereum/go-ethereum/vendor/github.com/naoina/go-stringutil
github.com/ethereum/go-ethereum/vendor/github.com/naoina/toml/ast
github.com/ethereum/go-ethereum/vendor/github.com/naoina/toml
github.com/ethereum/go-ethereum/cmd/geth
Done building.
Run "/home/test/install/go-ethereum/build/bin/geth" to launch geth.
创建连接
$ ln -s  /home/test/install/go-ethereum/build/bin/geth /usr/local/bin/geth

创建私有链

创建创世区块
{
    "config": {
        "chainId": 15,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
    "coinbase" : "0x0000000000000000000000000000000000000000",
    "difficulty" : "0x40000",
    "extraData" : "",
    "gasLimit" : "0xffffffff",
    "nonce" : "0x0000000000000042",
    "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
    "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp" : "0x00",
    "alloc": { }
}
$ mkdir my_chain
$ cd my_chain
$ vim genesis.json # json文件的内容是上面的json字符串
创建创世节点,并且初始化数据
$ geth --datadir data00 init genesis.json

data00就是用来保存创世节点的数据

启动节点,指定networkid
$ geth --datadir ./data00 --networkid 5201314 console #使用console 支持命令行模式

创建节点的账号
# 在console命令模式下
> personal.newAccount("123")
"0x9ff8676095e5999bf82eafeab98192e33ad74364"
开始进行挖矿
> miner.start()
INFO [01-23|00:14:37] Updated mining threads                   threads=0
INFO [01-23|00:14:37] Transaction pool price threshold updated price=18000000000
INFO [01-23|00:14:37] Etherbase automatically configured       address=0x9FF8676095e5999bf82EafEaB98192E33ad74364
null
> INFO [01-23|00:14:37] Starting mining operation
INFO [01-23|00:14:37] Commit new mining work                   number=1 txs=0 uncles=0 elapsed=146.511µs
INFO [01-23|00:14:43] Generating DAG in progress               epoch=0 percentage=0 elapsed=4.529s
INFO [01-23|00:14:48] Generating DAG in progress               epoch=0 percentage=1 elapsed=8.913s
INFO [01-23|00:14:52] Generating DAG in progress               epoch=0 percentage=2 elapsed=13.169s
INFO [01-23|00:14:56] Generating DAG in progress               epoch=0 percentage=3 elapsed=17.298s
INFO [01-23|00:15:01] Generating DAG in progress               epoch=0 percentage=4 elapsed=21.964s
...

等到percentage加载到100的时候就开始进行挖矿

结束挖矿
> miner.stop()
查看挖矿的金额
> eth.getBalance(eth.accounts[0])
5000000000000000000
新开一个窗口,创建第二个节点
$ geth --datadir data01 init genesis.json
运行第二个节点

networkid需要和第一个账号相同

$ geth --datadir data01 --networkid 5201314 --ipcdisable --port 61910 --rpcport 8200 console #使用命令行模式
创建账号
> personal.newAccount("456")
"0xfc350d17b0fb92eeb0a3bab80116c27d9f7e40d1"
开始进行挖矿
> miner.start()
结束挖矿
> miner.stop()

用户交易

回到第一个节点

查看节点信息

> admin.nodeInfo.enode
"enode://49e538b3f090a04e97f56a7fd1e6223c29599535d5e93010349147dee334b690744504f057ae11adb2804baada222375a56398ef42be536c595c6197a4a7cb2d@[::]:30303"
切换到第二个节点窗口

建立联系, 添加第一个节点enode

 admin.addPeer("enode://49e538b3f090a04e97f56a7fd1e6223c29599535d5e93010349147dee334b690744504f057ae11adb2804baada222375a56398ef42be536c595c6197a4a7cb2d@[::]:30303")
true
切换到第一个控制台

查看建立的联系数量

> net.peerCount
1

peerCount=1,说明已经建立了联系

开始进行交易

切换到一个控制台,交易之前,需要先解锁账号才行

> personal.unlockAccount(eth.accounts[0], "123")
true

返回true说明已经解锁成功

> eth.sendTransaction({from: "0x9ff8676095e5999bf82eafeab98192e33ad74364", to: "0xfc350d17b0fb92eeb0a3bab80116c27d9f7e40d1", value: web3.toWei(1, "ether")})

to和form分别是接受和发送的账号,也就是personal.listAccounts里面的账号

查看确认下交易信息

> eth.pendingTransactions
[{
    blockHash: null,
    blockNumber: null,
    from: "0x9ff8676095e5999bf82eafeab98192e33ad74364",
    gas: 90000,
    gasPrice: 18000000000,
    hash: "0x6146513432b27b6a27f54b64fcf0a30dc90290452dfd25e282a05aaf423f4afa",
    input: "0x",
    nonce: 0,
    r: "0x77644ff132f800da9b2d8133c796916f956061a491b55e3cbbd0d710f5157199",
    s: "0x9dbe268e152e5fc15421efa5b00e5d4a601ef4cb2655b577901fd96c5d3c959",
    to: "0xfc350d17b0fb92eeb0a3bab80116c27d9f7e40d1",
    transactionIndex: 0,
    v: "0x42",
    value: 1000000000000000000
}]

开始进行挖矿,使交易生效

> miner.start()
确认交易是否成功

在一个和第二个控制台分别运行命令,确认是否交易成功

> eth.getBalance(eth.accounts[0])

总结

挖矿对电脑要求比较高,我使用阿里云的服务器,1核1G基本的配置,两个节点同时挖矿,经常出现CPU吃满的情况。

还有一个比较奇怪的,当发起交易的时候,一定要进行挖矿操作,才能使交易生效。

参考文献

blockchain随笔

PHP7内存管理之写时复制

其实PHP的内存管理是包含引用计数和写时复制两部分,这篇文章主要是介绍写时复制。

简要介绍

其实写时复制在计算机中有很多应用,它只在必要的时候才会进行深拷贝,也就是把保存的值连同内存一块拷贝一份,可以很好的节省效率。比如,Linux在fork子进程的时候,不会立刻复制父进程的地址空间,而是和父进程共享一个地址空间,只有在必要写入的时候,才会复制地址空间,和父进程进行分离。简单来讲,资源的复制是只有需要写入的时候,再回进行,再次之前,都是以只读的方式进行共享。

PHP的写时复制

PHP的写时复制原理是一样的。当变量要修改value的结构的时候,这个时候,就会对之前共享的内存资源进行复制一份进行修改,同事断开原来的指向,指向复制后的内存地址。
举个例子:

<?php
$a = array(1, 2);
$b = $a;
$c = $b;
echo xdebug_debug_zval('a');
?>

PHP7
a: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)

<?php
$a = array(1, 2);
$b = $a;
$c = $b;
//进行分离
$c[] = 3;
echo xdebug_debug_zval('a');
?>

PHP7
a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)

运行结果很明显,当变量c新插入了一个元素,对那么就没有在继续引用变量a,而是独立复制了一份。

然而并不是所有的类型都是支持写时复制,比如对象、资源就无法进行复制,也就是无法进行分离,如果多个变量指向同一个对象,当其中一个变量修改对象的时候,其修改将会反应到所有对象上面。事实上只有string和array两种支持分离。
举个例子:

<?php
class test {
        public $c = 123;
}

$a = new test();
$b = $a;
$c = $b;
echo xdebug_debug_zval('a');
$c->c = 456;
echo $a->c;
echo "\n";
echo xdebug_debug_zval('a');
?>

PHP7
a: (refcount=3, is_ref=0)=class test { public $c = (refcount=0, is_ref=0)=123 }
456
a: (refcount=3, is_ref=0)=class test { public $c = (refcount=0, is_ref=0)=456 }

同样,变量a实例化了一个新的对象,然后依次进行赋值给其他变量,使用xdebug_debug_zval的时候,打印出来了变量a的3次引用计数,然后对变量c进行赋值,咦?居然发现变量a的引用计数没有变化,所以object的类型是不支持写时复制的。

支持复制的value类型:

type copyable
simple types N
string Y
interned string N
array Y
immutable array N
object N
resource N
reference N

参考文献

《PHP7内核剖析》

PHP7内存管理之引用计数

C/C++的内存管理

C/C++想要在堆上面分配内存,需要手动进行内存的分配和释放,变量管理非常的麻烦和繁琐,稍有不慎,就可能会造成内存上的错误使用。现在的一些高级语言,都普遍实行自动GC机制。

自己的意淫

我们自己先思考下实行自动GC的方法,当我们定义一个变量的时候,给变量分配一块内存,用于保存zval和value的值,等到函数返回的时候,再讲内存回收。如果将变量赋值给其他变量的时候,再进行内存的复制,变量之间相互独立,互不影响。

PHP的内存管理

PHP的内存管理肯定不会像是我们想象的那么简单,如果那么简单,那该要浪费多少内存。PHP的内存管理是采用:引用计数+写时复制 的方法。

引用计数

引用计数是指会有多少个zval指向同一个zend_value。当把变量赋值给一个新的变量的时候,引用计数就会+1。PHP7是将引用计数保存到了zval的结构中。

//php7
typedef union _zend_value {
    zend_long         lval;             /* long value */
    double            dval;             /* double value */
    zend_refcounted  *counted;  // 引用计数
    zend_string      *str;
    zend_array       *arr;
    zend_object      *obj;
    zend_resource    *res;
    zend_reference   *ref;
    zend_ast_ref     *ast;
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

PHP5是把引用计数放到了zend_struct里面

struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount__gc; //引用计数
    zend_uchar type;    /* active type */
    zend_uchar is_ref__gc;
};

PHP5不是这篇文章的重点,暂且不说。

下面我们看下PHP7 zend_refcounted具体的结构

typedef struct _zend_refcounted_h {
    uint32_t         refcount;          /* reference counter 32-bit */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,    /* used for strings & objects */
                uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

struct _zend_refcounted {
    zend_refcounted_h gc;
};

很明显,refcount字段使用了进行计数操作的。
举个例子来看看:

<?php
$a = array();
echo xdebug_debug_zval('a');
$b = $a;
echo xdebug_debug_zval('a');
$c = $b;
echo xdebug_debug_zval('a');
unset($c);
echo xdebug_debug_zval('a');
?>

运行结果如下:
a: (refcount=1, is_ref=0)=array ()
a: (refcount=2, is_ref=0)=array ()
a: (refcount=3, is_ref=0)=array ()
a: (refcount=2, is_ref=0)=array ()

就像代码运行结果一样,首先定义了一个变量a,引用给数组分配了一块空间,引用计数为1,然后把a赋值给变量b,引用计数+1,然后赋值给变量c,继续+1,然后把变量c释放,引用计数-1。

但是,并不是所有的变量都会使用引用计数。比如整形,浮点型,布尔型,NULL,他们的值是直接保存在zval中,所以他们的引用计数是0。这个也是PHP5和PHP7的一个不同点。

举例说明:

<?php
$a = 123;
echo xdebug_debug_zval('a');
$b = $a;
echo xdebug_debug_zval('a');
?>

PHP5
a: (refcount=1, is_ref=0)=123
a: (refcount=2, is_ref=0)=123

PHP7
a: (refcount=0, is_ref=0)=123
a: (refcount=0, is_ref=0)=123

特殊情况

在PHP7中还有两种特殊的情况

举例说明:

<?php
$a = "hi";
$b = $a;
$c = $a;
xdebug_debug_zval('a');
?>

PHP7
a: (refcount=0, is_ref=0)=’hi’

<?php
$a = "hi".time();
$b = $a;
$c = $a;
xdebug_debug_zval('a');
?>

PHP7
a: (refcount=3, is_ref=0)=’hi1516202718′

wtf,为什么这两个是不一样的?这就是另外的特殊情况了。

  • 在PHP中,函数名、类名、变量名、静态字符串等这种类型,比如第一个例子$a = "hi",后面的字符串是唯一不变的,这等同于C语言中的char *a = "hi",这些字符串是整个请求周期,请求结束后,同意销毁,自然不用引用计数来进行管理。
  • 不可变数组,这是opcache的一种优化类型,这里不做详细说明。

总结

引用计数算是PHP7和PHP5的一个重要的变更,这个也是内存的一个优化的地方。
在PHP5中,引用计数是在zval中,而不是在zend_value中,这样一来,导致变量复制的时候要复制两个结构(zval和zend_value),PHP7将引用计数放到zend_value中,这样就可以进行公用,设计也更加合理。

参考文献

  • 《PHP7内核剖析》