MySQL中的日志总结

MySQL中有六种日志文件:

分别是:重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)。

其中重做日志和回滚日志与事务操作息息相关,二进制日志也与事务操作有一定的关系,这三种日志,对理解MySQL中的事务操作有着重要的意义。
这里简单总结一下这三者具有一定相关性的日志。
重做日志(redo log)

作用:
  确保事务的持久性。
  防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
内容:
  物理格式的日志,记录的是物理数据页面的修改的信息,其redo log是顺序写入redo log file的物理文件中去的。
什么时候产生:
  事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。
什么时候释放:
  当对应事务的脏页写入到磁盘之后,redo log的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。
对应的物理文件:
  默认情况下,对应的物理文件位于数据库的data目录下的ib_logfile1&ib_logfile2
  innodb_log_group_home_dir 指定日志文件组所在的路径,默认./ ,表示在数据库的数据目录下。
  innodb_log_files_in_group 指定重做日志文件组中文件的数量,默认2
  关于文件的大小和数量,由一下两个参数配置
  innodb_log_file_size 重做日志文件的大小。
  innodb_mirrored_log_groups 指定了日志镜像文件组的数量,默认1
其他:
  很重要一点,redo log是什么时候写盘的?前面说了是在事物开始之后逐步写盘的。
  之所以说重做日志是在事务开始之后逐步写入重做日志文件,而不一定是事务提交才写入重做日志缓存,
  原因就是,重做日志有一个缓存区Innodb_log_buffer,Innodb_log_buffer的默认大小为8M(这里设置的16M),Innodb存储引擎先将重做日志写入innodb_log_buffer中。

1

然后会通过以下三种方式将innodb日志缓冲区的日志刷新到磁盘

  1)Master Thread 每秒一次执行刷新Innodb_log_buffer到重做日志文件。
  2)每个事务提交时会将重做日志刷新到重做日志文件。
  3)当重做日志缓存可用空间 少于一半时,重做日志缓存被刷新到重做日志文件
  由此可以看出,重做日志通过不止一种方式写入到磁盘,尤其是对于第一种方式,Innodb_log_buffer到重做日志文件是Master Thread线程的定时任务。

因此重做日志的写盘,并不一定是随着事务的提交才写入重做日志文件的,而是随着事务的开始,逐步开始的。
另外引用《MySQL技术内幕 Innodb 存储引擎》(page37)上的原话:
即使某个事务还没有提交,Innodb存储引擎仍然每秒会将重做日志缓存刷新到重做日志文件。
这一点是必须要知道的,因为这可以很好地解释再大的事务的提交(commit)的时间也是很短暂的。
回滚日志(undo log)

作用:
  保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读
内容:
  逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。
什么时候产生:
  事务开始之前,将当前是的版本生成undo log,undo 也会产生 redo 来保证undo log的可靠性
什么时候释放:
  当事务提交之后,undo log并不能立马被删除,
  而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间。
对应的物理文件:
  MySQL5.6之前,undo表空间位于共享表空间的回滚段中,共享表空间的默认的名称是ibdata,位于数据文件目录中。
  MySQL5.6之后,undo表空间可以配置成独立的文件,但是提前需要在配置文件中配置,完成数据库初始化后生效且不可改变undo log文件的个数
  如果初始化数据库之前没有进行相关配置,那么就无法配置成独立的表空间了。
  关于MySQL5.7之后的独立undo 表空间配置参数如下
  innodb_undo_directory = /data/undospace/ --undo独立表空间的存放目录
  innodb_undo_logs = 128 --回滚段为128KB
  innodb_undo_tablespaces = 4 --指定有4个undo log文件

  如果undo使用的共享表空间,这个共享表空间中又不仅仅是存储了undo的信息,共享表空间的默认为与MySQL的数据目录下面,其属性由参数innodb_data_file_path配置。2
其他:
  undo是在事务开始之前保存的被修改数据的一个版本,产生undo日志的时候,同样会伴随类似于保护事务持久化机制的redolog的产生。
  默认情况下undo文件是保持在共享表空间的,也即ibdatafile文件中,当数据库中发生一些大的事务性操作的时候,要生成大量的undo信息,全部保存在共享表空间中的。
  因此共享表空间可能会变的很大,默认情况下,也就是undo 日志使用共享表空间的时候,被“撑大”的共享表空间是不会也不能自动收缩的。
  因此,mysql5.7之后的“独立undo 表空间”的配置就显得很有必要了。

二进制日志(binlog):

作用:
  1,用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。
  2,用于数据库的基于时间点的还原。
内容:
  逻辑格式的日志,可以简单认为就是执行过的事务中的sql语句。
  但又不完全是sql语句这么简单,而是包括了执行的sql语句(增删改)反向的信息,
  也就意味着delete对应着delete本身和其反向的insert;update对应着update执行前后的版本的信息;insert对应着delete和insert本身的信息。
  在使用mysqlbinlog解析binlog之后一些都会真相大白。
  因此可以基于binlog做到类似于oracle的闪回功能,其实都是依赖于binlog中的日志记录。
什么时候产生:
  事务提交的时候,一次性将事务中的sql语句(一个事物可能对应多个sql语句)按照一定的格式记录到binlog中。
  这里与redo log很明显的差异就是redo log并不一定是在事务提交的时候刷新到磁盘,redo log是在事务开始之后就开始逐步写入磁盘。
  因此对于事务的提交,即便是较大的事务,提交(commit)都是很快的,但是在开启了bin_log的情况下,对于较大事务的提交,可能会变得比较慢一些。
  这是因为binlog是在事务提交的时候一次性写入的造成的,这些可以通过测试验证。
什么时候释放:
  binlog的默认是保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。
3
对应的物理文件:
  配置文件的路径为log_bin_basename,binlog日志文件按照指定大小,当日志文件达到指定的最大的大小之后,进行滚动更新,生成新的日志文件。
  对于每个binlog日志文件,通过一个统一的index文件来组织。

4
其他:
  二进制日志的作用之一是还原数据库的,这与redo log很类似,很多人混淆过,但是两者有本质的不同
  1)作用不同:redo log是保证事务的持久性的,是事务层面的,binlog作为还原的功能,是数据库层面的(当然也可以精确到事务层面的),虽然都有还原的意思,但是其保护数据的层次是不一样的。
  2)内容不同:redo log是物理日志,是数据页面的修改之后的物理记录,binlog是逻辑日志,可以简单认为记录的就是sql语句
  3)另外,两者日志产生的时间,可以释放的时间,在可释放的情况下清理机制,都是完全不同的。
  4)恢复数据时候的效率,基于物理日志的redo log恢复数据的效率要高于语句逻辑日志的binlog

关于事务提交时,redo log和binlog的写入顺序,为了保证主从复制时候的主从一致(当然也包括使用binlog进行基于时间点还原的情况),是要严格一致的,
MySQL通过两阶段提交过程来完成事务的一致性的,也即redo log和binlog的一致性的,理论上是先写redo log,再写binlog,两个日志都提交成功(刷入磁盘),事务才算真正的完成。
参考:http://www.cnblogs.com/hustcat/p/3577584.html

总结:

MySQL中,对于以上三种日志,每一种细化起来都可以够写一个章节的,这里粗略地总结了一下三种日志的一些特点和作用,以帮助理解MySQL中的事物以及事物背后的原理。

参考:《MySQL技术内幕 Innodb 存储引擎》

网络TCP建立连接为什么需要三次握手而结束要四次

举个打电话的例子:

A : 你好我是A,你听得到我在说话吗

B : 听到了,我是B,你听到我在说话吗

A : 嗯,听到了

建立连接,开始聊天!

8
direct

为什么TCP协议终止链接要四次?

1、当主机A确认发送完数据且知道B已经接受完了,想要关闭发送数据口(当然确认信号还是可以发),就会发FIN给主机B。

2、主机B收到A发送的FIN,表示收到了,就会发送ACK回复。

3、但这是B可能还在发送数据,没有想要关闭数据口的意思,所以FIN与ACK不是同时发送的,而是等到B数据发送完了,才会发送FIN给主机A。

4、A收到B发来的FIN,知道B的数据也发送完了,回复ACK, A等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭链接,B也关闭链接了。

A为什么等待2MSL,从TIME_WAIT到CLOSE?

在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

这个网上转载的例子不错:

三次握手:
A:“喂,你听得到吗?”A->SYN_SEND

B:“我听得到呀,你听得到我吗?”应答与请求同时发出 B->SYN_RCVD | A->ESTABLISHED

A:“我能听到你,今天balabala……”B->ESTABLISHED

四次挥手:
A:“喂,我不说了。”A->FIN_WAIT1

B:“我知道了。等下,上一句还没说完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2

B:”好了,说完了,我也不说了。”B->LAST_ACK

A:”我知道了。”A->TIME_WAIT | B->CLOSED

A等待2MSL,保证B收到了消息,否则重说一次”我知道了”,A->CLOSED
链接:https://zhuanlan.zhihu.com/p/21940234

参考:http://blog.chinaunix.net/uid-25002135-id-3314682.html
http://www.cnblogs.com/vamei/archive/2012/12/16/2812188.html

redis持久化存储

redis支持两种方式的持久化,可以单独使用或者结合起来使用
第一种:RDB方式,redis默认的持久化方式,

第二种:AOF方式,需要手动修改配置。下面我们来看一下两种持久化方式以及持久化中所注意的一些问题。

持久化之RDB

rdb方式的持久化是通过快照完成的,当符合一定条件时redis会自动将内存中的所有数据执行快照操作并存储到硬盘上。默认存储在dump.rdb文件中(文件名在配置文件中dbfilename),默认打开可以到启动的目录去查看,注意: dump.rdb是在哪儿启动redis,就会在哪儿生成rdb文件。

  • dump.rdb
    [root@momo1 ~]# cd /usr/local/redis/
    [root@momo1 redis]# ll
    总用量 156
    -rw-rw-r--. 1 root root 34339 12月 18 23:19 00-RELEASENOTES
    -rw-rw-r--. 1 root root 53 12月 18 23:19 BUGS
    -rw-rw-r--. 1 root root 1805 12月 18 23:19 CONTRIBUTING
    -rw-rw-r--. 1 root root 1487 12月 18 23:19 COPYING
    drwxrwxr-x. 6 root root 4096 4月 27 19:40 deps
    -rw-r--r--. 1 root root 36 4月 29 01:20 dump.rdb ##dump.rdb
    -rw-rw-r--. 1 root root 11 12月 18 23:19 INSTALL
    -rw-rw-r--. 1 root root 151 12月 18 23:19 Makefile
    -rw-rw-r--. 1 root root 4223 12月 18 23:19 MANIFESTO
    -rw-rw-r--. 1 root root 5201 12月 18 23:19 README
    -rw-rw-r--. 1 root root 41561 4月 27 19:53 redis.conf
    -rwxrwxr-x. 1 root root 271 12月 18 23:19 runtest
    -rwxrwxr-x. 1 root root 280 12月 18 23:19 runtest-cluster
    -rwxrwxr-x. 1 root root 281 12月 18 23:19 runtest-sentinel
    -rw-rw-r--. 1 root root 7113 4月 29 00:26 sentinel.conf
    drwxrwxr-x. 2 root root 4096 4月 27 19:41 src
    drwxrwxr-x. 10 root root 4096 12月 18 23:19 tests
    drwxrwxr-x. 5 root root 4096 12月 18 23:19 utils
  1. redis进行快照的时机(在配置文件redis.conf中)
    1. save 900 1:表示900秒内至少一个键被更改则进行快照。
    2. save 300 10
    3. save 60 10000
  2. redis实现快照的过程
    1. redis使用fork函数复制一份当前进程的副本(子进程)
    2. 父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件
    3. 当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此,一次快照操作完成。
  3. 手动执行save或者bgsave命令让redis执行快照
    1. save是由主进程进行快照操作,会阻塞其它请求。
    2. bgsave是由redis执行fork函数复制出一个子进程来进行快照操作。
  4. 文件恢复:redis-check-dump
    [root@iZ94r8hgrjcZ /]# redis-check-dump dump.rdb 
    ==== Processed 5 valid opcodes (in 45 bytes) ===================================
    CRC64 checksum is OK
  • rdb的优缺点
    优点:由于存储的有数据快照文件,恢复数据很方便。
    缺点:会丢失最后一次快照以后更改的所有数据。
  • 注意
  1. redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的
  2. 我们可以通过定时备份RDB文件来实现redis数据库的备份。
  3. RDB文件是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。
  • 示例代码:
    127.0.0.1:6379> set a 123
    OK
    127.0.0.1:6379> set b 456
    OK
    127.0.0.1:6379> keys *
    1) "b"
    2) "a"
    127.0.0.1:6379> shutdown
    not connected> exit
    ###注意此时有值,关掉退出然后我们切换一下目录后再启动,就会是一个新的rdb文件,无值
    
    [root@momo1 redis]# cd /home/up/
    [root@momo1 up]# redis-server /etc/redis.conf 
    [root@momo1 up]# redis-cli 
    127.0.0.1:6379> keys *
    (empty list or set)
    127.0.0.1:6379> shutdown
    not connected> exit
    [root@momo1 up]# ll
    总用量 1348
    -rw-r--r--. 1 root root 18 4月 29 04:04 dump.rdb

持久化之AOF

aof方式的持久化是通过日志文件的方式。默认情况下redis没有开启aof,可以通过参数appendonly参数开启。appendonly yes
aof文件的保存位置和rdb文件的位置相同,都是dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改
appendfilename appendonly.aof

appendonly.aofappendonly.aof
aof同步的时机aof同步的时机
  1. redis写命令同步的时机
    1. appendfsync always 每次都会执行
    2. appendfsync everysec 默认 每秒执行一次同步操作(推荐,默认)
    3. appendfsync no不主动进行同步,由操作系统来做,30秒一次
  2. aof日志文件重写
    1. auto-aof-rewrite-percentage 100(当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的aof文件大小为依据)
    2. auto-aof-rewrite-min-size 64mb
  3. 手动执行bgrewriteaof进行重写

    重写的过程只和内存中的数据有关,和之前的aof文件无关。即针对数据库中当前数据进行重新整理成redis语句

  4. 文件恢复:redis-check-aof
  • 示例代码:
    ## 在rdb是持久化下游三个keys
    [root@momo1 redis]# redis-cli
    127.0.0.1:6379> keys *
    1) "d"
    2) "a"
    3) "b"
    
    ## 
    [root@momo1 redis]# vim redis.conf #修改appendonly yes
    [root@momo1 redis]# redis-server ./redis.conf 
    [root@momo1 redis]# redis-cli
    127.0.0.1:6379> keys *
    (empty list or set)

动态切换redis持久方式

RDB 切换到 AOF支持Redis 2.2及以上

  • 问题: 在很多情况下,默认使用了rdb持久化方式,如果我们再开启aof持久化方式,就会发现一个问题 : 原先的redis数据丢失了!
  • 主要原因: redis的aof持久化方式是最安全的,如果开启aof之后,redis会优先选择aof的方式,而appendonly.aof文件此时还是空文件于是之前的数据就丢失了。
  • 解决办法: 使用config命令,首先动态修改配置,然后再修改配置文件!
    1. cinfig set appendonly yes
    2. config set save “”(可选,如果开启了aof可以选择关闭rdb)
  • 总结:
  1. 当redis启动时,如果rdb持久化和aof持久化都打开了,那么程序会优先使用aof方式来恢复数据集,因为aof方式所保存的数据通常是最完整的。如果aof文件丢失了,则启动之后数据库内容为空。
  2. 如果想把正在运行的redis数据库,从RDB切换到AOF,先使用动态切换方式,再修改配置文件,重启数据库。(不能自己修改配置文件,重启数据库,否则数据库中数据就为空了。)
  3. 如果我们失误操作,没有动态切换数据为空了,我们应该尽快手动kill掉redis进程,然后删掉aof备份文件改掉配置后重新动态切换。
  • 示例代码:动态切换
    127.0.0.1:6379> config set appendonly yes
    OK
    127.0.0.1:6379> mget a b d
    1) "123"
    2) "123"
    3) "999"
    
    ####然后我们来看一下appendonly.aof文件内容
    [root@momo1 redis]# vim appendonly.aof 
    *2
    $6
    SELECT
    $1
    0
    *3
    $3
    SET
    $1
    d ##d的值
    $3 ##长度为3
    999 ##值为999
    *3
    $3
    SET
    $1
    b
    $3
    123
    *3
    $3
    SET
    $1
    a
    $3
    123
  • 示例代码:如果我们失误操作,没有动态切换数据为空了
    [root@momo1 redis]# vim redis.conf #修改appendonly yes此时没有动态切换
    [root@momo1 redis]# redis-server ./redis.conf ##启动
    [root@momo1 redis]# redis-cli ##此时有数据
    127.0.0.1:6379> keys *
    (empty list or set)
    
    [root@momo1 redis]# ps -ef|grep redis ##重开一个端口kill进程
    root 6399 1 0 04:20 ? 00:00:00 redis-server *:6379 
    root 6402 5794 0 04:20 pts/2 00:00:00 redis-cli
    root 6413 6113 0 04:21 pts/3 00:00:00 grep redis
    [root@momo1 redis]# kill -9 6399 
    [root@momo1 redis]# rm -rf appendonly.aof ##删掉appendonly.aof
    [root@momo1 redis]# vim redis.conf ##再次改回no
    [root@momo1 redis]# redis-server ./redis.conf ##启动
    [root@momo1 redis]# redis-cli
    127.0.0.1:6379> keys * ##数据还在,万幸
    1) "d"
    2) "b"
    3) "a"

config命令

  • 1、使用config set可以动态设置参数信息,服务器重启之后就失效了。
    config set appendonly yes
    config set save "90 1 30 10 60 100"
  • 2、使用config get可以查看所有可以使用config set命令设置的参数
    127.0.0.1:6379> config get * ##这里我省略了很多
     1) "dbfilename" ##rdb存储文件
     2) "dump.rdb"
     9) "logfile" ##日志文件
     10) "" ##可重新配置
     11) "pidfile" ##pid文件
     12) "/var/run/redis.pid"
     13) "maxmemory"
     14) "0"
     15) "maxmemory-samples"
     16) "5"
     17) "timeout"
     18) "0"
     40) "3000"
     41) "lua-time-limit"
     42) "5000"
     43) "slowlog-log-slower-than" 
     44) "10000"
     45) "latency-monitor-threshold"
     46) "0"
     47) "slowlog-max-len"
     48) "128"
     49) "port" ##端口
     50) "6379"
     51) "tcp-backlog"
     52) "511"
     53) "databases" ##支持数据库
     54) "16"
     75) "cluster-node-timeout" ##集群的一些信息
     76) "15000"
     77) "cluster-migration-barrier"
     78) "1"
     79) "cluster-slave-validity-factor"
     80) "10"
     81) "repl-diskless-sync-delay"
     82) "5"
     83) "cluster-require-full-coverage"
     84) "yes"
     85) "no-appendfsync-on-rewrite"
     86) "no"
     87) "slave-serve-stale-data"
     88) "yes"
     89) "slave-read-only"
     90) "yes"
     91) "stop-writes-on-bgsave-error"
     92) "yes"
     93) "daemonize" ##是否开启daemonize
     94) "yes"
     95) "rdbcompression"
     96) "yes"
     97) "rdbchecksum"
     98) "yes"
     99) "activerehashing"
    100) "yes"
    101) "repl-disable-tcp-nodelay"
    102) "no"
    103) "repl-diskless-sync"
    104) "no"
    105) "aof-rewrite-incremental-fsync"
    106) "yes"
    107) "aof-load-truncated"
    108) "yes"
    109) "appendonly" ##
    110) "no"
    111) "dir" ##dir 目录,可修改
    112) "/root"
    117) "save" ##rdb持久化点
    118) "900 1 300 10 60 10000"
    119) "loglevel" ##日志级别
    120) "notice"
    124) "0"
    125) "slaveof"  ##主从
    129) "bind"  ##bind ip
    130) ""
  • 3、 使用config rewrite命令对启动 Redis 服务器时所指定的 redis.conf 文件进行改写(Redis 2.8 及以上版本才可以使用),主要是把使用
    config set动态指定的命令保存到配置文件中。
    config rewrite
  • 注意:

config rewrite命令对 redis.conf 文件的重写是原子性的, 并且是一致的:如果重写出错或重写期间服务器崩溃, 那么重写失败, 原有 redis.conf 文件不会被修改。 如果重写成功, 那么 redis.conf 文件为重写后的新文件

修改持久化、日志路径:

###########1、创建目录存储redis,博主在/data/创建redis目录来存放数据###
mkdir /data/redis 
kill -9 redis进程号或shutdown redis#关掉redis进程

###########2、修改日志文件、dump文件路径###########
logfile "/data/redis/redis.log"
dir /data/redis/

###########3、移动之前dump文件到新目录##########
mv /dump.rdb /data/redis/ #我这里之前dump文件在根目录下

###########4、启动redis、查看数据正常##########
[root@iZ94r8hgrjcZ usr]# service redis start ##启动redis
Starting Redis server...
[root@iZ94r8hgrjcZ redis]# pwd  
/data/redis
[root@iZ94r8hgrjcZ redis]# ll         ##以生成日志文件
total 8
-rw-r--r-- 1 root root   62 Apr 30 11:51 dump.rdb
-rw-r--r-- 1 root root 2206 Apr 30 13:04 redis.log
[root@iZ94r8hgrjcZ redis]# redis-cli  ##数据正常
127.0.0.1:6379> keys *
//......

////////##########如果还想测试aof#############
127.0.0.1:6379> config set appendonly yes ##1、动态修改持久化
OK

[root@iZ94r8hgrjcZ redis]# vim /etc/redis/6379.conf ##2、修改配置appendonly yes
[root@iZ94r8hgrjcZ redis]# ll   ##3、查看数据正常
total 12
-rw-r--r-- 1 root root  132 Apr 30 13:20 appendonly.aof
-rw-r--r-- 1 root root   62 Apr 30 11:51 dump.rdb
-rw-r--r-- 1 root root 2937 Apr 30 13:20 redis.log
参考:
http://redisbook.readthedocs.io/en/latest/internal/aof.html

https://lanjingling.github.io/2015/11/16/redis-chijiuhua/

MongoDB分片原理

在系统早期,数据量还小的时候不会引起太大的问题,但是随着数据量持续增多,后续迟早会出现一台机器硬件瓶颈问题的。而mongodb主打的就是海量数据架构,他不能解决海量数据怎么行!不行!“分片”就用这个来解决这个问题。

传统数据库怎么做海量数据读写?其实一句话概括:分而治之。上图看看就清楚了,如下 taobao岳旭强在infoq中提到的 架构图:

1

上图中有个TDDL,是taobao的一个数据访问层组件,他主要的作用是SQL解析、路由处理。根据应用的请求的功能解析当前访问的sql判断是在哪个业务数据库、哪个表访问查询并返回数据结果。具体如图:

2

说了这么多传统数据库的架构,那Nosql怎么去做到了这些呢?mysql要做到自动扩展需要加一个数据访问层用程序去扩展,数据库的增加、删除、备份还需要程序去控制。一但数据库的节点一多,要维护起来也是非常头疼的。不过mongodb所有的这一切通过他自己的内部机制就可以搞定!顿时石化了,这么牛X!还是上图看看mongodb通过哪些机制实现路由、分片:

3

从图中可以看到有四个组件:mongos、config server、shard、replica set。

mongos,数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器,mongos自己就是一个请求分发中心,它负责把对应的数据请求请求转发到对应的shard服务器上。在生产环境通常有多mongos作为请求的入口,防止其中一个挂掉所有的mongodb请求都没有办法操作。

config server,顾名思义为配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,这个可不能丢失!就算挂掉其中一台,只要还有存货, mongodb集群就不会挂掉。

shard,这就是传说中的分片了。上面提到一个机器就算能力再大也有天花板,就像军队打仗一样,一个人再厉害喝血瓶也拼不过对方的一个师。俗话说三个臭皮匠顶个诸葛亮,这个时候团队的力量就凸显出来了。在互联网也是这样,一台普通的机器做不了的多台机器来做,如下图:

4

一台机器的一个数据表 Collection1 存储了 1T 数据,压力太大了!在分给4个机器后,每个机器都是256G,则分摊了集中在一台机器的压力。也许有人问一台机器硬盘加大一点不就可以了,为什么要分给四台机器呢?不要光想到存储空间,实际运行的数据库还有硬盘的读写、网络的IO、CPU和内存的瓶颈。在mongodb集群只要设置好了分片规则,通过mongos操作数据库就能自动把对应的数据操作请求转发到对应的分片机器上。在生产环境中分片的片键可要好好设置,这个影响到了怎么把数据均匀分到多个分片机器上,不要出现其中一台机器分了1T,其他机器没有分到的情况,这样还不如不分片!

replica set,上两节已经详细讲过了这个东东,怎么这里又来凑热闹!其实上图4个分片如果没有 replica set 是个不完整架构,假设其中的一个分片挂掉那四分之一的数据就丢失了,所以在高可用性的分片架构还需要对于每一个分片构建 replica set 副本集保证分片的可靠性。生产环境通常是 2个副本 + 1个仲裁。

 

MongoDB副本集心跳和同步机制

MongoDB建议副本集成员为奇数。最多12个副本节点最多7个节点参与选举。限制副本节点的数量,主要是因为一个集群中过多的副本节点,增加了复制的成本,反而拖累了集群的整体性能。太多的副本节点参与选举,也会增加选举的时间。MongoDB官方推荐节点数量为奇数。主要在于副本集常常为分布式,可能位于不同的IDC。如果为偶数,可能出现每个IDC的节点数一样,从而如果网络故障,那么每个IDC里的节点都无法选出主节点,导致全部不可用的情况。比如,节点数为4,分处于2个IDC,现在IDC之间的网络出现故障,每个IDC里的节点都没有大于2,所以副本集没有主节点,变成只读。

MongoDB节点会向副本集中的其他节点每两秒就会发送一次pings包,如果其他节点在10秒钟之内没有返回就标示为不能访问。每个节点内部都会维护一个状态映射表,表明当前每个节点是什么角色、日志时间戳等关键信息。如果是主节点,除了维护映射表外还需要检查自己能否和集群中内大部分节点通讯,如果不能则把自己降级为secondary只读节点。

副本集同步分为初始化同步和keep复制。初始化同步指全量从主节点同步数据,如果主节点数据量比较大同步时间会比较长。而keep复制指初始化同步过后,节点之间的实时同步一般是增量同步。初始化同步不只是在第一次才会被触发,有以下两种情况会触发:

secondary第一次加入,这个是肯定的。

secondary落后的数据量超过了oplog的大小,这样也会被全量复制。

那什么是oplog的大小?前面说过oplog保存了数据的操作记录,secondary复制oplog并把里面的操作在secondary执行一遍。但是oplog也是mongodb的一个集合,保存在local.oplog.rs里,但是这个oplog是一个capped collection也就是固定大小的集合,新数据加入超过集合的大小会覆盖。所以这里需要注意,跨IDC的复制要设置合适的oplogSize,避免在生产环境经常产生全量复制。oplogSize 可以通过–oplogSize设置大小,对于linux 和windows 64位,oplog size默认为剩余磁盘空间的5%。

同步也并非只能从主节点同步,假设集群中3个节点,节点1是主节点在IDC1,节点2、节点3在IDC2,初始化节点2、节点3会从节点1同步数据。后面节点2、节点3会使用就近原则从当前IDC的副本集中进行复制,只要有一个节点从IDC1的节点1复制数据。

设置同步还要注意以下几点:

secondary不会从delayed和hidden成员上复制数据。

只要是需要同步,两个成员的buildindexes必须要相同无论是否是true和false。buildindexes主要用来设置是否这个节点的数据用于查询,默认为true。

如果同步操作30秒都没有反应,则会重新选择一个节点进行同步。

附:副本集不可用的应对之道:

当节点发生故障,或者因为网络原因失联,造成余下的节点小于等于副本集总节点数的一半,那么整个副本集中都不再存在主节点,原来的主节点会降级成为从节点!此时整个副本集呈现只读状态,也即不再可用。
当出现这种瘫痪情况,目前我还没找到将某个从节点切换为主节点的方法,也许本来就不存在这种方法。因为这和MongoDB的Primary选举策略有关:如果情况不是Secondary宕机,而是网络断开,那么可能出现位于不同IDC的节点各自选取自己为Primary。这样在网络恢复后就需要处理复杂的一致性问题。而且断开的时间越长,时间越复杂。所以MongoDB选择的策略是如果集群中存活节点数量不够,则不选取Primary。
所以
1) 一方面要极力避免出现这种存活节点不够半数的情况,在规划副本集的时候就注意:
设置仲裁节点。
节点总数为奇数,且主节点所在的IDC,拥有超过半数的节点数量
2) 注意对节点的备份,必要时可以对节点进行恢复
也可以按照相同配置建立一个全新的从节点,恢复副本集后,系统会自动同步数据。但猜测数据量比较大的情况下,耗时会比较长,所以平时对从节点进行备份,还是有必要。

MongoDB副本集

MongoDB的副本集不同于以往的主从模式。
在集群Master故障的时候,副本集可以自动投票,选举出新的Master,并引导其余的Slave服务器连接新的Master,
而这个过程对于应用是透明的。可以说MongoDB的副本集是自带故障转移功能的主从复制。

1.相对于传统主从模式的优势


传统的主从模式,需要手工指定集群中的Master。
如果Master发生故障,一般都是人工介入,指定新的Master。
这个过程对于应用一般不是透明的,往往伴随着应用重新修改配置文件,重启应用服务器等。
11
而MongoDB副本集,集群中的任何节点都可能成为Master节点。
一旦Master节点故障,则会在其余节点中选举出一个新的Master节点。
并引导剩余节点连接到新的Master节点。这个过程对于应用是透明的。
12

2. Bully选举算法


Bully算法是一种协调者(主节点)竞选算法,主要思想是集群的每个成员都可以声明它是主节点并通知其他节点。
别的节点可以选择接受这个声称或是拒绝并进入主节点竞争。被其他所有节点接受的节点才能成为主节点。
节点按照一些属性来判断谁应该胜出。这个属性可以是一个静态ID,也可以是更新的度量像最近一次事务ID(最新的节点会胜出)


他的选举过程大致如下:

1.得到每个服务器节点的最后操作时间戳。每个mongodb都有oplog机制会记录本机的操作,方便和主服务器进行对比数据是否同步还可以用于错误恢复。

2.如果集群中大部分服务器down机了,保留活着的节点都为 secondary状态并停止,不选举了。

3.如果集群中选举出来的主节点或者所有从节点最后一次同步时间看起来很旧了,停止选举等待人来操作。

4.如果上面都没有问题就选择最后操作时间戳最新(保证数据是最新的)的服务器节点作为主节点。
选举的触发条件
1.初始化一个副本集时。
2.副本集和主节点断开连接,可能是网络问题。
3.主节点挂掉。
4.人为介入,比如修改节点优先级等
5.选举还有个前提条件,参与选举的节点数量必须大于副本集总节点数量的一半,如果已经小于一半了所有节点保持只读状态。


3 .搭建副本集集群


每个虚拟机都使用如下的配置文件启动实例:
mongod --dbpath=/data/db --logpath=/var/log/mongodb.log --port 27017 --replSet mvbox --maxConns=2000 --logappend --fork

然后在任意一台虚拟机登陆mongo,输入如下设置
config = { _id:"mvbox", members:[
{_id:0,host:"192.168.1.1:27017"},
{_id:1,host:"192.168.1.2:27017"},
{_id:2,host:"192.168.1.3:27017"}]
}
rs.initiate(config);
可以看到副本集已经生效


2 可以使用rs.status()查看集群状态,或者rs.isMaster() 3

4. 更改节点优先级

修改节点的优先级可以触发重新选举,这样可以人工指定主节点。
使用如下命令,在主节点登录,将192.168.1.3提升为Master。
rs.conf();
cfg=rs.conf();
cfg.members[0].priority=1
cfg.members[1].priority=1
cfg.members[2].priority=10
rs.reconfig(cfg);

需要注意的是,修改节点优先级需要登录Master节点运行。否则报错。
4

再次查看集群状态,可以看到192.168.1.3已经作为Master运行
5

5 .节点类型


MongoDB的节点类型有主节点(Master),副本节点(Slave或者称为Secondary),仲裁节点,Secondary-Only节点,Hidden节点,Delayed节点和Non-Voting节点。

仲裁节点不存储数据,只是负责故障转移的群体投票,这样就少了数据复制的压力。

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

Hidden:这类节点是不能够被客户端制定IP引用,也不能被设置为主节点,但是可以投票,一般用于备份数据。

Delayed:可以指定一个时间延迟从primary节点同步数据。主要用于备份数据,如果实时同步,误删除数据马上同步到从节点。所以延迟复制主要用于避免用户错误。

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

6 .设置隐藏节点(Hidden)


隐藏节点可以在选举中投票,但是不能被客户端引用,也不能成为主节点。也就是说这个节点不能用于读写分离的场景。
将192.168.1.3设置为隐藏节点。
注意,只有优先级为0的成员才能设置为隐藏节点。
如果设置优先级不为0的节点为隐藏节点,则报错如下
13

使用如下命令设置隐藏节点
cfg=rs.conf();
cfg.members[0].priority=10
cfg.members[1].priority=1
cfg.members[2].priority=0
cfg.members[2].hidden=1
rs.reconfig(cfg);
设置完成之后,使用rs.status()查看该节点还是SECONDARY状态。
但是通过rs.isMaster()和rs.conf()可以看到这个节点的变化。
rs.isMaster()的hosts中192.168.1.3节点已经不可见
14
并且rs.conf()显示该节点状态为hidden

15

7 .设置仲裁节点


仲裁节点不存储数据,只是用于投票。所以仲裁节点对于服务器负载很低。
节点一旦以仲裁者的身份加入集群,他就只能是仲裁者,无法将仲裁者配置为非仲裁者,反之也是一样。
另外一个集群最多只能使用一个仲裁者,额外的仲裁者拖累选举新Master节点的速度,同时也不能提供更好的数据安全性。
初始化集群时,设置仲裁者的配置如下
config = { _id:"mvbox", members:[
{_id:0,host:"192.168.1.1:27017"},
{_id:1,host:"192.168.1.2:27017",arbiterOnly:true},
{_id:2,host:"192.168.1.3:27017"}]
}
使用仲裁者主要是因为MongoDB副本集需要奇数成员,而又没有足够服务器的情况。在服务器充足的情况下,不应该使用仲裁者节点。

8 .设置延迟复制节点


MongoDB官方没有增量备份方案,只有一个导出的工具mongodump。
他不能像数据库一样,通过binlog或者归档日志将数据推到事故发生的前一刻。
假设每天凌晨2点使用mongodump备份,而下午5点发生事故,数据库损毁,则凌晨2点到下午5点的数据全部都会丢失。
虽然副本集可以一定程度避免这个问题,但是默认情况下不能避免人为的失误。
比如没有指定筛选条件删除了全部的数据。副本节点会应用这个命令,删除所有副本节点的数据。
在这个场景下,可以使用延迟节点,它会延迟应用复制。
如果主节点发生了人为的失误,而这个操作因为延迟的原因,还没有应用在延迟节点。
这个时候,修改延迟节点的优先级为最高级,使他成为新的Master服务器。

延迟节点的优先级必须为0.这个和hidden节点是一样的。
设置192.168.1.2为延迟节点
cfg=rs.conf();
cfg.members[1].priority=0
cfg.members[1].slaveDelay=3600
rs.reconfig(cfg);
slaveDelay的单位是秒
在192.168.1.1主节点删除一个集合所有数据,模拟人为失误。
16

在192.168.1.3查看,发现数据已经全部丢失。
17
而在192.168.1.2延迟节点,可以看到因为延迟复制的缘故,数据还在。
18
这个时候千万不要提升延迟节点的优先级。因为这样他会立即应用原主节点的所有操作,并成为新的主节点。这样误操作就同步到了延迟节点。
首先,关闭副本集中其他的成员,除了延迟节点。
删除其他成员数据目录中的所有数据。确保每个其他成员的数据目录都是空的(除了延迟节点)
重启其他成员,他们会自动从延迟节点中恢复数据。
9 .设置Secondary-Only节点

Priority为0的节点永远不能成为主节点,所以设置Secondary-only节点只需要将其priority设置为0.

10 .设置Non-Voting节点


假设设置192.168.1.1不能投票,则使用如下命令
cfg=rs.conf();
cfg.members[0].votes=0;
rs.reconfig(cfg);
11 .副本集成员状态

副本集成员状态指的是rs.status()的stateStr字段
19
STARTUP:刚加入到复制集中,配置还未加载

STARTUP2:配置已加载完,初始化状态

RECOVERING:正在恢复,不适用读

ARBITER: 仲裁者

DOWN:节点不可到达

UNKNOWN:未获取其他节点状态而不知是什么状态,一般发生在只有两个成员的架构,脑裂

REMOVED:移除复制集

ROLLBACK:数据回滚,在回滚结束时,转移到RECOVERING或SECONDARY状态

FATAL:出错。查看日志grep “replSet FATAL”找出错原因,重新做同步

PRIMARY:主节点

SECONDARY:备份节点

12. 读写分离

如果Master节点读写压力过大,可以考虑读写分离的方案。
20

不过需要考虑一种场景,就是主服务器的写入压力非常的大,所以副本节点复制的写入压力同样很大。
这时副本节点如果读取压力也很大的话,根据MongoDB库级别读写锁的机制,
很可能复制写入程序拿不到写锁,从而导致副本节点与主节点有较大延迟。

如果进行读写分离,首先需要在副本节点声明其为slave,

21
其中的ReadRreference有几种设置,

primary:默认参数,只从主节点上进行读取操作;

primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。

secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。

secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据;

nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。
参考:http://www.lanceyan.com/tech/mongodb/mongodb_repset1.html

Composer 是什么

简单来说,Composer 是一个新的安装包管理工具,服务于 PHP 生态系统。它实际上包含了两个部分:ComposerPackagist。下面我们就简单说一下他们各自的用途。

Composer

Composer 是由 Jordi Boggiano 和 Nils Aderman 创造的一个命令行工具,它的使命就是帮你为项目自动安装所依赖的开发包。Composer 包含了一个依赖解析器,用来处理开发包之间复杂的依赖关系;另外,它还包含了下载器、安装器等有趣的东西。

作为一个用户,你所要做的就是在 composer.json 文件中声明当前项目所依赖的开发包,然后运行composer.phar install 就行了。composer.json 文件定义了当前项目所依赖的开发包和 composer 的配置信息。下面是一个小型实例:

{
    "require": {
        "monolog/monolog": "1.2.*"
    }
}

Packagist

Packagist 是 Composer 的默认的开发包仓库。你可以将自己的安装包提交到 packagist,将来你在自己的 VCS (源码管理软件,比如 Github)仓库中新建了 tag 或更新了代码,packagist 都会自动构建一个新的开发包。这就是 packagist 目前的运作方式,将来 packagist 将允许直接上传开发包。

学习站:http://www.phpcomposer.com/

Lumen与Laravel的关系

Lumen 是一个由 Laravel 组件搭建而成的微框架, 由 Laravel 官方维护. Lumen 为速度而生, 是当前最快的 PHP 框架之一, 甚至比类似的微框架 Silex 速度还要快.从上面可以看出 Lumen 是去除了很多 配置和可自定义的选项的 Laravel。也就是说如果你先了解了 Laravel 就很容易掌握 Lumen 。

Solr 增量导入数据原理

solr导入数据库数据创建索引时提供了full-import和delta-import两种导入方式,这篇文章主要讲解后者。

所谓delta-import主要是对于数据库(也可能是文件等等)中增加或者被修改的字段进行导入。主要原理是利用率每次我们进行import的时候在solr.home\conf下面生成的dataimport.properties文件,此文件里面有最近一次导入的相关信息。这个文件如下:

 #Wed Apr 21 16:48:27 CST 2010

 last_index_time=2010-04-21 16\:48\:24

 id.last_index_time=2010-04-21 16\:48\:24

其实last_index_time是最近一次索引(full-import或者delta-import)的时间。

通过比较这个时间和我们数据库表中的timestamp列即可得出哪些是之后修改或者添加的。

solr原理

1.solr原理:

我本人的理解:solr是为解决高性能的全文索引而出现的,它将用户输入的关键字进行智能分解,分解成一个个词,过滤掉一些多余的停词及空格等,比如,“在”、“里面”、“也”、“的”、“它”、“为”这些词都是停止词。这些词因为使用频率过高,几乎 每个网页上都存在,所以搜索引擎开发人员都将这一类词语全部忽略掉。如果我们的网站上存在大量这样的词语,那么相当于浪费了很多资源。然后将分解之后的词去建好的solr索引的字段中根据词的比重逐一进行匹配,最后将符合条件的数据返回给用户。

2.分词原理:

建立索引和查询的过程中,都是以基本的语素项为单位的。基本的语素项就是通过分词得到。这个过程决定了索引单元金额最终的匹配过程。 
分词在文本索引的建立过程和用户提交检索过程中都存在。利用相同的分词器,把短语或者句子切分成相同的结果,才能保证检索过程顺利进行。

1、 英文分词的原理 
基本的处理流程是:输入文本、词汇分割、词汇过滤(去除停留词)、词干提取(形态还原)、大写转为小写、结果输出。 
2、 中文分词原理 
中文分词比较复杂,并没有英文分词那么简单。这主要是因为中文的词与词之间并不像英文中那样用空格来隔开。
主要的方法有三种:基于词典匹配的分词方法、基于语义理解的分词、基于词频统计的分词。

3.Lucene:分词器和索引文件

1、分词器

分词器,对文本资源进行切分,将字符文本串按照一定的规则切分为一个个可以进行索引的最小单位(关键词),以便检索时使用。

建立索引和进行检索时都要用到分词器。为了保证能正确的检索到结果,在建立索引与进行检索时使用的分词器应是同一个。

2、索引文件结构

索引库是一组索引文件的集合。

索引文件的检索:索引表规模相对较小,文档集合规模较大。进行检索时,先从检索索引表开始,然后找到相对应的文档。如果查询中仅包含一个关键词,则在索引表中找到该关键词,并取出它所对应的文档就可以了。如果查询中包含多个关键词,则需要将各个关键字检索出的文档记录进行合并。


索引文件的维护:维护索引常使用三个操作:插入、删除和更新文档。但是更新操作需要较高的代价,因为文档修改后(即使是很小的修改),也可以造成文档中的很多的关键词的位置发生了变化,这时需要频繁的读取和修改记录,这种代价是相当高的。因此,一般不进行更新操作,而是使用“先删除,后创建”的方式代替更新操作。

3、常用的中文分词器

ik-analyzer:IK Analyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始, IKAnalyzer已经推出了4个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。从3.0版本开始,IK发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。在2012版本中,IK实现了简单的分词歧义排除算法,标志着IK分词器从单纯的词典分词向模拟语义分词衍化。
Ansj:Ansj中文分词是一款纯Java的主要应用于自然语言处理的高精度的中文分词。追求的目标是--准确-高效-自由。包括:中文分词、人名识别、地名识别、组织机构名识别、多级词性标注、关键词提取、指纹提取支持行业词典、用户自定义词典。Ansj 是基于中科院的 ictclas 中文分词算法,比其他常用的开源分词工具(如mmseg4j)的分词准确率更高。
在线演示:http://ansj.sdapp.cn/demo/seg.jsp
官网地址:http://www.ansj.org/
Github地址:https://github.com/ansjsun/ansj_seg
mmseg4j:用 Chih-Hao Tsai 的 MMSeg 算法(http://technology.chtsai.org/mmseg/)实现的中文分词器,并实现 lucene 的 analyzer 和 solr 的TokenizerFactory 以方便在Lucene和Solr中使用。MMSeg 算法有两种分词方法:Simple和Complex,都是基于正向最大匹配。Complex 加了四个规则过虑。官方说:词语的正确识别率达到了 98.41%。mmseg4j 已经实现了这两种分词算法。
imdict-chinese-analyzer:imdict-chinese-analyzer 是 imdict智能词典 的智能中文分词模块,算法基于隐马尔科夫模型(Hidden Markov Model, HMM),是中国科学院计算技术研究所的ictclas中文分词程序的重新实现(基于Java),可以直接为lucene搜索引擎提供简体中文分词支持。
ictclas4j:ICTCLAS4j中文分词系统是sinboy在中科院张华平和刘群老师的研制的FreeICTCLAS的基础上完成的一个java开源分词项目,简化了原分词程序的复杂度,旨在为广大的中文分词爱好者一个更好的学习机会。关于ICTCLAS分词系统的讨论,请访问google group关于ictclas分词系统的讨论组http://groups.google.com/group/ictclas
ICTCLAS汉语分词系:http://ictclas.org/

4.solr的配置文件:

(1)solr.xml
 与整个搜索引擎相关的配置。
(2)solrconfig.xml
 与某个core相关的主要配置,如定义updatehandler用于索引文件,requesthandler用于搜索内容等。
(3)schema.xml
 定义了某类型文档的索引格式。