mongo 数据方案-容灾


单点意外崩溃

mongodb仲裁服务器会选举出了一位新主人

同时意外崩溃2个节点

这时副本集中可用的数据节点只有1个了,仲裁服务器无数据的,所以自动降级为 secondary,这时整个集群只可读,不可写。在 secondary (副本 )上无法进行写操作的。只有两台服务器恢复了进程,才能恢复正常。

其中一台硬盘数据损坏

坏掉硬盘的服务器中 mongodb数据会全部丢失,但是我们建立的集群有服务器备份。

接下来换硬盘,装好了准备加入集群,但是!!!!!!!!!!!!!!!!!!!!!

假如我们有三台服务器

当副本集只剩下1台节点会进入 secondary ,就无法写入操作了,无法正常运行应用。 而且如果我们的数据很大,同步的时间就会很长,导致整个应用缓慢,甚至崩溃

所以我们需要四台服务器

所以这里就需要我们对副本集设置4台机器, 让集群中的一台服务器,先脱离集群,然后将数据文件拷贝到新换硬盘的服务器上,然后再将两个服务器加入集群,也就是说(1台服务器崩溃,1台服务器脱离集群去修复,还有2台正好1主1从应用)

总结一下具体步骤

  1. 将副本集中的一台机器脱离副本集

  2. 将这台机器的数据库文件夹 copy 到新安装的 服务器上,或者启动 mongo
  3. db 副本集让这 2 台机器慢慢的同步,这样脱离应用的副本集同步不会拖慢整个副本集

  4. 同步完成以后分别将这两台服务器 加入到原来的副本集中

mongo 数据方案-备份


简介

我在之前的文章中简单的介绍过 MonoDB –一个为海量数据为生的数据库

http://mingyueli.com/cn/2016/10/17/mysql-mongodb-select/

MongoDB 是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

它的特点是高性能、易部署、易使用,存储数据非常方便。主要功能特性有:

备份(热备份)

在不停止服务的情况下 实现备份和恢复。

mongodump

备份 mongodump -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 -o 文件存在路径


E:\mongodb\bin>mongodump -o D:\56\mongo\ -d lgb_new
2017-03-21T08:11:18.496+0800    writing lgb_new.twoNavigation to
2017-03-21T08:11:18.497+0800    writing lgb_new.picture to
2017-03-21T08:11:18.497+0800    writing lgb_new.article to
2017-03-21T08:11:18.497+0800    writing lgb_new.oneNavigation to
2017-03-21T08:11:18.498+0800    done dumping lgb_new.picture (10 documents)
2017-03-21T08:11:18.498+0800    done dumping lgb_new.twoNavigation (21 documents
)
2017-03-21T08:11:18.498+0800    writing lgb_new.image to
2017-03-21T08:11:18.499+0800    done dumping lgb_new.oneNavigation (7 documents)

2017-03-21T08:11:18.502+0800    done dumping lgb_new.image (2 documents)
2017-03-21T08:11:18.510+0800    done dumping lgb_new.article (91 documents)

E:\mongodb\bin>

mongorestore

还原 ` mongorestore -h IP –port 端口 -u 用户名 -p 密码 -d 数据库 –drop 文件存在路径`

–drop参数,有此参数,则表示,先删除所有的记录,然后恢复。如无此参数,则恢复备份时候的数据,备份之后新增加的数据依然存在


E:\mongodb\bin>mongorestore -drop D:\56\mongo\lgb_new -d lgb_new

导出 & 导入

导出 mongoexport

mongoexport -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 -c 集合名 -f 字段 -q 条件导出 --csv -o 文件名

-f 导出指字段,以字号分割,-f name,email,age导出name,email,age这三个字段 -q 可以根查询条件导出,-q ‘{ “uid” : “100” }’ 导出uid为100的数据 –csv 表示导出的文件格式为csv的,这个比较有用,因为大部分的关系型数据库都是支持csv,在这里有共同点


> db.testLogin.find()
{ "_id" : ObjectId("57e6661e56e28c00152d8723"), "name" : "lmy", "password" : "12
3456" }


E:\mongodb\bin>mongoexport -d login -c testLogin --csv -f name,password -o D:\56
\mongo\login\testLogin.csv
2017-03-21T08:46:01.675+0800    csv flag is deprecated; please use --type=csv in
stead
2017-03-21T08:46:01.696+0800    connected to: localhost
2017-03-21T08:46:01.720+0800    exported 1 record

CSV如下所示:

12
namepassword
lmy123456

导入 mongoimport

mongoimport -h IP --port 端口 -u 用户名 -p 密码 -d 数据库 -c 集合 --type 类型 --headerline --upsert --drop 文件名

–upsert 更新插入现有数据


E:\mongodb\bin>mongoimport -d login -c testLogin --type csv --headerline --file
D:\56\mongo\login\testLogin.csv
2017-03-21T09:02:36.762+0800    connected to: localhost
2017-03-21T09:02:36.785+0800    imported 4 documents

CSV如下所示:

12
namepassword
lmy123456
aaa1
bbb2
ccc3

> db.testLogin.find()
{ "_id" : ObjectId("57e6661e56e28c00152d8723"), "name" : "lmy", "password" : "12
3456" }
{ "_id" : ObjectId("58d07baccf7c57263767b2f8"), "name" : "lmy", "password" : 123
456 }
{ "_id" : ObjectId("58d07baccf7c57263767b2f9"), "name" : "bbb", "password" : 2 }

{ "_id" : ObjectId("58d07baccf7c57263767b2fa"), "name" : "aaa", "password" : 1 }

{ "_id" : ObjectId("58d07baccf7c57263767b2fb"), "name" : "ccc", "password" : 3 }

当然是否为CSV文件都可以

mongodump / mongorestore 与mongoexport / mongoimport有什么区别

mongodump/mongorestore 导入导出 BSON格式,二进制文件体积虽然小,但是可读性也小,BSON格式跨版本的数据迁移的时候有可能(情况特殊)会出现版本兼容性的错误,所以跨版本备份一般不使用 mongodump / mongorestore

mongoexport / mongoimport 导入导出JSON格式,体积大,可读性强,但是只保留了数据的部分,索引包括在内的其他信息不会被保留


我眼中的 系统数据库


MySQL 数据库系统

Alt text

MongoDB 我没找到合适的图片来说明,有时间我自己画一个贴上来

硬盘 & 内存

内存写入到硬盘上面的时候是分为几个单元,可以称之为事务,

其中几个操作分为一个单元,然后写入到硬盘里面,但是当正在写入到一个单元里面的时候,突然断了,这时候数据也就是丢失了,所以 需要一个记录写入单元时候的过程,这个时候就需要日志了,用来记录每个单元操作的过程,

当然还有另外一个办法就是操作在内存里面,为了防止突然宕机导致数据的同步不一致性,可以考虑开启日志,把所有的记录都存在日志里面,这样会有一个缺点就是,宕机的时候我们需要恢复日志里面所有的内容,然而日志里面的信息存在硬盘里面,这样如果要恢复日志,就需要把硬盘里面的信息计算加入到内存里面解析(这个时候会 加大 磁盘IO )所以效率并不是很好

比如说我要把一个苹果换成梨,其中的过程我们都是不关心的,只要能把苹果换成梨, 苹果 - 哈密瓜 - 柿子 - 梨 但是当换成哈密瓜的时候被旁边的一个进程调走了,这个时候有人来找梨,于是 查找的人就去找有没有梨,发现苹果还没有换成梨,在途中负责别的进程了,于是就去那个进程找苹果,这个时候就需要告诉找梨的人等着,这就是所谓的锁!

其中这两个进程就是不同的事务

InnoDB在运行过程中,会在可能的情况下使用异步磁盘I/O,主要通过创建多个线程来处理I/O操作,与此同时允许其他数据库操作,尽可能降低I/O操作和优化磁盘文件的组织(如,延迟某些I/O操作直到数据库空闲时,或者为了保证数据一致性,在数据库重启后执行某些恢复操作)。

Alt text

分类(索引)

例子说: 图书馆的书籍管理,比如说我要一本文学的书,这样我们可以直接去文学类书籍的书架上面去找我们要找的书籍,但是假如我们的图书馆一遭乱(不分种类什么的,直接全推到图书馆),这样我么存(写)的时候是简单了,但是当我们查询(读)的时候就麻烦了,我们需要几乎全部扫描一遍,由于几乎所有的系统都是读大于写的,所以这样在效率上面就下降了很多,为了解决这个问题就出现了结构和索引

索引

索引的目的就是提高查询(读)的效率,就比如说我们在字典中查询 name 这个单词,我们肯定需要定位到n字母,然后再找到剩下的字母。所谓的索引就是 通过不断的缩小想要获得数据的范围来筛选出最终想要的结果

磁盘IO与预读

磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。

MySQL 目前提供下面4种索引:

MongoDB 支持多种类型的索引 包括单字段索引、复合索引、多key索引、文本索引地理空间索引等,但是每种类型的索引在不同的使用场合效果会不同

SQL 的编写 & 执行

编写

编写的过程不过就是:

  1. 了解业务
  2. 构建数据
  3. 添加构建条件
  4. 添加构建笛卡尔积

而实际的业务应该体现在 select 什么数据

数据的处理本质是计算,减轻程序计算压力,同时也加大了数据库计算压力

count(1) 比count 某一列甚至是索引都速度快为什么????

因为 count(1) 是只把1加载了 而 count(column) 需要列扫描所有的值,需要把值加载到内存里面读出来,所以就算是索引效率也比 count(1)差

执行

Alt text

语法解析

传入一个SQL ,通过关键字将SQL语句进行解析,然后SQL解析器,MySQL解析器将使用MySQL语法规则验证和解析查询

查询优化器

优化器在查询树中遍历每个关系,确定关系是否是常量表,为每个关系查找可用的索引,运用关系 代数原理和启发式规则进行逻辑上的查询优化(如消除子查询,消除外连接等)。 然后对每个连接的表进行排序,再求解多表连接最优路径,对于每个关系尽量利用索引计算其 代价,找出代价最小的路径后保存到JOIN类的best_positions。

执行器

把查询执行计划传到执行器进行执行。


超好玩的编辑距离


编辑距离

编辑距离(Edit Distance),又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。

最小编辑距离通常作为一种相似度计算函数被用于多种实际应用中

又拼写检查(Spell Checker),将每个词与词典中的词条比较,英文单词往往需要做词干提取等规范化处理,如果一个词在词典中不存在,就被认为是一个错误,然后试图提示N个最可能要输入的词——拼写建议。常用的提示单词的算法就是列出词典中与原词具有最小编辑距离的词条。

由于实体的命名往往没有规律,如品牌名,且可能存在多种变形、拼写形式,如“IBM”和“IBM Inc.”,这样导致基于词典完全匹配的命名实体识别方法召回率较低,为此,我们可以使用编辑距离由完全匹配泛化到模糊匹配,先抽取实体名候选词。

许可的编辑操作包括:将一个字符替换成另一个字符,插入一个字符,删除一个字符。

例如将eeba转变成abac:

eba(删除第一个e) aba(将剩下的e替换成a) abac(在末尾插入c) 所以eeba和abac的编辑距离就是3

    1. 最上和最左相等为左上方数字,不等则为左上方加一
    2. 左方加一
    3. 上方加一

上面三个取最小值

  1. 取右下角值 为 最小编辑距离

speak & safe

##speak
#012345
s101234
a211223
f332233
e443234

所以 speak & safe 的编辑距离是4

python 通俗的实现方式:

# 定义初始矩阵,初始为0
In [1]: len_str1 = 3

In [2]: len_str2 = 4

In [3]: [0 for n in range(len_str1 * len_str2)]
Out[3]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

In [4]: len([0 for n in range(len_str1 * len_str2)])
Out[4]: 12


def edit(str1, str2):
    len_str1 = len(str1) + 1
    len_str2 = len(str2) + 1
    # 定义一个矩阵
    matrix = [0 for n in range(len_str1 * len_str2)]
    # 初始化
    for x in range(len_str1):
        matrix[x] = x
    for y in range(0, len(matrix), len_str1):
        if y % len_str1 == 0:
                matrix[y] = y // len_str1
    '''
    1.  1. 最上和最左相等为左上方数字,不等则为左上方加一
        2.  左方加一
        3.  上方加一

    上面三个取最小值
    '''
    for x in range(1, len_str1):
        for y in range(1, len_str2):
            if str1[x - 1] == str2[y - 1]:
                cost = 0
            else:
                cost = 1
            matrix[y * len_str1 + x] = min(matrix[(y - 1) * len_str1 + x] + 1,
                                           matrix[y * len_str1 + (x - 1)] + 1,
                                           matrix[(y - 1) * len_str1 + (x - 1)] + cost)

    '''2. 取右下角值 为 最小编辑距离'''
    return matrix[-1]


if __name__ == '__main__':
    i = edit('speak', 'safe')
    print i

附上一小部分拼写检查

# 字母拼写的所有可能的结果
In [11]: word = "ab"

In [12]: letters = 'abcdefghijklmnopqrstuvwxyz'

In [13]: splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
    ...: # 删除
    ...: deletes = [L + R[1:] for L, R in splits if R]
    ...: # 调换位置
    ...: transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1
    ...: ]
    ...: # 取代字母
    ...: replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
    ...: # 添加字母
    ...: inserts = [L + c + R for L, R in splits for c in letters]
    ...:

In [17]: set(deletes + transposes + replaces + inserts)
    ...:
Out[17]:
{'a',
 'aa',
 'aab',
 'ab',
 'aba',
 'abb',
 'abc',
 'abd',
 'abe',
 'abf',
 'abg',
 'abh',
 'abi',
 'abj',
 'abk',
 'abl',
 'abm',
 'abn',
 'abo',
 'abp',
 'abq',
 'abr',
 'abs',
 'abt',
 'abu',
 'abv',
 'abw',
 'abx',
 'aby',
 'abz',
 'ac',
 'acb',
 'ad',
 'adb',
 'ae',
 'aeb',
 'af',
 'afb',
 'ag',
 'agb',
 'ah',
 'ahb',
 'ai',
 'aib',
 'aj',
 'ajb',
 'ak',
 'akb',
 'al',
 'alb',
 'am',
 'amb',
 'an',
 'anb',
 'ao',
 'aob',
 'ap',
 'apb',
 'aq',
 'aqb',
 'ar',
 'arb',
 'as',
 'asb',
 'at',
 'atb',
 'au',
 'aub',
 'av',
 'avb',
 'aw',
 'awb',
 'ax',
 'axb',
 'ay',
 'ayb',
 'az',
 'azb',
 'b',
 'ba',
 'bab',
 'bb',
 'cab',
 'cb',
 'dab',
 'db',
 'eab',
 'eb',
 'fab',
 'fb',
 'gab',
 'gb',
 'hab',
 'hb',
 'iab',
 'ib',
 'jab',
 'jb',
 'kab',
 'kb',
 'lab',
 'lb',
 'mab',
 'mb',
 'nab',
 'nb',
 'oab',
 'ob',
 'pab',
 'pb',
 'qab',
 'qb',
 'rab',
 'rb',
 'sab',
 'sb',
 'tab',
 'tb',
 'uab',
 'ub',
 'vab',
 'vb',
 'wab',
 'wb',
 'xab',
 'xb',
 'yab',
 'yb',
 'zab',
 'zb'}

In [18]:

def edits1(word):
    letters = 'abcdefghijklmnopqrstuvwxyz'
    # 字母分割
    splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
    # 删除
    deletes = [L + R[1:] for L, R in splits if R]
    # 调换位置
    transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]
    # 取代字母
    replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
    # 添加字母
    inserts = [L + c + R for L, R in splits for c in letters]
    return set(deletes + transposes + replaces + inserts)

# 由于我们需要修正要检查的单词。所以需要一个生成器,用来进行迭代时返回值,这里用生成器表达式的形式解析
def edits2(word):
    return (e2 for e1 in edits1(word) for e2 in edits1(e1))

# -*- coding:utf-8 -*-


def edits1(word):
    letters = 'abcdefghijklmnopqrstuvwxyz'
    # 字母分割
    splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
    # 删除
    deletes = [L + R[1:] for L, R in splits if R]
    # 调换位置
    transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]
    # 取代字母
    replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
    # 添加字母
    inserts = [L + c + R for L, R in splits for c in letters]
    return set(deletes + transposes + replaces + inserts)


def edits2(word):
    return (e2 for e1 in edits1(word) for e2 in edits1(e1))



if __name__ == '__main__':
    # edits1('study')
    i = edits2('study')
    print i


'''
结果是一个生成器
<generator object <genexpr> at 0x00000000029AD120>
'''
# -*- coding:utf-8 -*-

import re
from collections import Counter


def words(text):
    return re.findall(r'\w+', text.lower())

# 统计出现的单词
WORDS = Counter(words(open('big.txt').read()))


# word_list = {}
# with open('big.txt') as f:
#     for line in f:
#         line = line.replace(",", " ")
#         line = line.replace(".", " ")
#         line = line.replace("!", " ")
#         words = line.split()
#         for word in words:
#             if word_list.has_key(word):
#                 word_list[word] += 1
#             else:
#                 word_list[word] = 1
#     result = sorted(word_list.items(), key=lambda k: k[1], reverse=True)


def edits1(word):
    letters = 'abcdefghijklmnopqrstuvwxyz'
    # 字母分割
    splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
    # 删除
    deletes = [L + R[1:] for L, R in splits if R]
    # 调换位置
    transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]
    # 取代字母
    replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
    # 添加字母
    inserts = [L + c + R for L, R in splits for c in letters]
    return set(deletes + transposes + replaces + inserts)


def edits2(word):
    return (e2 for e1 in edits1(word) for e2 in edits1(e1))

# 在已知的单词中进行匹配
def known(words):
    return set(w for w in words if w in WORDS)


if __name__ == '__main__':
    # edits1('study')
    # i = edits2('study')
    # print i
    a = known(edits2('studa'))
    print a


'''
结果
set(['tula', 'stump', 'sta', 'sturdy', 'sudan', 'stung', 'studio', 'stuck', 'stuff', 'stead', 'squad', 'stud', 'soda', 'souza', 'study', 'shuya'])

'''

参考文章:

  1. http://norvig.com/spell-correct.html

赢在起跑线 - MySQL体系结构


MySQL数据库体系结构

架构

MySQL 是单进程多线程架构 插件式存储引擎架构,存储引擎的对象是表,类似于文件系统

存储

从大到小来说 可以 依次为 instance database schema table view

和Oracle 类似 MySQL中 一个database 也对应一个schema
所以在mysql中 create database name = create schema name

数据库 & 数据库实例

数据库是 物理操作系统文件或者其他形式文件类型的集合

数据库实例 指的是一个程序 数据库实例操作数据库文件

一个数据库(这里指的数据库可以想象成一个data 数据目录,并不是指一个 database库)只能对应一个实例 ,一个实例只能对应一个数据库(这里指的数据库可以想象成一个data 数据目录,并不是指一个 database库),要在一台服务器上安装多个数据库实例的话,需要每个实例对应一个data 目录,所以数据库和实例是一一对应的 我们不能说两个实例放在同一个data目录下面

但是oracle rac 可以起多个实例 多个 实例可以连接同一个磁盘的同一个数据,,然后访问多个实例都是对同一个文件夹下面的同一个数据进行访问 ,,一个库对应多个实例这种架构 的话就可以做一些负载均衡 ,,数据库的内存就可以进行无线的扩展也就是可以无限的加instance 加instance就可以提高性能

要是在一台服务器上面装上多个数据库 实例,其中 实例与数据库对应的关系

Alt text


Pagination