MongoDB TTL索引不起作用

最近在开发一个基于MongoDB的分布式锁功能,其中需要利用TTL索引解决锁释放问题,结果索引是建成了,但就是无法删除超时的文档。
翻来覆去用过各种客户端工具重建索引,调整保存数据和建立索引顺序,查看serverStatus等,就是无法触发超时删除功能。
把stackoverflow相同问题的答案都过了一遍,就是不行。
最后突然想到像这种互联网型的数据库,日志还是比较靠谱,为毛不先看看日志,于是登服务器看日志,打头一段就是:

1
2
3
4
5
6
7
Fri Mar 17 14:34:05.152 I FTDC     [initandlisten] Initializing full-time diagnostic data capture with directory '/root/deploy/mongodb/db/diagnostic.data'
Fri Mar 17 14:34:05.152 I STORAGE [initandlisten]
Fri Mar 17 14:34:05.152 I STORAGE [initandlisten] ** WARNING: mongod started without --replSet yet 1 documents are present in local.system.replset
Fri Mar 17 14:34:05.152 I STORAGE [initandlisten] ** Restart with --replSet unless you are doing maintenance and no other clients are connected.
Fri Mar 17 14:34:05.152 I STORAGE [initandlisten] ** The TTL collection monitor will not start because of this.
Fri Mar 17 14:34:05.152 I STORAGE [initandlisten] **
Fri Mar 17 14:34:05.152 I STORAGE [initandlisten] For more info see http://dochub.mongodb.org/core/ttlcollections

wtf,为了图省事用的一个以前的库,结果这个库曾经做过某个复制集中的一员,后来复制集取消了,但复制集相关的信息还在,所以TTL Monitor根本不会启动,估计也是为了数据完整性。
最后清空system.replset集合中的相关数据,TTL正常。✌️

修复SSH "no matching host key type found" 错误

今天发现家里的NAS小半年没关机了,打算关机歇歇,结果发现ssh上不去,提示错误

1
Unable to negotiate with 10.0.0.7 port 22: no matching host key type found. Their offer: ssh-dss

一时无解,以前没出现过,有可能是因为换了路由导致,也有可能是因为固件升级。
最后google到一个解决办法,增加参数

1
ssh -o HostKeyAlgorithms=+ssh-dss root@10.0.0.7

  • -o 增加参数
  • HostKeyAlgorithms 主机key算法

指定了主机key算法后顺利登入。

修改Docker仓库,提高拉取速度

一直以为挂着vpn世界任我闯,但今天想从官方的docker store 拉取个mongodb的镜像都拉不下来,不是TLS handshake timeout就是下载阻塞,没辙只能改成国内的DaoCloud,改了后3分钟拉下来。
首先在DaoCloud的镜像市场找到想要的镜像,然后拉取的时候改为从DaoCloud拉取

1
docker pull daocloud.io/library/mongo:3.4.1

但右侧显示的最新版本可能不是官方的最新版本,所以需要到详情旁边的版本tab里找到想要的版本,改版本号。

利用Linux Ramfs处理临时文件

在系统中总会有各种各样的临时文件的读写需求,如果在linux下完全可以使用ramfs来提高读写速度,先做个简单对比。
以下测试都在同台机器,因为就简单看看没必要贴参数。

非ramfs的目录

写:8.1m/s,94 seconds

1
2
3
4
5
6
7
8
time dd if=/dev/zero of=test.bin bs=8k count=100000 oflag=direct
100000+0 records in
100000+0 records out
819200000 bytes (819 MB) copied, 94.2261 s, 8.7 MB/s

real 1m34.231s
user 0m0.079s
sys 0m6.650s

读:89.9m/s,9 seconds

1
2
3
4
dd if=./test.bin bs=8k count=100000 of=/dev/null 
100000+0 records in
100000+0 records out
819200000 bytes (819 MB) copied, 9.11253 s, 89.9 MB/s

下面先来看看ramfs在详细说下。

1
2
3
mkdir ramfstest
mount -t ramfs none ramfstest/
cat /proc/mounts

  • 新建个目录
  • 以ramfs类型挂载目录
  • 检查下

ramfs目录

写:431m/s,1.9 seconds

1
2
3
4
5
6
7
8
time dd if=/dev/zero of=testram.bin bs=8k count=100000
100000+0 records in
100000+0 records out
819200000 bytes (819 MB) copied, 1.89941 s, 431 MB/s

real 0m1.901s
user 0m0.014s
sys 0m1.885s

读:2.6g/s,300 milliseconds

1
2
3
4
dd if=./testram.bin bs=8k count=100000 of=/dev/null 
100000+0 records in
100000+0 records out
819200000 bytes (819 MB) copied, 0.319219 s, 2.6 GB/s

可以看到差别还是巨大的,所以当有些临时文件的读写、临时缓冲区之类的需求完全可以使用ramfs替代物理的磁盘,为什么说是 临时,因为ramfs还有个显著的好处就是当unmount这个目录的时候,目录中的所有内容都会自动消失。

其他相关

linux下内存文件系统有三种Ramdisk、ramfs、tmpfs。

  • Ramdisk 对外表现就像一块物理磁盘可格式化,容量大小固定,可以所以unmount,只要不重启linux数据都在。
  • ramfs 使用内存作为磁盘,默认大小为内存的一半,可改变,不使用交换分区,可以使用虚拟内存。
  • tmpfs 使用内存作为磁盘,大小创建时固定,可以使用交换分区,无法使用虚拟内存.

MongoDB内嵌数组文档条件查询

MongoDB的查询一项简单高效,但要对内嵌的数组文档查询并且按条件返回匹配的项目则需要费点小事。
假设MongoDB中有个集合的内容为:

Blog
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
"_id" : ObjectId("58a3b24f934f070f5e34699b"),
"className" : "mongo.service.Blog",
"author" : "test1",
"post" : "This page displays a list of Java EE applications and stand-alone application modules that have been installed to this domain.",
"comments" : [
{
"author" : "a1",
"date" : ISODate("2017-02-15T01:43:43.092Z"),
"content" : "nice"
},
{
"author" : "a2",
"date" : ISODate("2017-02-15T01:43:43.092Z"),
"content" : "i dont give sh*t"
},
{
"author" : "a3",
"date" : ISODate("2017-02-15T01:43:43.092Z"),
"content" : "dope"
}
]
}
{
"_id" : ObjectId("58a3b24f934f070f5e34699c"),
"className" : "mongo.service.Blog",
"author" : "test1",
"post" : "The deployment has been successfully installed.",
"comments" : [
{
"author" : "a4",
"date" : ISODate("2017-02-15T01:43:43.109Z"),
"content" : "gg"
},
{
"author" : "a5",
"date" : ISODate("2017-02-15T01:43:43.109Z"),
"content" : "good job"
}
]
}

comments是个内嵌的数组文档,现在假设我要查找comments里author等于a3的评论,如使用find的话

1
db.Blog.find({"comments.author":"a3"});

这种查询会把匹配这个条件的整个文档返回出来,包括那些author并不是a3的评论,因为查询匹配父文档
这样的返回结果显然是不符合要求,并且在内嵌文档数量很多的情况下很浪费资源。
所以我们只能使用聚合查询解决这种问题。

Aggregate
1
2
3
4
5
6
db.Blog.aggregate(
{"$match":{"comments.author" : "a3"}},
{"$project":{"_id":1,"comments":"$comments"}},
{"$unwind":"$comments"},
{"$match":{"comments.author" : "a3"}}
)

  • 第一个$match可有可无,假设文档中有大量的数据,可以先通过条件定位到主文档
  • $project投射哪些字段,这里提取了id和comments(提取了文档中的$comments这个字段并且改名为comments)
  • $unwind将数组拆成一条条独立的文档
  • $match最后的这个条件尝试匹配这些独立的文档。

所以最终的返回结果。

1
2
3
4
5
6
7
8
{ 
"_id" : ObjectId("58a3b24f934f070f5e34699b"),
"comments" : {
"author" : "a3",
"date" : ISODate("2017-02-15T01:43:43.092+0000"),
"content" : "dope"
}
}

匹配的数据虽然由comments数组变为对象但仍然是一个内嵌的文档,我们需要转换成一个独立的主文档,这样在某些情况下更加方便,比如java领域对象中的序列化,所以需要将上面Aggregate改改。

Aggregate
1
2
3
4
5
6
7
8
9
10
11
12
db.Blog.aggregate(
{"$match":{"comments.author" : "a3"}},
{"$unwind":"$comments"},
{"$match":{"comments.author" : "a3"}},
{"$project":{
"_id":0,
"author":"$comments.author",
"date":"$comments.date",
"content":"$comments.content"
}
}
)

  • 最后一步增加了投射$project,将主文档的_id隐藏,匹配的内嵌文档中每个字段投射出来,返回结果变为。
1
2
3
4
5
{ 
"author" : "a3",
"date" : ISODate("2017-02-15T01:43:43.092+0000"),
"content" : "dope"
}

将数据整成想要的样子完全是为了在开发中能方便的进行对象序列化,以MongoDB ORM框架Morphia为例,上面集合对应的JavaBean为

Blog
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author akalxs@gmail.com
*/
@Data
@Entity("Blog")
public class Blog {
@Id
private ObjectId _id;
private String author;
private String post;
@Embedded
private List<Comment> comments;
}

Comment
1
2
3
4
5
6
7
8
9
/**
* @author akalxs@gmail.com
*/
@Data
public class Comment {
private String author;
private Date date;
private String content;
}

测试代码

Test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void getCommentByAuthor() {
Datastore ds = getDatastore();
Query q = ds.createQuery(Blog.class).field("comments.author").equal("a3");
ds.createAggregation(Blog.class)
.match(q)
.unwind("comments")
.match(q)
.project(
Projection.projection("author", "comments.author"),
Projection.projection("date", "comments.date"),
Projection.projection("content", "comments.content")
)
.aggregate(Comment.class)
.forEachRemaining(
comment -> System.out.println("评论:" + comment)
);
}

输出结果已转成我们想要的对象Comment

1
评论:Comment(author=a3, date=Wed Feb 15 09:43:43 CST 2017, content=dope)