So Tired !_! 逆水行舟, 不进则退!

17Nov/15

MongoDB 副本集的原理、搭建、应用

Posted by Nick Xu

概念:

在了解了这篇文章之后,可以进行该篇文章的说明和测试。MongoDB 副本集(Replica Set)是有自动故障恢复功能的主从集群,有一个Primary节点和一个或多个Secondary节点组成。类似于MySQL的MMM架构。更多关于副本集的介绍请见官网。也可以在google、baidu上查阅。

副本集中数据同步过程Primary节点写入数据,Secondary通过读取Primary的oplog得到复制信息,开始复制数据并且将复制信息写入到自己的oplog。如果某个操作失败,则备份节点停止从当前数据源复制数据。如果某个备份节点由于某些原因挂掉了,当重新启动后,就会自动从oplog的最后一个操作开始同步,同步完成后,将信息写入自己的oplog,由于复制操作是先复制数据,复制完成后再写入oplog,有可能相同的操作会同步两份,不过MongoDB在设计之初就考虑到这个问题,将oplog的同一个操作执行多次,与执行一次的效果是一样的。简单的说就是:

当Primary节点完成数据操作后,Secondary会做出一系列的动作保证数据的同步:
1:检查自己local库的oplog.rs集合找出最近的时间戳。
2:检查Primary节点local库oplog.rs集合,找出大于此时间戳的记录。
3:将找到的记录插入到自己的oplog.rs集合中,并执行这些操作。

副本集的同步和主从同步一样,都是异步同步的过程,不同的是副本集有个自动故障转移的功能。其原理是:slave端从primary端获取日志,然后在自己身上完全顺序的执行日志所记录的各种操作(该日志是不记录查询操作的),这个日志就是local数据 库中的oplog.rs表,默认在64位机器上这个表是比较大的,占磁盘大小的5%,oplog.rs的大小可以在启动参数中设 定:--oplogSize 1000,单位是M。

注意:在副本集的环境中,要是所有的Secondary都宕机了,只剩下Primary。最后Primary会变成Secondary,不能提供服务。

一:环境搭建

1:准备服务器

192.168.200.25

192.168.200.245

192.168.200.252

2:安装

http://www.cnblogs.com/zhoujinyi/archive/2013/06/02/3113868.html

3:修改配置,只需要开启:replSet 参数即可。格式为:

192.168.200.252: --replSet = mmm/192.168.200.245:27017  # mmm是副本集的名称,192.168.200.25:27017 为实例的位子。

192.168.200.245: --replSet = mmm/192.168.200.252:27017

192.168.200.25: --replSet = mmm/192.168.200.252:27017,192.168.200.245:27017

4:启动

启动后会提示:

replSet info you may need to run replSetInitiate -- rs.initiate() in the shell -- if that is not already done

说明需要进行初始化操作,初始化操作只能执行一次。

5:初始化副本集

登入任意一台机器的MongoDB执行:因为是全新的副本集所以可以任意进入一台执行;要是有一台有数据,则需要在有数据上执行;要多台有数据则不能初始化。

复制代码
zhoujy@zhoujy:~$ mongo --host=192.168.200.252
MongoDB shell version: 2.4.6
connecting to: 192.168.200.252:27017/test
> rs.initiate({"_id":"mmm","members":[
... {"_id":1,
... "host":"192.168.200.252:27017",
... "priority":1
... },
... {"_id":2,
... "host":"192.168.200.245:27017",
... "priority":1
... }
... ]})
{
    "info" : "Config now saved locally.  Should come online in about a minute.",
    "ok" : 1
}
######
"_id": 副本集的名称
"members": 副本集的服务器列表
"_id": 服务器的唯一ID
"host": 服务器主机
"priority": 是优先级,默认为1,优先级0为被动节点,不能成为活跃节点。优先级不位0则按照有大到小选出活跃节点。
"arbiterOnly": 仲裁节点,只参与投票,不接收数据,也不能成为活跃节点。
> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T04:03:53Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 76,
            "optime" : Timestamp(1392696191, 1),
            "optimeDate" : ISODate("2014-02-18T04:03:11Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 35,
            "optime" : Timestamp(1392696191, 1),
            "optimeDate" : ISODate("2014-02-18T04:03:11Z"),
            "lastHeartbeat" : ISODate("2014-02-18T04:03:52Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T04:03:53Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
复制代码

6:日志

查看252上的日志:

Tue Feb 18 12:03:29.334 [rsMgr] replSet PRIMARY
…………
…………
Tue Feb 18 12:03:40.341 [rsHealthPoll] replSet member 192.168.200.245:27017 is now in state SECONDARY

至此,整个副本集已经搭建成功了。

上面的的副本集只有2台服务器,还有一台怎么添加?除了在初始化的时候添加,还有什么方法可以后期增删节点?

二:维护操作

1:增删节点。

把25服务加入到副本集中:

rs.add("192.168.200.25:27017")

复制代码
mmm:PRIMARY> rs.add("192.168.200.25:27017")
{ "ok" : 1 }
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T04:53:00Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 3023,
            "optime" : Timestamp(1392699177, 1),
            "optimeDate" : ISODate("2014-02-18T04:52:57Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 2982,
            "optime" : Timestamp(1392699177, 1),
            "optimeDate" : ISODate("2014-02-18T04:52:57Z"),
            "lastHeartbeat" : ISODate("2014-02-18T04:52:59Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T04:53:00Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 6,
            "stateStr" : "UNKNOWN",             #等一会就变成了 SECONDARY 
            "uptime" : 3,
            "optime" : Timestamp(0, 0),
            "optimeDate" : ISODate("1970-01-01T00:00:00Z"),
            "lastHeartbeat" : ISODate("2014-02-18T04:52:59Z"),
            "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "still initializing"
        }
    ],
    "ok" : 1
}
复制代码

把25服务从副本集中删除:

rs.remove("192.168.200.25:27017")

复制代码
mmm:PRIMARY> rs.remove("192.168.200.25:27017")
Tue Feb 18 13:01:09.298 DBClientCursor::init call() failed
Tue Feb 18 13:01:09.299 Error: error doing query: failed at src/mongo/shell/query.js:78
Tue Feb 18 13:01:09.300 trying reconnect to 192.168.200.252:27017
Tue Feb 18 13:01:09.301 reconnect 192.168.200.252:27017 ok
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:01:19Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 3522,
            "optime" : Timestamp(1392699669, 1),
            "optimeDate" : ISODate("2014-02-18T05:01:09Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 10,
            "optime" : Timestamp(1392699669, 1),
            "optimeDate" : ISODate("2014-02-18T05:01:09Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:01:19Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:01:18Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "syncing to: 192.168.200.252:27017",
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
复制代码

192.168.200.25 的节点已经被移除。

2:查看复制的情况

db.printSlaveReplicationInfo()

复制代码
mmm:PRIMARY> db.printSlaveReplicationInfo()
source:   192.168.200.245:27017
     syncedTo: Tue Feb 18 2014 13:02:35 GMT+0800 (CST)
         = 145 secs ago (0.04hrs)
source:   192.168.200.25:27017
     syncedTo: Tue Feb 18 2014 13:02:35 GMT+0800 (CST)
         = 145 secs ago (0.04hrs)
复制代码

source:从库的ip和端口。

syncedTo:目前的同步情况,以及最后一次同步的时间。

从上面可以看出,在数据库内容不变的情况下他是不同步的,数据库变动就会马上同步。

3:查看副本集的状态

rs.status()

复制代码
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:12:28Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 4191,
            "optime" : Timestamp(1392699755, 1),
            "optimeDate" : ISODate("2014-02-18T05:02:35Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 679,
            "optime" : Timestamp(1392699755, 1),
            "optimeDate" : ISODate("2014-02-18T05:02:35Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:12:27Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:12:27Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 593,
            "optime" : Timestamp(1392699755, 1),
            "optimeDate" : ISODate("2014-02-18T05:02:35Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:12:28Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:12:28Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
复制代码

4:副本集的配置

rs.conf()/rs.config()

复制代码
mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 4,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017"
        }
    ]
}
复制代码

5:操作Secondary

默认情况下,Secondary是不提供服务的,即不能读和写。会提示:
error: { "$err" : "not master and slaveOk=false", "code" : 13435 }

在特殊情况下需要读的话则需要:
rs.slaveOk() ,只对当前连接有效。

mmm:SECONDARY> db.test.find()
error: { "$err" : "not master and slaveOk=false", "code" : 13435 }
mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.find()
{ "_id" : ObjectId("5302edfa8c9151a5013b978e"), "a" : 1 }

6:更新ing

 

三:测试

1:测试副本集数据复制功能

在Primary(192.168.200.252:27017)上插入数据:

mmm:PRIMARY> for(var i=0;i<10000;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
10001

在Secondary上查看是否已经同步:

mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.count()
10001

数据已经同步。

2:测试副本集故障转移功能

关闭Primary节点,查看其他2个节点的情况:

复制代码
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:38:54Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 5777,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 2265,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:38:54Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:38:53Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 2179,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:38:54Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:38:53Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}

#关闭
mmm:PRIMARY> use admin
switched to db admin
mmm:PRIMARY> db.shutdownServer()

#进入任意一台:
mmm:SECONDARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:47:41Z"),
    "myState" : 2,
    "syncingTo" : "192.168.200.25:27017",
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 0,
            "state" : 8,
            "stateStr" : "(not reachable/healthy)",
            "uptime" : 0,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:47:40Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:45:57Z"),
            "pingMs" : 0
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 5888,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "errmsg" : "syncing to: 192.168.200.25:27017",
            "self" : true
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 2292,
            "optime" : Timestamp(1392701576, 2678),
            "optimeDate" : ISODate("2014-02-18T05:32:56Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:47:40Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:47:39Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
复制代码

看到192.168.200.25:27017 已经从 SECONDARY 变成了 PRIMARY。具体的信息可以通过日志文件得知。继续操作:

在新主上插入:

mmm:PRIMARY> for(var i=0;i<10000;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
20001

重启启动之前关闭的192.168.200.252:27017

复制代码
mmm:SECONDARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T05:45:14Z"),
    "myState" : 2,
    "syncingTo" : "192.168.200.245:27017",
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 12,
            "optime" : Timestamp(1392702168, 8187),
            "optimeDate" : ISODate("2014-02-18T05:42:48Z"),
            "errmsg" : "syncing to: 192.168.200.245:27017",
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 11,
            "optime" : Timestamp(1392702168, 8187),
            "optimeDate" : ISODate("2014-02-18T05:42:48Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:45:13Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:45:12Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.25:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 9,
            "optime" : Timestamp(1392702168, 8187),
            "optimeDate" : ISODate("2014-02-18T05:42:48Z"),
            "lastHeartbeat" : ISODate("2014-02-18T05:45:13Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T05:45:13Z"),
            "pingMs" : 0
        }
    ],
    "ok" : 1
}
复制代码

启动之前的主,发现其变成了SECONDARY,在新主插入的数据,是否已经同步:

mmm:SECONDARY> db.test.count()
Tue Feb 18 13:47:03.634 count failed: { "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" } at src/mongo/shell/query.js:180
mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.count()
20001

已经同步。

注意

所有的Secondary都宕机、或则副本集中只剩下一个节点,则该节点只能为Secondary节点,也就意味着整个集群智能进行读操作而不能进行写操作,当其他的恢复时,之前的primary节点仍然是primary节点。

当某个节点宕机后重新启动该节点会有一段的时间(时间长短视集群的数据量和宕机时间而定)导致整个集群中所有节点都成为secondary而无法进行写操作(如果应用程序没有设置相应的ReadReference也可能不能进行读取操作)。

官方推荐的最小的副本集也应该具备一个primary节点和两个secondary节点。两个节点的副本集不具备真正的故障转移能力。

四:应用

1:手动切换Primary节点到自己给定的节点
上面已经提到过了优先集priority,因为默认的都是1,所以只需要把给定的服务器的priority加到最大即可。让245 成为主节点,操作如下:

复制代码
mmm:PRIMARY> rs.conf() #查看配置
{
    "_id" : "mmm",
    "version" : 6,  #每改变一次集群的配置,副本集的version都会加1。
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017"
        }
    ]
}
mmm:PRIMARY> rs.status() #查看状态
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T07:25:51Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 47,
            "optime" : Timestamp(1392708304, 1),
            "optimeDate" : ISODate("2014-02-18T07:25:04Z"),
            "lastHeartbeat" : ISODate("2014-02-18T07:25:50Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T07:25:50Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "syncing to: 192.168.200.25:27017",
            "syncingTo" : "192.168.200.25:27017"
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 47,
            "optime" : Timestamp(1392708304, 1),
            "optimeDate" : ISODate("2014-02-18T07:25:04Z"),
            "lastHeartbeat" : ISODate("2014-02-18T07:25:50Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T07:25:51Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "syncing to: 192.168.200.25:27017",
            "syncingTo" : "192.168.200.25:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 13019,
            "optime" : Timestamp(1392708304, 1),
            "optimeDate" : ISODate("2014-02-18T07:25:04Z"),
            "self" : true
        }
    ],
    "ok" : 1
}
mmm:PRIMARY> cfg=rs.conf() #
{
    "_id" : "mmm",
    "version" : 4,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017"
        }
    ]
}
mmm:PRIMARY> cfg.members[1].priority=2  #修改priority
2
mmm:PRIMARY> rs.reconfig(cfg) #重新加载配置文件,强制了副本集进行一次选举,优先级高的成为Primary。在这之间整个集群的所有节点都是secondary

mmm:SECONDARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T07:27:38Z"),
    "myState" : 2,
    "syncingTo" : "192.168.200.245:27017",
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 71,
            "optime" : Timestamp(1392708387, 1),
            "optimeDate" : ISODate("2014-02-18T07:26:27Z"),
            "lastHeartbeat" : ISODate("2014-02-18T07:27:37Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T07:27:38Z"),
            "pingMs" : 0,
            "lastHeartbeatMessage" : "syncing to: 192.168.200.245:27017",
            "syncingTo" : "192.168.200.245:27017"
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 71,
            "optime" : Timestamp(1392708387, 1),
            "optimeDate" : ISODate("2014-02-18T07:26:27Z"),
            "lastHeartbeat" : ISODate("2014-02-18T07:27:37Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T07:27:38Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.25:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 13126,
            "optime" : Timestamp(1392708387, 1),
            "optimeDate" : ISODate("2014-02-18T07:26:27Z"),
            "errmsg" : "syncing to: 192.168.200.245:27017",
            "self" : true
        }
    ],
    "ok" : 1
}
复制代码

这样,给定的245服务器就成为了主节点。

2:添加仲裁节点

把25节点删除,重启。再添加让其为仲裁节点:

rs.addArb("192.168.200.25:27017")
复制代码
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-18T08:14:36Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 795,
            "optime" : Timestamp(1392711068, 100),
            "optimeDate" : ISODate("2014-02-18T08:11:08Z"),
            "lastHeartbeat" : ISODate("2014-02-18T08:14:35Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T08:14:35Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.245:27017"
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 14703,
            "optime" : Timestamp(1392711068, 100),
            "optimeDate" : ISODate("2014-02-18T08:11:08Z"),
            "self" : true
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 26,
            "lastHeartbeat" : ISODate("2014-02-18T08:14:34Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-18T08:14:34Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 9,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017",
            "priority" : 2
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017",
            "arbiterOnly" : true
        }
    ]
}
复制代码

上面说明已经让25服务器成为仲裁节点。副本集要求参与选举投票(vote)的节点数为奇数,当我们实际环境中因为机器等原因限制只有两个(或偶数)的节点,这时为了实现 Automatic Failover引入另一类节点:仲裁者(arbiter),仲裁者只参与投票不拥有实际的数据,并且不提供任何服务,因此它对物理资源要求不严格。

通过实际测试发现,当整个副本集集群中达到50%的节点(包括仲裁节点)不可用的时候,剩下的节点只能成为secondary节点,整个集群只能读不能 写。比如集群中有1个primary节点,2个secondary节点,加1个arbit节点时:当两个secondary节点挂掉了,那么剩下的原来的 primary节点也只能降级为secondary节点;当集群中有1个primary节点,1个secondary节点和1个arbit节点,这时即使 primary节点挂了,剩下的secondary节点也会自动成为primary节点。因为仲裁节点不复制数据,因此利用仲裁节点可以实现最少的机器开 销达到两个节点热备的效果。

3:添加备份节点

hidden(成员用于支持专用功能):这样设置后此机器在读写中都不可见,并且不会被选举为Primary,但是可以投票,一般用于备份数据。

把25节点删除,重启。再添加让其为hidden节点:

复制代码
mmm:PRIMARY> rs.add({"_id":3,"host":"192.168.200.25:27017","priority":0,"hidden":true})
{ "down" : [ "192.168.200.25:27017" ], "ok" : 1 }
mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 17,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017",
            "priority" : 0,
            "hidden" : true
        }
    ]
}
复制代码

测试其能否参与投票:关闭当前的Primary,查看是否自动转移Primary

复制代码
关闭Primary(252):
mmm:PRIMARY> use admin
switched to db admin
mmm:PRIMARY> db.shutdownServer()

连另一个链接察看:
mmm:PRIMARY> rs.status()
{
    "set" : "mmm",
    "date" : ISODate("2014-02-19T09:11:45Z"),
    "myState" : 1,
    "members" : [
        {
            "_id" : 1,
            "name" : "192.168.200.252:27017",
            "health" : 1,
            "state" : 1,
            "stateStr" :"(not reachable/healthy)",
            "uptime" : 4817,
            "optime" : Timestamp(1392801006, 1),
            "optimeDate" : ISODate("2014-02-19T09:10:06Z"),
            "self" : true
        },
        {
            "_id" : 2,
            "name" : "192.168.200.245:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "PRIMARY",
            "uptime" : 401,
            "optime" : Timestamp(1392801006, 1),
            "optimeDate" : ISODate("2014-02-19T09:10:06Z"),
            "lastHeartbeat" : ISODate("2014-02-19T09:11:44Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-19T09:11:43Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        },
        {
            "_id" : 3,
            "name" : "192.168.200.25:27017",
            "health" : 1,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 99,
            "optime" : Timestamp(1392801006, 1),
            "optimeDate" : ISODate("2014-02-19T09:10:06Z"),
            "lastHeartbeat" : ISODate("2014-02-19T09:11:44Z"),
            "lastHeartbeatRecv" : ISODate("2014-02-19T09:11:43Z"),
            "pingMs" : 0,
            "syncingTo" : "192.168.200.252:27017"
        }
    ],
    "ok" : 1
}
上面说明Primary已经转移,说明hidden具有投票的权利,继续查看是否有数据复制的功能。
#####
mmm:PRIMARY> db.test.count()
20210
mmm:PRIMARY> for(var i=0;i<90;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
20300

Secondady:
mmm:SECONDARY> db.test.count()
Wed Feb 19 17:18:19.469 count failed: { "note" : "from execCommand", "ok" : 0, "errmsg" : "not master" } at src/mongo/shell/query.js:180
mmm:SECONDARY> rs.slaveOk()
mmm:SECONDARY> db.test.count()
20300
上面说明hidden具有数据复制的功能
复制代码

后面大家可以在上面进行备份了,后一篇会介绍如何备份、还原以及一些日常维护需要的操作。

4:添加延迟节点

Delayed(成员用于支持专用功能):可以指定一个时间延迟从primary节点同步数据。主要用于处理误删除数据马上同步到从节点导致的不一致问题。

把25节点删除,重启。再添加让其为Delayed节点:

复制代码
mmm:PRIMARY> rs.add({"_id":3,"host":"192.168.200.25:27017","priority":0,"hidden":true,"slaveDelay":60})  #语法
{ "down" : [ "192.168.200.25:27017" ], "ok" : 1 }

mmm:PRIMARY> rs.conf()
{
    "_id" : "mmm",
    "version" : 19,
    "members" : [
        {
            "_id" : 1,
            "host" : "192.168.200.252:27017"
        },
        {
            "_id" : 2,
            "host" : "192.168.200.245:27017"
        },
        {
            "_id" : 3,
            "host" : "192.168.200.25:27017",
            "priority" : 0,
            "slaveDelay" : 60,   
            "hidden" : true
        }
    ]
}
复制代码

测试:操作Primary,看数据是否60s后同步到delayed节点。

复制代码
mmm:PRIMARY> db.test.count()
20300
mmm:PRIMARY> for(var i=0;i<200;i++){db.test.insert({"name":"test"+i,"age":123})}
mmm:PRIMARY> db.test.count()
20500

Delayed:
mmm:SECONDARY> db.test.count()
20300
#60秒之后
mmm:SECONDARY> db.test.count()
20500
复制代码

上面说明delayed能够成功的把同步操作延迟60秒执行。除了上面的成员之外,还有:

Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止一些性能不高的节点成为主节点。

Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。

具体成员信息如下:

成为primary 对客户端可见 参与投票 延迟同步 复制数据
Default
Secondary-Only
Hidden
Delayed
Arbiters
Non-Voting

5:读写分离

MongoDB副本集对读写分离的支持是通过Read Preferences特性进行支持的,这个特性非常复杂和灵活。

应用程序驱动通过read reference来设定如何对副本集进行读取操作,默认的,客户端驱动所有的读操作都是直接访问primary节点的,从而保证了数据的严格一致性。

支持五种的read preference模式官网说明

复制代码
primary
主节点,默认模式,读操作只在主节点,如果主节点不可用,报错或者抛出异常。
primaryPreferred
首选主节点,大多情况下读操作在主节点,如果主节点不可用,如故障转移,读操作在从节点。
secondary
从节点,读操作只在从节点, 如果从节点不可用,报错或者抛出异常。
secondaryPreferred
首选从节点,大多情况下读操作在从节点,特殊情况(如单主节点架构)读操作在主节点。
nearest
最邻近节点,读操作在最邻近的成员,可能是主节点或者从节点,关于最邻近的成员请参考
复制代码

注意:2.2版本之前的MongoDB对Read Preference支持的还不完全,如果客户端驱动采用primaryPreferred实际上读取操作都会被路由到secondary节点。

因为读写分离是通过修改程序的driver的,故这里就不做说明,具体的可以参考这篇文章或则可以在google上查阅。

验证:(Python)

通过python来验证MongoDB ReplSet的特性。

1:主节点断开,看是否影响写入

脚本:

复制代码
#coding:utf-8
import time
from pymongo import ReplicaSetConnection
conn = ReplicaSetConnection("192.168.200.201:27017,192.168.200.202:27017,192.168.200.204:27017", replicaSet="drug",read_preference=2, safe=True)
#打印Primary服务器
#print conn.primary
#打印所有服务器
#print conn.seeds
#打印Secondary服务器
#print conn.secondaries

#print conn.read_preference
#print conn.server_info()
for i in xrange(1000):
    try:
        conn.test.tt.insert({"name":"test" + str(i)})
        time.sleep(1)
        print conn.primary
        print conn.secondaries
    except:
        pass
复制代码

脚本执行打印出的内容:

View Code

体操作如下:

在执行脚本的时候,模拟Primary宕机,再把其开启。看到其从201(Primary)上迁移到202上,201变成了Secondary。查看插入的数据发现其中间有一段数据丢失了。

{ "name" : "GOODODOO15" }
{ "name" : "GOODODOO592" }
{ "name" : "GOODODOO593" }

其实这部分数据是由于在选举过程期间丢失的,要是不允许数据丢失,则把在选举期间的数据放到队列中,等到找到新的Primary,再写入。

上面的脚本可能会出现操作时退出,这要看xrange()里的数量了,所以用一个循环修改(更直观):

复制代码
#coding:utf-8
import time
from pymongo import ReplicaSetConnection
conn = ReplicaSetConnection("192.168.200.201:27017,192.168.200.202:27017,192.168.200.204:27017", replicaSet="drug",read_preference=2, safe=True)

#打印Primary服务器
#print conn.primary
#打印所有服务器
#print conn.seeds
#打印Secondary服务器
#print conn.secondaries

#print conn.read_preference
#print conn.server_info()

while True:
    try:
        for i in xrange(100):
            conn.test.tt.insert({"name":"test" + str(i)})
            print "test" + str(i)
            time.sleep(2)
            print conn.primary
            print conn.secondaries
            print '\n'
    except:
        pass
复制代码

上面的实验证明了:在Primary宕机的时候,程序脚本仍可以写入,不需要人为的去干预。只是期间需要10s左右(选举时间)的时间会出现不可用,进一步说明,写操作时在Primary上进行的。

2:主节点断开,看是否影响读取

脚本:

复制代码
#coding:utf-8
import time
from pymongo import ReplicaSetConnection
conn = ReplicaSetConnection("192.168.200.201:27017,192.168.200.202:27017,192.168.200.204:27017", replicaSet="drug",read_preference=2, safe=True)

#打印Primary服务器
#print conn.primary
#打印所有服务器
#print conn.seeds
#打印Secondary服务器
#print conn.secondaries

#print conn.read_preference
#print conn.server_info()

for i in xrange(1000):
    
    time.sleep(1)
    obj=conn.test.tt.find({},{"_id":0,"name":1}).skip(i).limit(1)
    for item in obj:
        print item.values()
    print conn.primary
    print conn.secondaries
复制代码

脚本执行打印出的内容:

View Code

具体操作如下:

在执行脚本的时候,模拟Primary宕机,再把其开启。看到201(Primary)上迁移到202上,201变成了Secondary,读取数据没有间断。再让Primary宕机,不开启,读取也不受影响。

上面的实验证明了:在Primary宕机的时候,程序脚本仍可以读取,不需要人为的去干预。一进步说明,读取是在Secondary上面。

 

1。冻结其中的一个从节点,使其不参与到与primary 的内部选举工作

  进入客户端,执行(单位:秒)
  rs.freeze(30);
2.对原主节点进行降级
进入客户端,执行下面代码 (单位:秒)
rs.stepDown(15);
3.经过冻结和降级之后查看复制集状态
rs.status();
复制集顺利切换
Tagged as: Comments Off
12Nov/15

NUMA架构的CPU — 你真的用好了么?

Posted by Nick Xu

本文从NUMA的介绍引出常见的NUMA使用中的陷阱,继而讨论对于NUMA系统的优化方法和一些值得关注的方向。

文章欢迎转载,但转载时请保留本段文字,并置于文章的顶部
作者:卢钧轶(cenalulu)
本文原文地址:http://cenalulu.github.io/linux/numa/

NUMA简介

这部分将简要介绍下NUMA架构的成因和具体原理,已经了解的读者可以直接跳到第二节。

为什么要有NUMA

在NUMA架构出现前,CPU欢快的朝着频率越来越高的方向发展。受到物理极限的挑战,又转为核数越来越多的方向发展。如果每个core的工作性质都是share-nothing(类似于map-reduce的node节点的作业属性),那么也许就不会有NUMA。由于所有CPU Core都是通过共享一个北桥来读取内存,随着核数如何的发展,北桥在响应时间上的性能瓶颈越来越明显。于是,聪明的硬件设计师们,先到了把内存控制器(原本北桥中读取内存的部分)也做个拆分,平分到了每个die上。于是NUMA就出现了!

NUMA是什么

NUMA中,虽然内存直接attach在CPU上,但是由于内存被平均分配在了各个die上。只有当CPU访问自身直接attach内存对应的物理地址时,才会有较短的响应时间(后称Local Access)。而如果需要访问其他CPU attach的内存的数据时,就需要通过inter-connect通道访问,响应时间就相比之前变慢了(后称Remote Access)。所以NUMA(Non-Uniform Memory Access)就此得名。

numa

我们需要为NUMA做什么

假设你是Linux教父Linus,对于NUMA架构你会做哪些优化?下面这点是显而易见的:

既然CPU只有在Local-Access时响应时间才能有保障,那么我们就尽量把该CPU所要的数据集中在他local的内存中就OK啦~

没错,事实上Linux识别到NUMA架构后,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的Local内存上分配空间。如果local内存不足,优先淘汰local内存中无用的Page(Inactive,Unmapped)。
那么,问题来了。。。


NUMA的“七宗罪”

几乎所有的运维都会多多少少被NUMA坑害过,让我们看看究竟有多少种在NUMA上栽的方式:

究其原因几乎都和:“因为CPU亲和策略导致的内存分配不平均”及“NUMA Zone Claim内存回收”有关,而和数据库种类并没有直接联系。所以下文我们就拿MySQL为例,来看看重内存操作应用在NUMA架构下到底会出现什么问题。

MySQL在NUMA架构上会出现的问题

几乎所有NUMA + MySQL关键字的搜索结果都会指向:Jeremy Cole大神的两篇文章

大神解释的非常详尽,有兴趣的读者可以直接看原文。博主这里做一个简单的总结:

  • CPU规模因摩尔定律指数级发展,而总线发展缓慢,导致多核CPU通过一条总线共享内存成为瓶颈
  • 于是NUMA出现了,CPU平均划分为若干个Chip(不多于4个),每个Chip有自己的内存控制器及内存插槽
  • CPU访问自己Chip上所插的内存时速度快,而访问其他CPU所关联的内存(下文称Remote Access)的速度相较慢三倍左右
  • 于是Linux内核默认使用CPU亲和的内存分配策略,使内存页尽可能的和调用线程处在同一个Core/Chip中
  • 由于内存页没有动态调整策略,使得大部分内存页都集中在CPU 0
  • 又因为Reclaim默认策略优先淘汰/Swap本Chip上的内存,使得大量有用内存被换出
  • 当被换出页被访问时问题就以数据库响应时间飙高甚至阻塞的形式出现了

imbalance

解决方案

Jeremy Cole大神推荐的三个方案如下,如果想详细了解可以阅读 原文

  • numactl --interleave=all
  • 在MySQL进程启动前,使用sysctl -q -w vm.drop_caches=3清空文件缓存所占用的空间
  • Innodb在启动时,就完成整个Innodb_buffer_pool_size的内存分配

这三个方案也被业界普遍认可可行,同时在 Twitter 的5.5patchPercona 5.5 Improved NUMA Support 中作为功能被支持。

不过这种三合一的解决方案只是减少了NUMA内存分配不均,导致的MySQL SWAP问题出现的可能性。如果当系统上其他进程,或者MySQL本身需要大量内存时,Innodb Buffer Pool的那些Page同样还是会被Swap到存储上。于是又在这基础上出现了另外几个进阶方案

  • 配置vm.zone_reclaim_mode = 0使得内存不足时去remote memory分配优先于swap out local page
  • echo -15 > /proc/<pid_of_mysqld>/oom_adj调低MySQL进程被OOM_killer强制Kill的可能
  • memlock
  • 对MySQL使用Huge Page(黑魔法,巧用了Huge Page不会被swap的特性)

重新审视问题

如果本文写到这里就这么结束了,那和搜索引擎结果中大量的Step-by-Step科普帖没什么差别。虽然我们用了各种参数调整减少了问题发生概率,那么真的就彻底解决了这个问题么?问题根源究竟是什么?让我们回过头来重新审视下这个问题:

NUMA Interleave真的好么?

为什么Interleave的策略就解决了问题?
借用两张 Carrefour性能测试 的结果图,可以看到几乎所有情况下Interleave模式下的程序性能都要比默认的亲和模式要高,有时甚至能高达30%。究其根本原因是Linux服务器的大多数workload分布都是随机的:即每个线程在处理各个外部请求对应的逻辑时,所需要访问的内存是在物理上随机分布的。而Interleave模式就恰恰是针对这种特性将内存page随机打散到各个CPU Core上,使得每个CPU的负载和Remote Access的出现频率都均匀分布。相较NUMA默认的内存分配模式,死板的把内存都优先分配在线程所在Core上的做法,显然普遍适用性要强很多。
perf1
perf2

也就是说,像MySQL这种外部请求随机性强,各个线程访问内存在地址上平均分布的这种应用,Interleave的内存分配模式相较默认模式可以带来一定程度的性能提升。
此外 各种 论文 中也都通过实验证实,真正造成程序在NUMA系统上性能瓶颈的并不是Remote Acess带来的响应时间损耗,而是内存的不合理分布导致Remote Access将inter-connect这个小水管塞满所造成的结果。而Interleave恰好,把这种不合理分布情况下的Remote Access请求平均分布在了各个小水管中。所以这也是Interleave效果奇佳的一个原因。

那是不是简简单单的配置个Interleave就已经把NUMA的特性和性能发挥到了极致呢?
答案是否定的,目前Linux的内存分配机制在NUMA架构的CPU上还有一定的改进空间。例如:Dynamic Memory Loaction, Page Replication。

Dynamic Memory Relocation
我们来想一下这个情况:MySQL的线程分为两种,用户线程(SQL执行线程)和内部线程(内部功能,如:flush,io,master等)。对于用户线程来说随机性相当的强,但对于内部线程来说他们的行为以及所要访问的内存区域其实是相对固定且可以预测的。如果能对于这把这部分内存集中到这些内存线程所在的core上的时候,就能减少大量Remote Access,潜在的提升例如Page Flush,Purge等功能的吞吐量,甚至可以提高MySQL Crash后Recovery的速度(由于recovery是单线程)。
那是否能在Interleave模式下,把那些明显应该聚集在一个CPU上的内存集中在一起呢?
很可惜,Dynamic Memory Relocation这种技术目前只停留在理论和实验阶段。我们来看下难点:要做到按照线程的行为动态的调整page在memory的分布,就势必需要做线程和内存的实时监控(profile)。对于Memory Access这种非常异常频繁的底层操作来说增加profile入口的性能损耗是极大的。在 关于CPU Cache程序应该知道的那些事的评论中我也提到过,这个道理和为什么Linux没有全局监控CPU L1/L2 Cache命中率工具的原因是一样的。当然优化不会就此停步。上文提到的Carrefour算法和Linux社区的Auto NUMA patch都是积极的尝试。什么时候内存profile出现硬件级别,类似于CPU中 PMU 的功能时,动态内存规划就会展现很大的价值,甚至会作为Linux Kernel的一个内部功能来实现。到那时我们再回过头来审视这个方案的实际价值。

Page Replication
再来看一下这些情况:一些动态加载的库,把他们放在任何一个线程所在的CPU都会导致其他CPU上线程的执行效率下降。而这些共享数据往往读写比非常高,如果能把这些数据的副本在每个Memory Zone内都放置一份,理论上会带来较大的性能提升,同时也减少在inter-connect上出现的瓶颈。实时上,仍然是上文提到的Carrefour也做了这样的尝试。由于缺乏硬件级别(如MESI协议的硬件支持)和操作系统原生级别的支持,Page Replication在数据一致性上维护的成本显得比他带来的提升更多。因此这种尝试也仅仅停留在理论阶段。当然,如果能得到底层的大力支持,相信这个方案还是有极大的实际价值的。

究竟是哪里出了问题

NUMA的问题?
NUMA本身没有错,是CPU发展的一种必然趋势。但是NUMA的出现使得操作系统不得不关注内存访问速度不平均的问题。

Linux Kernel内存分配策略的问题?
分配策略的初衷是好的,为了内存更接近需要他的线程,但是没有考虑到数据库这种大规模内存使用的应用场景。同时缺乏动态调整的功能,使得这种悲剧在内存分配的那一刻就被买下了伏笔。

数据库设计者不懂NUMA?
数据库设计者也许从一开始就不会意识到NUMA的流行,或者甚至说提供一个透明稳定的内存访问是操作系统最基本的职责。那么在现状改变非常困难的情况下(下文会提到为什么困难)是不是作为内存使用者有义务更好的去理解使用NUMA?

总结

其实无论是NUMA还是Linux Kernel,亦或是程序开发他们都没有错,只是还做得不够极致。如果NUMA在硬件级别可以提供更多低成本的profile接口;如果Linux Kernel可以使用更科学的动态调整策略;如果程序开发人员更懂NUMA,那么我们完全可以更好的发挥NUMA的性能,使得无限横向扩展CPU核数不再是一个梦想。

Tagged as: , , Comments Off
10Nov/15

mac 编译openresty常见问题

Posted by Nick Xu

第一次装openresty没有事情的,然而重装MAC后,再装openresty出现了问题。安装openresty可以直接

git clone https://github.com/openresty

下来,运行make,自动下载依赖包,也可以直接到 http://openresty.org/ 下载打包好的。

1.openssl缺少错误如下

1
2
3
4
5
6
./configure: error: SSL modules require the OpenSSL library.
You can either do not enable the modules, or install the OpenSSL library
into the system, or build the OpenSSL library statically from the source
with nginx by using --with-openssl=<path> option.

ERROR: failed to run command: sh ./configure --prefix=/usr/local/openresty/nginx \…

缺少openssl库,那就把本机安装路径告诉它吧:

./configure —with-openssl=/usr/local/Cellar/openssl/1.0.2c

2.openssl源码安装错误

1
2
3
4
5
6
7
8
9
10
/Applications/Xcode.app/Contents/Developer/usr/bin/make -f objs/Makefile
cd /usr/local/Cellar/openssl/1.0.2c \
	&& if [ -f Makefile ]; then /Applications/Xcode.app/Contents/Developer/usr/bin/make clean; fi \
	&& ./config --prefix=/usr/local/Cellar/openssl/1.0.2c/.openssl no-shared  no-threads \
	&& /Applications/Xcode.app/Contents/Developer/usr/bin/make \
	&& /Applications/Xcode.app/Contents/Developer/usr/bin/make install LIBDIR=lib
/bin/sh: ./config: No such file or directory
make[2]: *** [/usr/local/Cellar/openssl/1.0.2c/.openssl/include/openssl/ssl.h] Error 127
make[1]: *** [build] Error 2
make: *** [all] Error 2

上了github,https://github.com/torch/image/issues/16,查查,明白了,这里要的是source code,不是安装路径,够坑的了吧。

那么好了,到https://www.openssl.org/下载了最新的,openssl.1.0.2c版本,到bundle目录里:

export KERNEL_BITS=64
./configure --with-cc-opt='-I/usr/local/Cellar/pcre/8.36/include/' \
       --with-ld-opt='-L/usr/local/Cellar/pcre/8.36/lib' \
       --with-openssl=bundle/openssl-OpenSSL_1_0_2c -j2

报了个warning:

1
2
3
WARNING! If you wish to build 64-bit library, then you have to
         invoke './Configure darwin64-x86_64-cc' *manually*.
         You have about 5 seconds to press Ctrl-C to abort.

看着是openssl与darwin的版本不兼容问题,后来发现是新版的openssl与nginx兼容问题。

3.pcre依赖报错

1
2
3
4
5
ld: symbol(s) not found for architecture x86_64 collect2: ld 
returned 1 exit status make[2]: *** [objs/nginx] 
Error 1 make[1]: *** [build] 
Error 2 make: *** 
[all] Error 2

找一找,发现了问题解决方案,是pcre依赖包没带上,也就是正则匹配依赖包的问题了:

在github上找到了issuse相关信息: https://github.com/openresty/ngx_openresty/issues/3#issuecomment-120227290

最后在issuse上问道了agentzh的解决方案,agentzh的makefile里在处理新版nginx与openssl依赖上的一点问题,后来他更新了github仓库:

1
2
3
4
5
6
export KERNEL_BITS=64
./configure --with-cc-opt='-I/usr/local/Cellar/pcre/8.37/include/' \
       --with-ld-opt='-L/usr/local/Cellar/pcre/8.37/lib' \
       --with-openssl=$HOME/work/openssl-1.0.2d -j9
make -j9
sudo make install

在我这里(MAC OSX 10.10.4)运行起来是没问题的。

两个问题:

1.openssl依赖,要用源码,要export告诉系统环境变量

2.pcre包要手动加上去。

Tagged as: , Comments Off
3Nov/15

nginx log 记录请求的头信息

Posted by Nick Xu

记录访问的log,为了在出现特殊情况时,方便检查出现问题的地方。

log_format access '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';
access_log /var/log/51yip.log access;

记录下,用户ip,用户真实ip,用户访问时间,用户访问的游览器信息等。这里面这样的$remote_addr变量是从哪来的呢?

$arg_PARAMETER 这个变量包含在查询字符串时GET请求PARAMETER的值。
$args 这个变量等于请求行中的参数。
$binary_remote_addr 二进制码形式的客户端地址。
$body_bytes_sent
$content_length 请求头中的Content-length字段。
$content_type 请求头中的Content-Type字段。
$cookie_COOKIE cookie COOKIE的值。
$document_root 当前请求在root指令中指定的值。
$document_uri 与$uri相同。
$host 请求中的主机头字段,如果请求中的主机头不可用,则为服务器处理请求的服务器名称。
$is_args 如果$args设置,值为"?",否则为""。
$limit_rate 这个变量可以限制连接速率。
$nginx_version 当前运行的nginx版本号。
$query_string 与$args相同。
$remote_addr 客户端的IP地址。
$remote_port 客户端的端口。
$remote_user 已经经过Auth Basic Module验证的用户名。
$request_filename 当前连接请求的文件路径,由root或alias指令与URI请求生成。
$request_body 这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义。
$request_body_file 客户端请求主体信息的临时文件名。
$request_completion 请求完成
$request_method 这个变量是客户端请求的动作,通常为GET或POST。包括0.8.20及之前的版本中,这个变量总为main request中的动作,如果当前请求是一个子请求,并不使用这个当前请求的动作。
$request_uri 这个变量等于包含一些客户端请求参数的原始URI,它无法修改,请查看$uri更改或重写URI。
$schemeHTTP 方法(如http,https)。按需使用,例:
rewrite ^(.+)$ $scheme://example.com$1 redirect;

$server_addr 服务器地址,在完成一次系统调用后可以确定这个值,如果要绕开系统调用,则必须在listen中指定地址并且使用bind参数。
$server_name 服务器名称。
$server_port 请求到达服务器的端口号。
$server_protocol 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
$uri 请求中的当前URI(不带请求参数,参数位于$args),可以不同于浏览器传递的$request_uri的值,它可以通过内部重定向,或者使用index指令进行修改。

上面的这些是nginx 支持一些内置的变量,当然我们可以自定义,例如

$http_x_forwarded_for

这个变量就是自定义的,用来获得用了代理用户的真实IP。

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Tagged as: , , Comments Off
   
site
site