Wetts's blog

Stay Hungry, Stay Foolish.

0%

转自:http://www.mongoing.com/blog/file-storage

参考:http://blog.mlab.com/2014/01/how-big-is-your-mongodb/

数据库文件类型

MongoDB 的数据库文件主要有 3 种:

  • journal 日志文件
  • namespace 表名文件
  • data 数据及索引文件

日志文件

跟一些传统数据库不同,MongoDB 的日志文件只是用来在系统出现宕机时候恢复尚未来得及同步到硬盘的内存数据。日志文件会存放在一个分开的目录下面。启动时候 MongoDB 会自动预先创建 3 个每个为 1G 的日志文件(初始为空)。除非你真的有持续海量数据并发写入,一般来说 3 个 G 已经足够。

命名文件 dbname.ns

这个文件用来存储整个数据库的集合以及索引的名字。这个文件不大,默认 16M,可以存储 24000 个集合或者索引名以及那些集合和索引在数据文件中得具体位置。通过这个文件 MongoDB 可以知道从哪里去开始寻找或插入集合的数据或者索引数据。这个值可以通过参数调整至2G。

数据文件 dbname.0, dbname.1,… dbname.n

MongoDB 的数据以及索引都存放在一个或者多个 MongoDB 数据文件里。第一个数据文件会以“数据库名.0”命名,如 my-db.0。这个文件默认大小是 64M,在接近用完这个 64M 之前,MongoDB 会提前生成下一个数据文件如 my-db.1。数据文件的大小会 2 倍递增。第二个数据文件的大小为 128M,第三个为 256M。一直到了 2G 以后就会停止,一直按这个 2G 这个大小增加新的文件。


当然 MongoDB还 会生成一些临时文件如 _tmpmongod.lock等, 不过他们跟我们的讨论都没有太大相关性。

数据文件结构

Extent

在每一个数据文件内,MongoDB 把所存储的 BSON 文档的数据和 B 树索引组织到逻辑容器“Extent”里面。如下图所示(my-db.1 和 my-db.2 是数据库的两个数据文件):

Extent

  • 一个文件可以有多个 Extent
  • 每一个 Extent 只会包含一个集合的数据或者索引
  • 同一个集合的数据或索引可以分布在多个 Extent 内。这几个 Extent 也可以分步于多个文件内
  • 同一个 Extent 不会又有数据又有索引

Record 记录

在每个 Extent 里面存放有多个”Record“, 每一个记录里包含一个记录头以及 MongoDB 的 BSON 文档,以及一些额外的 padding 空间。Padding 是 MongoDB 在插入记录时额外分配一些未用空间,这样将来文档变大的时候不至于需要把文档迁移到别处。 记录头以整个记录的大小开始,包括该记录自己的位置以及前一个记录和后一个记录的位置。可以想象成一个 Double Linked List。

Record

数据库大小参数

在之前的基础上,我们可以来理解一下 db.stats() 里面关于空间大小参数的含义。

dataSize

dataSize 是最接近真实数据大小的一个参数。你可以用来检查你的数据有多少。这个大小包括了数据库(或者集合)的每条记录的总和。注意每条记录除了 BSON 文档外还有 header 及 padding 这些额外开销。所以实际大小会比真正数据所占空间会稍大。

当删除文档的时候,这个参数会相应变小因为它是所有文档数的大小总和。如果你的文档没有删除,只是文档内部的字段被删除或缩小,则不会对 dataSize 有影响。原因就是因为文档所在记录还在,并且整条记录所占空间并无改动,只不过记录内的未用空间变多了而已。

dataSize

storageSize

这个参数等于数据库或者某个集合所有用到的 Data Extents 的总和。注意这个数字会大于 dataSize 因为 Extent 里面会有一些删除文档之后留下来的碎片(deleted)。及时你的 storageSize 大出 dataSize 很多,这个也不一定就是很糟糕的情况。如果有新插入的文档小于或等于碎片的大小,MongoDB 会重新利用这个碎片来存储新的文档。不过在这之前这些碎片将一直会被保留在那里占用空间。由于这个原因,你删除文档的时候这个参数不会变小。

storageSize

碎片问题会因为运行的时间变长而变得严重。你可以通过 compact 命令来进行碎片清理或者通过新架一台从机复制所有数据,然后变成主节点的方式来解决这些碎片。

fileSize

这个参数只在数据库上有效,指的是实际文件系统中用到的文件的大小。它包括所有的数据 Extents 的总和,索引 Extent 的总和,以及一些未被分配的空间。之前提到 MongoDB 会对数据库文件创建时候进行预分配,例如最小就是 64M,哪怕你只有几百个 KB 的数据。所以这个参数可能会比实际的数据大小会大不少。这些额外未用空间是用来保证 MongoDB 可以在新的数据写入时候快速的分配新的 Extent,避免引起磁盘空间分配引起的延迟。

fileSize

值得注意的是,当你删除文档,或甚至集合和索引,这个参数不会变小。换句话说,数据库所使用的硬盘空间只会上升(或者不变),而不会因为删除数据而变小。当然需要知道的是这并不就意味着浪费,只是说有很多预留空间而已。

git config --global http.proxy "localhost:8787"

端口可以设置为Lantern的代理端口

所以需要默认不设置代理:

git config --global --unset http.proxy

转自:http://www.jb51.net/article/71148.htm

限制某个IP同一时间段的访问次数

如何设置能限制某个IP某一时间段的访问次数是一个让人头疼的问题,特别面对恶意的ddos攻击的时候。其中CC攻击(Challenge Collapsar)是DDOS(分布式拒绝服务)的一种,也是一种常见的网站攻击方法,攻击者通过代理服务器或者肉鸡向向受害主机不停地发大量数据包, 造成对方服务器资源耗尽,一直到宕机崩溃。

cc攻击一般就是使用有限的ip数对服务器频繁发送数据来达到攻击的目的,nginx可以通过HttpLimitReqModul和HttpLimitZoneModule配置来限制ip在同一时间段的访问次数来防cc攻击。

HttpLimitReqModul用来限制连单位时间内连接数的模块,使用limit_req_zone和limit_req指令配合使用来达到限制。一旦并发连接超过指定数量,就会返回503错误。

HttpLimitConnModul用来限制单个ip的并发连接数,使用limit_zone和limit_conn指令

这两个模块的区别前一个是对一段时间内的连接数限制,后者是对同一时刻的连接数限制

HttpLimitReqModul 限制某一段时间内同一ip访问数实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
http{
...
#定义一个名为allips的limit_req_zone用来存储session,大小是10M内存,
#以$binary_remote_addr 为key,限制平均每秒的请求为20个,
#1M能存储16000个状态,rete的值必须为整数,
#如果限制两秒钟一个请求,可以设置成30r/m
limit_req_zone $binary_remote_addr zone=allips:10m rate=20r/s;
...
server{
...
location {
...
#限制每ip每秒不超过20个请求,漏桶数burst为5
#brust的意思就是,如果第1秒、2,3,4秒请求为19个,
#第5秒的请求为25个是被允许的。
#但是如果你第1秒就25个请求,第2秒超过20的请求返回503错误。
#nodelay,如果不设置该选项,严格使用平均速率限制请求数,
#第1秒25个请求时,5个请求放到第2秒执行,
#设置nodelay,25个请求将在第1秒执行。
limit_req zone=allips burst=5 nodelay;
...
}
...
}
...
}

HttpLimitZoneModule 限制并发连接数实例

limit_zone只能定义在http作用域,limit_conn可以定义在http server location作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
http{
...
#定义一个名为one的limit_zone,大小10M内存来存储session,
#以$binary_remote_addr 为key
#nginx 1.18以后用limit_conn_zone替换了limit_conn
#且只能放在http作用域
limit_conn_zone one $binary_remote_addr 10m;
...
server{
...
location {
...
limit_conn one 20; #连接数限制
#带宽限制,对单个连接限数,如果一个ip两个连接,就是500x2k
limit_rate 500k;
...
}
...
}
...
}

服务器全局限IP

1
2
3
#vi nginx.conf
allow 10.57.22.172;
deny all;

指定目录的IP访问限制

实现重点

正则表达式中()和|的使用,()代表一个原则,|代表或

nginx的location匹配规则中,有一条按照文件顺序进行正则匹配(ps:可以把需要匹配的目录放置在server模块开始的位置)

allow和deny的使用

示例

目录结构

1
2
3
4
5
根目录/srv/
test1 / -- hello.php
test2/ -- hello.php
test3/ -- hello.php
test4/ -- {hello.php,1.php,2.php}

访问需求

对于test1,test2目录,只允许指定的192.168.1.101ip地址访问,禁止其它ip访问

对于其他目录的php程序,所有ip地址均可以访问

实现的nginx配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#指定目录实行白名单访问机制
location ~ ^/(test1|test2)/ {
allow 192.168.1.101;
deny all;

root /srv/;
fastcgi_param HTTPS on;
include /etc/nginx/fastcgi_params;
fastcgi_pass php5_fpm;
}

# proxy the PHP scripts to fpm
location ~ \.php$ {
root /srv/;
fastcgi_param HTTPS on;
include /etc/nginx/fastcgi_params;
fastcgi_pass php5_fpm;
}

注意事项:

  1. deny 一定要加一个ip,否则直接跳转到403,不往下执行了;如果403默认页是同一域名下,会造成死循环访问;
  2. allow的ip段

从允许访问的段位从小到大排列,如127.0.0.0/24

下面才能是10.10.0.0/16

24表示子网掩码:255.255.255.0

16表示子网掩码:255.255.0.0

8表示子网掩码:255.0.0.0

  1. deny all;结尾 表示除了上面allow的其他都禁止

如:

1
deny 192.168.1.1;           allow 127.0.0.0/24;           allo w 192.168.0.0/16;           allow 10.10.0.0/16;           deny all;

转自:http://redisbook.readthedocs.io/en/latest/datatype/sorted_set.html

有序集

REDIS_ZSET(有序集)是 ZADD、ZCOUNT 等命令的操作对象,它使用 REDIS_ENCODING_ZIPLIST 和 REDIS_ENCODING_SKIPLIST 两种方式编码:

zset

编码的选择

在通过 ZADD 命令添加第一个元素到空 key 时,程序通过检查输入的第一个元素来决定该创建什么编码的有序集。

如果第一个元素符合以下条件的话,就创建一个 REDIS_ENCODING_ZIPLIST 编码的有序集:

  • 服务器属性 server.zset_max_ziplist_entries 的值大于 0(默认为 128)。
  • 元素的 member 长度小于服务器属性 server.zset_max_ziplist_value 的值(默认为 64)。

否则,程序就创建一个 REDIS_ENCODING_SKIPLIST 编码的有序集。

编码的转换

对于一个 REDIS_ENCODING_ZIPLIST 编码的有序集,只要满足以下任一条件,就将它转换为 REDIS_ENCODING_SKIPLIST 编码:

  • ziplist 所保存的元素数量超过服务器属性 server.zset_max_ziplist_entries 的值(默认值为 128)
  • 新添加元素的 member 的长度大于服务器属性 server.zset_max_ziplist_value 的值(默认值为 64)

ZIPLIST 编码的有序集

当使用 REDIS_ENCODING_ZIPLIST 编码时,有序集将元素保存到 ziplist 数据结构里面。

其中,每个有序集元素以两个相邻的 ziplist 节点表示,第一个节点保存元素的 member 域,第二个元素保存元素的 score 域。

多个元素之间按 score 值从小到大排序,如果两个元素的 score 相同,那么按字典序对 member 进行对比,决定哪个元素排在前面,哪个元素排在后面。

1
2
3
4
5
6
7
8
9
          |<--  element 1 -->|<--  element 2 -->|<--   .......   -->|

+---------+---------+--------+---------+--------+---------+---------+---------+
| ZIPLIST | | | | | | | ZIPLIST |
| ENTRY | member1 | score1 | member2 | score2 | ... | ... | ENTRY |
| HEAD | | | | | | | END |
+---------+---------+--------+---------+--------+---------+---------+---------+

score1 <= score2 <= ...

虽然元素是按 score 域有序排序的,但对 ziplist 的节点指针只能线性地移动,所以在 REDIS_ENCODING_ZIPLIST 编码的有序集中,查找某个给定元素的复杂度为 $O(N)$。

每次执行添加/删除/更新操作都需要执行一次查找元素的操作,因此这些函数的复杂度都不低于 $O(N)$,至于这些操作的实际复杂度,取决于它们底层所执行的 ziplist 操作。

SKIPLIST 编码的有序集

当使用 REDIS_ENCODING_SKIPLIST 编码时, 有序集元素由 redis.h/zset 结构来保存:

1
2
3
4
5
6
7
8
9
10
11
12
/*
* 有序集
*/
typedef struct zset {

// 字典
dict *dict;

// 跳跃表
zskiplist *zsl;

} zset;

zset 同时使用字典和跳跃表两个数据结构来保存有序集元素。

其中,元素的成员由一个 redisObject 结构表示,而元素的 score 则是一个 double 类型的浮点数,字典和跳跃表两个结构通过将指针共同指向这两个值来节约空间(不用每个元素都复制两份)。

下图展示了一个 REDIS_ENCODING_SKIPLIST 编码的有序集:

skiplist

通过使用字典结构,并将 member 作为键,score 作为值,有序集可以在 $O(1)$ 时间复杂度内:

  • 检查给定 member 是否存在于有序集(被很多底层函数使用);
  • 取出 member 对应的 score 值(实现 ZSCORE 命令)。

另一方面,通过使用跳跃表,可以让有序集支持以下两种操作:

  • 在 $O(logN)$ 期望时间、$O(N)$ 最坏时间内根据 score 对 member 进行定位(被很多底层函数使用);
  • 范围性查找和处理操作,这是(高效地)实现 ZRANGE、ZRANK 和 ZINTERSTORE 等命令的关键。

通过同时使用字典和跳跃表,有序集可以高效地实现按成员查找和按顺序查找两种操作。

参考:https://redis.io/topics/security

Protected mode

Unfortunately many users fail to protect Redis instances from being accessed from external networks. Many instances are simply left exposed on the internet with public IPs. For this reasons since version 3.2.0, when Redis is executed with the default configuration (binding all the interfaces) and without any password in order to access it, it enters a special mode called protected mode. In this mode Redis only replies to queries from the loopback interfaces, and reply to other clients connecting from other addresses with an error, explaining what is happening and how to configure Redis properly.

We expect protected mode to seriously decrease the security issues caused by unprotected Redis instances executed without proper administration, however the system administrator can still ignore the error given by Redis and just disable protected mode or manually bind all the interfaces.


Redis 从 3.2 开始加强安全管理,如果 redis 没有设置密码,那么 redis 客户端只能从本地进行访问,如果是从其他机器连接过来访问的,就会报错误。

保护模式下,可以查看参数 protected-mode 值为 yes

解决办法

  1. 为 Redis 设置密码
  2. protected-mode 值设置为为 no
    1
    config set protected-mode no

转自:https://my.oschina.net/u/1590519/blog/342765

当主键”_id“不存在时,都是添加一个新的文档,但主健”_id“存在时,就有些不同了。

mongodb 的 save 和 insert 函数都可以向 collection 里插入数据,但两者是有两个区别:

  1. 使用 save 函数里,如果原来的对象不存在,那他们都可以向 collection 里插入数据,如果已经存在,save 会调用 update 更新里面的记录,而 insert 则会忽略操作

  2. insert 可以一次性插入一个列表,而不用遍历,效率高,save 则需要遍历列表,一个个插入。

看下这两个函数的原型就清楚了,直接输入函数名便可以查看原型,下面标红的部分就是实现了循环,对于远程调用来说,是一性次将整个列表 post 过来让 mongodb 去自己处理,效率会高些

insert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> db.user.insert
function (obj, _allow_dot) {
if (!obj) {
throw "no object passed to insert!";
}
if (!_allow_dot) {
this._validateForStorage(obj);
}
if (typeof obj._id == "undefined" && !Array.isArray(obj)) {
var tmp = obj;
obj = {_id:new ObjectId};
for (var key in tmp) {
obj[key] = tmp[key];
}
}
this._db._initExtraInfo();
this._mongo.insert(this._fullName, obj);
this._lastID = obj._id;
this._db._getExtraInfo("Inserted");
}

save

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> db.user.save
function (obj) {
if (obj == null || typeof obj == "undefined") {
throw "can't save a null";
}
if (typeof obj == "number" || typeof obj == "string") {
throw "can't save a number or string";
}
if (typeof obj._id == "undefined") {
obj._id = new ObjectId;
return this.insert(obj);
} else {
return this.update({_id:obj._id}, obj, true);
}
}

转自:https://docs.mongodb.com/manual/reference/database-references/

MongoDB does not support joins. In MongoDB some data is denormalized, or stored with related data in documents to remove the need for joins. However, in some cases it makes sense to store related information in separate documents, typically in different collections or databases.

MongoDB applications use one of two methods for relating documents:

  • Manual references where you save the _id field of one document in another document as a reference. Then your application can run a second query to return the related data. These references are simple and sufficient for most use cases.
  • DBRefs are references from one document to another using the value of the first document’s _id field, collection name, and, optionally, its database name. By including these names, DBRefs allow documents located in multiple collections to be more easily linked with documents from a single collection.

To resolve DBRefs, your application must perform additional queries to return the referenced documents. Many drivers have helper methods that form the query for the DBRef automatically. The drivers do not automatically resolve DBRefs into documents.

DBRefs provide a common format and type to represent relationships among documents. The DBRef format also provides common semantics for representing links between documents if your database must interact with multiple frameworks and tools.

Unless you have a compelling reason to use DBRefs, use manual references instead.

天气接口

  • http://wthrcdn.etouch.cn/weather_mini?citykey=101010100
  • http://wthrcdn.etouch.cn/weather_mini?city=北京

转自:http://www.lc365.net/blog/b/23997/

日志记录中HTTP状态码出现499错误有多种情况,我遇到的一种情况是nginx反代到一个永远打不开的后端,就这样了,日志状态记录是499、发送字节数是0。

499错误是什么?让我们看看NGINX的源码中的定义:

1
2
3
4
5
ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
ngx_string(ngx_http_error_497_page), /* 497, http to https */
ngx_string(ngx_http_error_404_page), /* 498, canceled */
ngx_null_string, /* 499, client has closed connection */

可以看到,499对应的是 “client has closed connection”。这很有可能是因为服务器端处理的时间过长,客户端“不耐烦”了。

转自:http://blog.163.com/wm_at163/blog/static/132173490201252610424458/

有时候,键值对的查询方式并不能满足我们的需求,我们有如下一个集合:

1
2
3
4
5
6
7
> db.foo.find()
{ “_id” : ObjectId(“4e9165cf717ed94f8289ac0c”), “bar” : “baz”, “count” : 35 }
{ “_id” : ObjectId(“4e916661739f1da5452a4dfe”), “bar” : “bazz”, “count” : 3 }
{ “_id” : ObjectId(“4e9165cf717ed94f8289ac0d”), “bar” : “baz”, “count” : 35 }
{ “_id” : ObjectId(“4e928bf8735a86e2c6f848ed”), “apple” : 1, “banana” : 6, “peach” : 3 }
{ “_id” : ObjectId(“4e928c17735a86e2c6f848ee”), “apple” : 1, “spinach” : 4, “watermelon” : 4 }
{ “_id” : ObjectId(“4e928d8a735a86e2c6f848ef”), “bar” : “baz”, “banana” : “baz” }

需要返回有两个字段相同的文档,也就是要返回如下文档

1
2
{ “_id” : ObjectId(“4e928c17735a86e2c6f848ee”), “apple” : 1, “spinach” : 4, “watermelon” : 4 }
{ “_id” : ObjectId(“4e928d8a735a86e2c6f848ef”), “bar” : “baz”, “banana” : “baz” }

就需要使用”$where“并借助javascript来做了

1
2
3
4
5
6
7
8
9
10
> db.foo.find({“$where”:function(){
… for(var current in this){
… for(var other in this){
… if(current != other && this[current] == this[other]){
… return true;
… }
… }
… }
… return false;
… }})

如果返回true,文档作为结果的一部分被返回;如果为false,则不会返回。

$where查询有以下几种写法:

1
2
> db.foo.find({“$where”:”this.x+this.y==10″})
> db.foo.find({“$where”:”function(){return this.x+this.y==10;}”})

tips:不是非常必要时,一定要避免使用”$where”查询,因为效率太低,相当的。文档在MongoDB中是以BSON格式保存的,在$where查询时,每个文档都要从BSON转换为javascript对象然后再通过”$where”中的表达式来运行。有时可以将常规查询作为前置过滤,再使用”$where”查询对结果进行调优