#265 Mongo 基础

2018-09-05

版本

以下是几个大版本和发布时间,作为一个大概的时间线吧:

2009/12 1.2
2010/03 1.4
2010/08 1.6
2011/03 1.8
2011/09 2.0
2012/08 2.2
2013/03 2.4
2014/04 2.6
2015/03 3.0
2015/12 3.2
2016/11 3.4
2017/11 3.6
2018/06 4.0

当前最新版本 8 月发布的 4.0.2

Update @ 2021/06/07:
之后主版本号就一直停在了 4,2020 年之后甚至一直停在了 4.4(2019 年 4.2,2020 年 4.4),这也意味着功能组件稳定下来了。

概念

在 Mongo 中,有一些名词变了,看文档的时候需要注意。

  1. 表 Table -> Collection 集合
  2. 行 Row -> Document 文档
  3. 列 Column -> Field 字段(其实 RDB 中的 Column 也经常说成字段)

数据库 Database,索引 Index 不变。

Collection

Document

  1. BSON
  2. 字段名是字符串类型(UTF-8)
  3. 字段有序
  4. 值的类型:
  5. String UTF-8 类型
  6. Int32
  7. Long
  8. Double
  9. Boolean
  10. Object
  11. Array
  12. Date
  13. ObjectId
  14. Null
  15. Date
  16. Timestamp
  17. Decimal128
  18. MinKey
  19. MaxKey
  20. Binary data
  21. JavaScript

废弃:

  1. Undefined
  2. DBPointer
  3. Symbol
  4. JavaScript code with scope

ObjectID

所有 Document 必须有一个 _id 键,没有类型限制,默认是 ObjectID 类型。
PS:所有 Document(BSON)中还包含一个时间戳,4B 时间戳 + 4B 自增数

  • 4B Unix 时间戳
  • 3B 机器标识符
  • 2B 进程号
  • 3B 随机数

关于 NoSQL

在大数据时代,传统 RDB 十分严谨的同时,效率也十分低下。
有人创造了 NoSQL 这个词,表示 Non-SQL,即不支持 SQL 的数据库。
后来部分产品添加了 SQL 支持,含义又演化成 Not Only SQL。
再后来又有人提出 NewSQL 概念,还是差不多的意思。

实现方式:

  1. 键值存储 Key-value store
  2. 文档存储 Document store
  3. 图数据库 Graph
  4. 对象数据库 Object database
  5. Tabular
  6. Tuple store
  7. Triple/quad store (RDF) database
  8. 主机式服务 Hosted
  9. 多数据库 Multivalue Database
  10. Multimodel Database

MongoDB 解决了什么问题

什么事情是 MongoDB 可以做,而 MySQL / PostgreSQL 不能做的?

特点

  • C++ 开发
  • 文档存储(JSON)
  • No Schema
  • 可以为任意属性建立索引
  • 灵活的查询语法
  • 内置小文件存储(GridFS)
  • 服务器端脚本
  • 分布式
  • 基于 AGPLv3 发布

PS:由于和云服务提供商的斗争,4.1.4 之后的所有版本(还有老版本的 BUG 修复版本)全部改用 SSPL (Server Side Public License,服务器端公共许可证) 。
该协议要求云服务提供商必须使用相同协议开放代码(之前所有主要的开源协议都没有对云服务提供商做任何限制),但 OSI 拒绝承认这是开源协议,部分 GNU/Linux 发行版将其从软件源移除。
再后来,其他开源项目,比如 Redis,Elastic,Graylog,也相继采用了和 MongoDB 相同的策略,改用了新的协议分发代码以对抗云服务提供商。

安装

sudo apt install -y mongodb
sudo systemctl disable mongodb
sudo systemctl restart mongodb
sudo systemctl status mongodb

如果希望直接从官方安装最新版本(仅支持 Ubuntu LTS 版本):

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
echo "deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu/dists/$(lsb_release -cs)/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb44.list
sudo apt update
sudo apt install -y mongodb-org
# sudo apt-mark hold mongodb-org # 避免随系统更新

配置

PS:较老的版本有一个自带的简易 Web UI,通过 httpinterface, 或者 rest 参数可以启动,3.2 之后去掉了。

配置文件

# 自带配置
dbpath=/var/lib/mongodb
logpath=/var/log/mongodb/mongodb.log
logappend=true
bind_ip = 127.0.0.1  # 修改为 0.0.0.0
# 端口是默认的 27017
journal=true

# 添加配置
replSet = markjour
# daemon 模式运行,如果用 systemd 就不需要这一行:
fork = true
sudo systemctl restart mongodb

sudo ps -ef | grep mongod
sudo netstat -antp | grep mongod

副本集

// 主节点初始化
rs.initiate();

// 查看副本集配置
cfg = rs.conf();

// 修改主节点 host 为 IP 形式
cfg.members[0].host = "<IP>:<Port>";
rs.reconfig(cfg);

// 添加从节点
rs.add("192.168.64.234");
// 如果是连接不上的节点:"Quorum check failed because not enough voting nodes responded; required 2 but only the following 1 voting nodes responded: 172.16.0.49:27017; the following nodes did not respond affirmatively: 192.168.64.234:27017 failed with Couldn't get a connection within the time limit"
rs.remove("192.168.64.234"); // 移除节点

使用

客户端 SHELL 是基于 JavaScript 的。

mongo
  • show dbs
  • db
  • use <dbName>
  • db.createCollection("s1", {capped:true, size:100000})

保留数据库名称:admin, config, local

连接字符串

https://www.mongodb.com/docs/manual/reference/connection-string/

mongodb://
[username:password@]
host1[:port1]
[,host2[:port2],...[,hostN[:portN]]]
[
    /[database]
    [?options]
]
  • 复制集(ReplicaSet)
  • replicaSet
  • 连接
  • ssl
  • connectTimeoutMS
  • socketTimeoutMS
  • 连接池
  • maxPoolSize
  • minPoolSize
  • maxIdleTimeMS
  • waitQueueMultiple
  • waitQueueTimeoutMS
  • Write Concern
  • w
  • wtimeoutMS
  • journal
  • Read Concern
  • readConcernLevel
  • Read Preference
  • readPreference
  • readPreferenceTags
  • 认证
  • authSource
  • authMedhanism
  • gssapiServiceName
  • 服务器选择与发现
  • localThresholdMS
  • serverSelectionTimeoutMS
  • serverSelectionTryOnce
  • heartbeatFrequencyMS
  • 其他
  • uuidRepresentation

参考资料与拓展阅读

#264 短信的原理

2018-09-04
  • Short Message, 短信
  • SMS, Short Message Service, 短信服务
  • MO, Mobile Originate, 发短信
  • MT, Mobile Terminate, 收短信
Terminal 终端
SMC      短信中心
SMS GW   短信网关

重要协议

编码方案

  • 7bit ASCII
  • 8bit ASCII
  • UCS2 (早期的 Unicode 方案,2 Bytes 表示一个字)

长度

受协议限制,短信内容最大 140 字节,所以:

采用 8bit 编码的话,最长 140 字符。
采用 7bit 编码的话,最长 160 字符(正好)。
采用 UCS2 编码的话,最长 70 字符。

如果涉及长短信切割,根据通行的拓展协议,需要采用头三个字节存储相关信息。

采用 8bit 编码的话,每段最长 137 字符。
采用 7bit 编码的话,每段最长 156 字符(最后剩余 4 bits 空着)。
采用 UCS2 编码的话,每段最长 67 字符。

长短信分割

参考 GSM 03.40 9.2.3.24 TP-User Data (TP-UD) 部分,一般有两种方案:

\x05        剩余协议头长度
\x00        短信标识 GSM 03.40
\x03        剩余短信标识长度
随机字节(1 字节)
总包数
包序号(1 开始)

还有一种没有怎么见过的方案,就是采用两个字节做随机标识,然后头三字节改成 \x06\x08\x04

第二字节叫做 The Information Element Identifier(信息元素标识符),上面的 \x00\x08 分别标识 1 字节,2 字节随机标识方案。其他值可以参考文档。

#263 tornado: yield

2018-08-05
from tornado import gen, ioloop

@gen.coroutine
def dosth():
    yield gen.sleep(1)
    print('slept for 1 second')

ioloop.IOLoop.current().run_sync(dosth)
from tornado import gen, ioloop

@gen.coroutine
def dosth():
    print('dosth 222222222222222222222')
    yield gen.sleep(1)
    print('slept for 1 second 22222222')
    print('dosth over 2222222222222222')

@gen.coroutine
def test():
    print('test 111111111111111')
    dosth()
    print('test over 1111111111')

print('start')
ioloop.IOLoop.current().run_sync(test)
print('over')

# start
# test 111111111111111
# dosth 222222222222222222222
# test over 1111111111
# over

# start
# test 111111111111111
# dosth 222222222222222222222
# test over 1111111111
# slept for 1 second 22222222
# dosth over 2222222222222222
# over

#262 Git 补丁包

2018-08-01
  • git diff 对应 diff 命令
  • git apply 对应 patch 命令
git diff v1.2.1 v1.2.2 > v1.2.1_v1.2.2.patch

git apply --check v1.2.1_v1.2.2.patch

git apply -v --whitespace=warn v1.2.1_v1.2.2.patch

有部分文档中说 git applypatch 在一些细节上实现不一致,需要留意。但我轻量级使用,没有遇到过什么问题。

#261 电子邮件是如何传输的?

2018-07-22

假设:

  1. A 要发一封邮件给 B、C。
  2. A 的地址是 aaa@163.com,B 的邮箱地址是 bbb@163.com,C 的邮箱地址是 ccc@qq.com

那么:

  1. A 先在自己的客户端 Thunderbird 上(WebMail 后面是怎么处理的就取决于各个邮件服务提供商了)将邮件编辑并发送出去。
  2. 邮件经过 SMTP(需要验证身份),到了网易的邮件发送服务 smtp.163.com
  3. 网易邮件发送服务检查了两个收信地址,发现:
    B 地址所在域和自己一致,就直接投给了自己的用户邮箱
    C 地址所在域和自己不一致,就检查 C 域的 MX 记录,得到 mx.qq.com,然后将邮件投递过去(SMTP)。
  4. B 通过客户端 Foxmail,从配置的 pop3.163.com 下载了邮件(POP3),手机自带的邮件客户端配的 imap.163.com,那就走 IMAP 协议。
  5. QQ 的 mx 服务器是没有身份验证的,但是会通过一系列手段验证 163 的连接是可靠的,才会接收这封邮件。
  6. QQ 收到邮件之后,将邮件投进 C 的收件箱。
  7. C 则在 Web 上浏览邮件,这时候 WebMail 通过某种技术,从 QQ 邮箱服务拉取到了邮件列表,C 就发现这封邮件,然后点开看到了 WebMail 获取到的邮件内容。
  QQ 163  
SMTP smtp.qq.com 465/587 smtp.163.com 25, 465/587
POP3 pop.qq.com 995 pop.163.com 110, 995
IMAP imap.qq.com 993 imap.163.com 143, 993

玩概念

可以看到各种 MxA:

  • MUA: user 用户代理,也就是邮件客户端
  • WebMail 或者 Foxmail、Thunderbird 这样的工具。
  • MSA: submission 发送代理
  • 比如 smtp.163.com 这样的收信服务
  • MTA: transfer 传送代理,或者说交换代理,又叫 Mail Exchanger 或 MX Host
  • 接收别的邮件服务转过来的邮件
  • 和 MSA 的区别是没有身份认证,但是有对投递者(服务器)身份信息的检验
  • Postfix 服务器
  • MDA: delivery 投递代理
  • 主要是对 MSA/MTA 收到的邮件进行过滤处理:
    • 检查和过滤垃圾邮件、病毒邮件等,
    • 然后外部地址的邮件就交换出去,
    • 内部地址的邮件就存储起来
  • MRA: retrieval,receive 接收/检索代理
  • POP,IMAP 协议
  • Dovecot 服务器

#260 在 Markdown 中使用数学公式

2018-07-21

vscode 或者 Typora 或这其他的一些编辑器都支持在 Markdown 中使用数学公式,直接预览就行了。

但是如果是自己做 Markdown 转换,生成 HTML 页面,就必须做点额外的工作。

我发现 Mathjax 挺好用的,只需加入三行:

<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script
  src="https://cdn.jsdelivr.net/npm/mathjax@3.0.1/es5/tex-mml-chtml.js"
  async
></script>
<script>
  MathJax = {
    tex: {
      inlineMath: [
        ["$", "$"],
        ["\\(", "\\)"],
      ],
    },
  };
</script>

Katex 据说不错,以后如果发现 Mathjax 有不足之处再试试。

Update @ 2022-05-27: 根据 May 19, 2022 的博客 Render mathematical expressions in Markdown, GitHub 开始采用 Mathjax 渲染数学公式。

关于数学公式

数学公式的语法都是参考 LaTeX(2019/07/21, LaTeX)。

#258 Nano 编辑器

2018-07-13

Pico (Pine composer) is a text editor for Unix and Unix-like computer systems. It is integrated with the Pine and Alpine email clients, which were initially designed by the Office of Computing and Communications at the University of Washington.
A clone of Pico called nano, which is part of the GNU Project, was developed because Pico's earlier license had unclear redistribution terms. Newer versions of Pico as part of Alpine are released under the Apache License version 2.0.

Nano 是由于 Pico 编辑器早期授权条款不够 free 而开发,是 GNU 计划的一部分。

使用

Nano 是一个超级轻量级的文本编辑器,是每个 Linux 发行版的标配。

  1. 语法高亮(比如:/usr/share/nano/json.nanorc

两个命令:nano,rnano(受限模式,参考 man 文档)

配置

  • 系统配置 /etc/nanorc/usr/share/nano/
  • 用户配置 ~/.nanorc~/.nano/

参考资料与拓展阅读

#257 Linux date 命令

2018-07-03
date
2018年 07月 03日 星期二 14:09:28 CST

# locale 格式
date +"%c"
2018年07月03日 星期二 14时09分22秒

# RFC3339 格式
date --rfc-3339=s
2018-07-03 14:09:48+08:00

# UTC 时间,ISO 8601 格式
date -u +"%Y-%m-%dT%H:%M:%SZ"

# yyyy-mm-dd hh:mm:ss,mm
date +'%Y-%m-%d %H:%M:%S,%3N'
2018-07-03 14:09:20,902

# 获取时间戳
date +%s

# 时间字符串转时间戳
date -d "2018-07-03 14:09:00" +%s
1530598140
date -d "2018-07-03 14:09:00" +%s --utc
1530626940

# 时间戳转时间字符串
date -d @1530598140
2018年 07月 03日 星期二 14:09:00 CST
date +'%Y-%m-%d %H:%M:%S' -d @1530598140
2018-07-03 14:09:00

# 获取一分钟前的时间
date --rfc-3339=s -d '1 min ago'
date --rfc-3339=s -d '1 minute ago'
date --rfc-3339=s -d '1 minutes ago'

# 获取一个小时前的时间
date --rfc-3339=s -d '1 hour ago'

#256 常用开发工具

2018-06-26

我本来就想记录一下请求搜集服务的,结果扩大化,发现完全兜不住,好吧,就这样吧!