• mysql 自动检测主从同步状态的一个小脚本

    Slave机器的IO和SQL状态都必须为YES,才是同步状态;

    #!/bin/bash 
    #Check MySQL Slave's Runnning Status
    #Crontab time 00:10
    
    MYSQLPORT=`netstat -na|grep "LISTEN"|grep "3306"|awk -F[:" "]+ '{print $5}'`
    MYSQLIP=`ifconfig eth0|grep "inet addr" | awk -F[:" "]+ '{print $4}'`
    STATUS=$(/usr/bin/mysql -uroot -p123qwe -S /var/lib/mysql/mysql.sock -e "show slave status\G" | grep -i "running")
    IO_env=`echo $STATUS | grep IO | awk  ' {print $2}'`
    SQL_env=`echo $STATUS | grep SQL | awk  '{print $2}'`
    DATA=`date +"%y-%m-%d %H:%M:%S"`
    
    function checkMysqlStatus(){
    	if [ "$MYSQLPORT" == "3306" ]
    	then
    		/usr/bin/mysql -uroot -p123qwe --connect_timeout=5 -e "show databases;" &>/dev/null 2>&1
    		if [ $? -ne 0 ]
    		then
    			echo "Server: $MYSQLIP mysql is down, please try to restart mysql by manual!" > /var/log/mysqlerr
                mail -s "WARN! server: $MYSQLIP  mysql is down." xxxx@126.com < /var/log/mysqlerr
    		else
    			echo "mysql is running..."
    		fi
    	else
    		mail -s "WARN!Server: $MYSQLIP mysql is down." mailcity@126.com
    	fi
    }
     
    checkMysqlStatus
    
    if [ "$IO_env" = "Yes" -a "$SQL_env" = "Yes" ]
    then
      echo "MySQL Slave is running!"
    else
      echo "####### $DATA #########">> /var/log/mysql/mysql_slave_status.log
      echo "MySQL Slave is not running!" >>    /var/log/mysql/mysql_slave_status.log
      echo "MySQL Slave is not running!" | mail -s "WARN! $MYSQLIP MySQL Slave is not running." xxxx@126.com
    fi
    
  • 压测工具 siege

    siege下载地址:http://www.joedog.org/pub/siege/siege-3.0.3.tar.gz

    centos 下编译安装

    ./configure

    make

    make install

    创建.siegec.config配置文件到当前用户的家目录下  /roor/.siegerc
    /usr/local/bin/siege.config

    使用

    50个用户(每次并发量,注意不是每秒并发量) 重复100次 共产生 50 * 100 = 5000个请求
    /usr/local/siege/bin/siege -c 50 -r 100  https://www.abc.com/a.php

    50个用户 重复100次 发送GET参数
    /usr/local/siege/bin/siege -c 50 -r 100  https://www.abc.com/a.php?name=zhangsan

    50个用户 重复100次 发送POST参数 (注意引号)
    /usr/local/siege/bin/siege -c 50 -r 100  “https://www.abc.com/a.php POST name=zhangsan”

    50个用户 重复100次 发送POST参数(从文件中读取)
    /usr/local/siege/bin/siege -c 50 -r 100  “https://www.abc.com/a.php POST < /root/ab_test/post.xml”

    另外还有发送时间参数等
    详情请man 或 siege -h
    参考链接:http://www.joedog.org/siege-home/

  • 张开你的大嘴

    昨晚帮一个美女装无线路由,折腾完都12点了,
    走的时候她竟然跟我说晚上骑车不好,不安全,要不留下住一晚吧。
    真是可笑,哥的车技如同行云流水,怎么会不安全!?
    20分钟就到家啦!

    今天在十字路口碰到俩打架得,周围很多人看热闹,
    警察也来了,一打听才得知,
    原来这俩人是到十字路口烧纸的,
    其中一个烧纸的时候说:爸,你生前没花到多少钱,现在给你多烧点,弄个飞机开。
    另一个烧纸的听到后,边烧纸边嘟囔:操、真JB能吹,爸,我也给你多烧点,买个大炮,专门轰飞机。
    然后就打起来了!

    某村搞计划生育,成年男子一律结扎。一个老光棍坐不住了,到计生办要求结扎。
    工作人员火了:你凑什么热闹,人家结扎的都是有老婆的,你一个人扎啥子?
    老光棍也火了,愤愤地说:全村的男人都结扎了,那以后村里哪个女人怀孕了,
    不都要赖在我头上吗?老子没那么傻!

    牙疼到医院拔牙,前面有一个四五岁小男孩不愿意治疗,哭闹,怎么哄都不行。
    男孩儿妈妈就说:“不疼的,乖,要不让后面的叔叔先来,你看看叔叔疼不疼”
    男孩儿点头答应。于是我坐在治疗椅上治疗,医生很麻利地打麻药,拿钳子拔牙。
    尼玛拔断了有没有,然后各种拽拉,终于拔掉了,弄的老子满嘴是血。
    疼的我啊啊大叫,眼泪都出来了。然后之前的那男孩吓傻了,哭着跑了……
    我只想说:姐,我真不是装的,是真他妈疼啊!你原谅我吧!

  • 思考

    有两种方式构建软件设计:一种是把软件做得很简单以至于明显找不到缺陷;另一种是把它做得很复杂以至于找不到明显的缺陷。

    一个人,一定要想清楚自己五年后要做什么,不要只看着眼前。

  • 运气就是机会碰巧撞到了你的努力

    行动是治愈恐惧的良药,而犹豫、拖延将不断滋养恐惧。

    活着一天,就是有福气,就该珍惜。当我哭泣我没有鞋子穿的时候,我发现有人却没有脚。

    宁愿做过了后悔,也不要错过了后悔。

    学的到东西的事情是锻炼,学不到的是磨练。

    过错是暂时的遗憾,而错过则是永远的遗憾!

    勇气是控制恐惧心理,而不是心里毫无恐惧。

    人一生下就会哭,笑是后来才学会的。所以忧伤是一种低级的本能,而快乐是一种更高级的能力。

    放弃该放弃的是无奈,放弃不该放弃的是无能,不放弃该放弃的是无知,不放弃不该放弃的是执著!

    一杯清水因滴入一滴污水而变污浊,一杯污水却不会因一滴清水的存在而变清澈。

    运气就是机会碰巧撞到了你的努力。

    只有你学会把自己已有的成绩都归零,才能腾出空间去接纳更多的新东西,如此才能使自己不断的超越自己。

  • linux抓包 tcpdump命令详解

    tcpdump可以将网络中传送的数据包的“头”完全截获下来提供分析。它支持针对网络层、协议、主机、网络或端口的过滤,并提供and、or、not等逻辑语句来帮助你去掉无用的信息。

    实用命令实例

    默认启动

    tcpdump

    普通情况下,直接启动tcpdump将监视第一个网络接口上所有流过的数据包。

    监视指定网络接口的数据包

    tcpdump -i eth1

    如果不指定网卡,默认tcpdump只会监视第一个网络接口,一般是eth0,下面的例子都没有指定网络接口。

    监视指定主机的数据包

    打印所有进入或离开sundown的数据包.

    tcpdump host sundown

    也可以指定ip,例如截获所有210.27.48.1 的主机收到的和发出的所有的数据包

    tcpdump host 210.27.48.1

    打印helios 与 hot 或者与 ace 之间通信的数据包

    tcpdump host helios and \( hot or ace \)

    截获主机210.27.48.1 和主机210.27.48.2 或210.27.48.3的通信

    tcpdump host 210.27.48.1 and \ (210.27.48.2 or 210.27.48.3 \)

    打印ace与任何其他主机之间通信的IP 数据包, 但不包括与helios之间的数据包.

    tcpdump ip host ace and not helios

    如果想要获取主机210.27.48.1除了和主机210.27.48.2之外所有主机通信的ip包,使用命令:

    tcpdump ip host 210.27.48.1 and ! 210.27.48.2

    截获主机hostname发送的所有数据

    tcpdump -i eth0 src host hostname

    监视所有送到主机hostname的数据包

    tcpdump -i eth0 dst host hostname

    监视指定主机和端口的数据包

    如果想要获取主机210.27.48.1接收或发出的telnet包,使用如下命令

    tcpdump tcp port 23 host 210.27.48.1

    对本机的udp 123 端口进行监视 123 为ntp的服务端口

    tcpdump udp port 123

    监视指定网络的数据包

    打印本地主机与Berkeley网络上的主机之间的所有通信数据包(nt: ucb-ether, 此处可理解为’Berkeley网络’的网络地址,此表达式最原始的含义可表达为: 打印网络地址为ucb-ether的所有数据包)

    tcpdump net ucb-ether

    打印所有通过网关snup的ftp数据包(注意, 表达式被单引号括起来了, 这可以防止shell对其中的括号进行错误解析)

    tcpdump 'gateway snup and (port ftp or ftp-data)'

    打印所有源地址或目标地址是本地主机的IP数据包

    (如果本地网络通过网关连到了另一网络, 则另一网络并不能算作本地网络.(nt: 此句翻译曲折,需补充).localnet 实际使用时要真正替换成本地网络的名字)

    tcpdump ip and not net localnet

    监视指定协议的数据包

    打印TCP会话中的的开始和结束数据包, 并且数据包的源或目的不是本地网络上的主机.(nt: localnet, 实际使用时要真正替换成本地网络的名字))

    tcpdump 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0 and not src and dst net localnet'

    打印所有源或目的端口是80, 网络层协议为IPv4, 并且含有数据,而不是SYN,FIN以及ACK-only等不含数据的数据包.(ipv6的版本的表达式可做练习)

    tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'

    (nt: 可理解为, ip[2:2]表示整个ip数据包的长度, (ip[0]&0xf)<<2)表示ip数据包包头的长度(ip[0]&0xf代表包中的IHL域, 而此域的单位为32bit, 要换算

    成字节数需要乘以4, 即左移2. (tcp[12]&0xf0)>>4 表示tcp头的长度, 此域的单位也是32bit, 换算成比特数为 ((tcp[12]&0xf0) >> 4) << 2,
    即 ((tcp[12]&0xf0)>>2). ((ip[2:2] – ((ip[0]&0xf)<<2)) – ((tcp[12]&0xf0)>>2)) != 0 表示: 整个ip数据包的长度减去ip头的长度,再减去
    tcp头的长度不为0, 这就意味着, ip数据包中确实是有数据.对于ipv6版本只需考虑ipv6头中的’Payload Length’ 与 ‘tcp头的长度’的差值, 并且其中表达方式’ip[]’需换成’ip6[]’.)

    打印长度超过576字节, 并且网关地址是snup的IP数据包

    tcpdump 'gateway snup and ip[2:2] > 576'

    打印所有IP层广播或多播的数据包, 但不是物理以太网层的广播或多播数据报

    tcpdump 'ether[0] & 1 = 0 and ip[16] >= 224'

    打印除’echo request’或者’echo reply’类型以外的ICMP数据包( 比如,需要打印所有非ping 程序产生的数据包时可用到此表达式 .
    (nt: ‘echo reuqest’ 与 ‘echo reply’ 这两种类型的ICMP数据包通常由ping程序产生))

    tcpdump 'icmp[icmptype] != icmp-echo and icmp[icmptype] != icmp-echoreply'

    tcpdump 与wireshark

    Wireshark(以前是ethereal)是Windows下非常简单易用的抓包工具。但在Linux下很难找到一个好用的图形化抓包工具。
    还好有Tcpdump。我们可以用Tcpdump + Wireshark 的完美组合实现:在 Linux 里抓包,然后在Windows 里分析包。

    tcpdump tcp -i eth1 -t -s 0 -c 100 and dst port ! 22 and src net 192.168.1.0/24 -w ./target.cap

    (1)tcp: ip icmp arp rarp 和 tcp、udp、icmp这些选项等都要放到第一个参数的位置,用来过滤数据报的类型
    (2)-i eth1 : 只抓经过接口eth1的包
    (3)-t : 不显示时间戳
    (4)-s 0 : 抓取数据包时默认抓取长度为68字节。加上-S 0 后可以抓到完整的数据包
    (5)-c 100 : 只抓取100个数据包
    (6)dst port ! 22 : 不抓取目标端口是22的数据包
    (7)src net 192.168.1.0/24 : 数据包的源网络地址为192.168.1.0/24
    (8)-w ./target.cap : 保存成cap文件,方便用ethereal(即wireshark)分析

    使用tcpdump抓取HTTP包

    tcpdump  -XvvennSs 0 -i eth0 tcp[20:2]=0x4745 or tcp[20:2]=0x4854

    0x4745 为”GET”前两个字母”GE”,0x4854 为”HTTP”前两个字母”HT”。

    tcpdump 对截获的数据并没有进行彻底解码,数据包内的大部分内容是使用十六进制的形式直接打印输出的。显然这不利于分析网络故障,通常的解决办法是先使用带-w参数的tcpdump 截获数据并保存到文件中,然后再使用其他程序(如Wireshark)进行解码分析。当然也应该定义过滤规则,以避免捕获的数据包填满整个硬盘。

  • 商业计划

    少听身边人的话,多听有结果人的话。

    预测大趋势,就能找到大商机;预测小趋势,只能找到小商机。商机有方法,创业有思路。

    商学院百万学费收获的最有用东西

    战略方向:
    一、SWOT分析法:

    Strengths:优势;
    Weaknesses:劣势;
    Opportunities:机会;
    Threats:威胁

    意义:帮您清晰地把握全局,分析自己在资源方面的优势与劣势,把握环境提供的机会,防范可能存在的风险与威胁,对我们的成功有非常重要的意义。

    战术层面:

    二、PDCA循环规则

    Plan:制定目标与计划;
    Do:任务展开,组织实施;
    Check:对过程中的关键点和最终结果进行检查;
    Action:纠正偏差,对成果进行标准化,并确定新的目标,制定下一轮计划。

    意义:每一项工作,都是一个pdca循环,都需要计划、实施、检查结果,并进一步进行改进,同时进入下一个循环,只有在日积月累的渐进改善中,才可能会有质的飞跃,才可能取得完善每一项工作,完善自己的人生

    三、5W2H法

    What:工作的内容和达成的目标
    Why:做这项工作的原因
    Who:参加这项工作的具体人员,以及负责人
    When:在什么时间、什么时间段进行工作;
    Where:工作发生的地点
    How:用什么方法进行
    How much:需要多少成本

    意义:做任何工作都应该从5W2H来思考,这有助于我们的思路的条理化,杜绝盲目性。我们的汇报也应该用5W2H,能节约写报告及看报告的时间。

    四、SMART原则  目标管理

    Specific 具体的;
    Measurable 可测量的;
    Attainable 可达到的;
    Relevant 相关的;
    Time based 时间的;

    意义:人们在制定工作目标或者任务目标时,考虑一下目标与计划是不是SMART化的。只有具备SMART化的计划才是具有良好可实施性的,也才能指导保证计划得以实现。

    特别注明:有的又如此解释此原则
    —S代表具体(Specific),指绩效考核要切中特定的工作指标,不能笼统;
    —M代表可度量(Measurable),指绩效指标是数量化或者行为化的,验证这些绩效指标的数据或者信息是可以获得的;
    —A代表可实现(Attainable),指绩效指标在付出努力的情况下可以实现,避免设立过高或过低的目标;
    —R代表现实性(realistic),指绩效指标是实实在在的,可以证明和观察;
    —T代表有时限(time bound),注重完成绩效指标的特定期限。

    五、时间管理-重要与紧急

    急迫
    不急迫
    重要
    紧急状况
    迫切的问题
    限期完成的工作
    你不做其他人也不能做
    准备工作
    预防措施
    价值观的澄清
    计划
    人际关系的建立
    真正的再创造
    增进自己的能力
    不重要
    造成干扰的事、电话、
    信件、报告
    会议
    许多迫在眉捷的急事
    符合别人期望的事
    忙碌琐碎的事
    广告函件
    电话
    逃避性活动
    等待时间

    优先顺序=重要性*紧迫性
    在进行时间安排时,应权衡各种事情的优先顺序,要学会“弹钢琴”。
    对工作要有前瞻能力,防患于未然,如果总是在忙于救火,那将使我们的工作永远处理被动之中。

    六、任务分解法[WBS]

    即Work Breakdown  Structure,如何进行WBS分解:目标→任务→工作→活动

    WBS分解的原则:
    将主体目标逐步细化分解,最底层的任务活动可直接分派到个人去完成;每个任务原则上要求分解到不能再细分为止。

    WBS分解的方法:
    至上而下与至下而上的充分沟通;
    一对一个别交流;
    小组讨论

    WBS分解的标准:
    分解后的活动结构清晰;
    逻辑上形成一个大的活动;
    集成了所有的关键因素包含临时的里程碑和监控点;
    所有活动全部定义清楚

    意义:学会分解任务,只有将任务分解得足够细,您才能心里有数,您才能有条不紊地工作,您才能统筹  安排您的时间表

    价值创造

    七、二八原则

    巴列特定律:“总结果的80%是由总消耗时间中的20%所形成的。”   按事情的“重要程度”编排事务优先次序的准则是建立在“重要的少数与琐碎的多数”的原理的基础上。举例说明:
    80%的销售额是源自20%的顾客;
    80%的电话是来自20%的朋友;
    80%的总产量来自20%的产品;
    80%的财富集中在20%的人手中。

  • Redis持久化实践及灾难恢复模拟

    目前Redis持久化的方式有两种: RDB 和 AOF

    Redis在利用RDB和AOF进行恢复的时候,都会读取RDB或AOF文件,重新加载到内存中。

    RDB就是Snapshot快照存储,是默认的持久化方式。
    可理解为半持久化模式,即按照一定的策略周期性的将数据保存到磁盘。
    对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定义快照的周期。
    下面是默认的快照设置:

    save 900 1    #当有一条Keys数据被改变时,900秒刷新到Disk一次
    save 300 10   #当有10条Keys数据被改变时,300秒刷新到Disk一次
    save 60 10000 #当有10000条Keys数据被改变时,60秒刷新到Disk一次

    第一次Slave向Master同步的实现是:
    Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。
    第二次以及以后的同步实现是:
    Master将变量的快照直接实时依次发送给各个Slave。
    但不管什么原因导致Slave和Master断开重连都会重复以上两个步骤的过程。
    Redis的主从复制是建立在内存快照的持久化基础上的,只要有Slave就一定会有内存快照发生。

    RDB的不足:从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。

    ————————————————–

    AOF(Append-Only File)比RDB方式有更好的持久化性。
    由于在使用AOF持久化方式时,Redis会将每一个收到的写命令都通过Write函数追加到文件中,类似于MySQL的binlog。
    当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
    对应的设置参数为:
    $ vim /opt/redis/etc/redis_6379.conf

    appendonly yes       #启用AOF持久化方式
    appendfilename appendonly.aof #AOF文件的名称,默认为appendonly.aof
    # appendfsync always #每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但速度也是最慢的,一般不推荐使用。
    appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,是受推荐的方式。
    # appendfsync no     #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不被推荐。

    AOF的完全持久化方式同时也带来了另一个问题,持久化文件会变得越来越大。
    比如我们调用INCR test命令100次,文件中就必须保存全部的100条命令,但其实99条都是多余的。
    因为要恢复数据库的状态其实文件中保存一条SET test 100就够了。
    为了压缩AOF的持久化文件,Redis提供了bgrewriteaof命令。
    收到此命令后Redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件,以此来实现控制AOF文件的增长。
    由于是模拟快照的过程,因此在重写AOF文件时并没有读取旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的AOF文件。
    对应的设置参数为:
    $ vim /opt/redis/etc/redis_6379.conf

    no-appendfsync-on-rewrite yes   #在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
    auto-aof-rewrite-percentage 100 #当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
    auto-aof-rewrite-min-size 64mb  #当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。

    到底选择什么呢?下面是来自官方的建议:
    通常,如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。
    如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用RDB。
    很多用户仅使用了AOF,但是我们建议,既然RDB可以时不时的给数据做个完整的快照,并且提供更快的重启,所以最好还是也使用RDB。
    因此,我们希望可以在未来(长远计划)统一AOF和RDB成一种持久化模式。

    在数据恢复方面:
    RDB的启动时间会更短,原因有两个:
    一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。
    另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在CPU消耗上要远小于AOF日志的加载。

    ————————————————–

    灾难恢复模拟
    既然持久化的数据的作用是用于重启后的数据恢复,那么我们就非常有必要进行一次这样的灾难恢复模拟了。
    据称如果数据要做持久化又想保证稳定性,则建议留空一半的物理内存。因为在进行快照的时候,fork出来进行dump操作的子进程会占用与父进程一样的内存,真正的copy-on-write,对性能的影响和内存的耗用都是比较大的。
    目前,通常的设计思路是利用Replication机制来弥补aof、snapshot性能上的不足,达到了数据可持久化。
    即Master上Snapshot和AOF都不做,来保证Master的读写性能,而Slave上则同时开启Snapshot和AOF来进行持久化,保证数据的安全性。

    首先,修改Master上的如下配置:
    $ sudo vim /opt/redis/etc/redis_6379.conf

    #save 900 1 #禁用Snapshot
    #save 300 10
    #save 60 10000
    
    appendonly no #禁用AOF

    接着,修改Slave上的如下配置:
    $ sudo vim /opt/redis/etc/redis_6379.conf

    save 900 1 #启用Snapshot
    save 300 10
    save 60 10000
    
    appendonly yes #启用AOF
    appendfilename appendonly.aof #AOF文件的名称
    # appendfsync always
    appendfsync everysec #每秒钟强制写入磁盘一次
    # appendfsync no  
    
    no-appendfsync-on-rewrite yes   #在日志重写时,不进行命令追加操作
    auto-aof-rewrite-percentage 100 #自动启动新的日志重写过程
    auto-aof-rewrite-min-size 64mb  #启动新的日志重写过程的最小值

    分别启动Master与Slave

    假设master当掉了

    在Slave上复制数据文件:appendonly.aof,dump.rdb  拷贝到master相应的位置

    启动Master上的Redis

    不出意外,恢复成功

    在此次恢复的过程中,我们同时复制了AOF与RDB文件,那么到底是哪一个文件完成了数据的恢复呢?
    实际上,当Redis服务器挂掉时,重启时将按照以下优先级恢复数据到内存:
    1. 如果只配置AOF,重启时加载AOF文件恢复数据;
    2. 如果同时 配置了RDB和AOF,启动是只加载AOF文件恢复数据;
    3. 如果只配置RDB,启动是将加载dump文件恢复数据。
    AOF的优先级要高于RDB
  • Redis持久化

    本文内容来源于Redis 作者博文,Redis作者说,他看到的所有针对Redis的讨论中,对Redis持久化 的误解是最大的,于是他写了一篇长文来对Redis的持久化进行了系统性的论述。

    什么是持久化,简单来讲就是将数据放到断电后数据不会丢失的设备中。也就是我们通常理解的硬盘上。

    写操作的流程

    首先我们来看一下数据库在进行写操作时到底做了哪些事,主要有下面五个过程。

    1. 客户端向服务端发送写操作(数据在客户端的内存中)
    2. 数据库服务端接收到写请求的数据(数据在服务端的内存中)
    3. 服务端调用write(2) 这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)
    4. 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)
    5. 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)

    写操作大致有上面5个流程,下面我们结合上面的5个流程看一下各种级别的故障。

    • 当数据库系统故障时,这时候系统内核还是OK的,那么此时只要我们执行完了第3步,那么数据就是安全的,因为后续操作系统会来完成后面几步,保证数据最终会落到磁盘上。
    • 当系统断电,这时候上面5项中提到的所有缓存都会失效,并且数据库和操作系统都会停止工作。所以只有当数据在完成第5步后,机器断电才能保证数据不丢失,在上述四步中的数据都会丢失。

    通过上面5步的了解,可能我们会希望搞清下面一些问题:

    • 数据库多长时间调用一次write(2),将数据写到内核缓冲区
    • 内核多长时间会将系统缓冲区中的数据写到磁盘控制器
    • 磁盘控制器又在什么时候把缓存中的数据写到物理介质上

    对于第一个问题,通常数据库层面会进行全面控制。而对第二个问题,操作系统有其默认的策略,但是我们也可以通过POSIX API提供的fsync系列命令强制操作系统将数据从内核区写到磁盘控制器上。对于第三个问题,好像数据库已经无法触及,但实际上,大多数情况下磁盘缓存是被设置关闭的。或者是只开启为读缓存,也就是写操作不会进行缓存,直接写到磁盘。建议的做法是仅仅当你的磁盘设备有备用电池时才开启写缓存。

    所谓数据损坏,就是数据无法恢复,上面我们讲的都是如何保证数据是确实写到磁盘上去,但是写到磁盘上可能并不意味着数据不会损坏。比如我们可能一次写请求会进行两次不同的写操作,当意外发生时,可能会导致一次写操作安全完成,但是另一次还没有进行。如果数据库的数据文件结构组织不合理,可能就会导致数据完全不能恢复的状况出现。

    这里通常也有三种策略来组织数据,以防止数据文件损坏到无法恢复的情况:

    1. 第一种是最粗糙的处理,就是不通过数据的组织形式保证数据的可恢复性。而是通过配置数据同步备份的方式,在数据文件损坏后通过数据备份来进行恢复。实际上MongoDB在不开启journaling日志,通过配置Replica Sets时就是这种情况。
    2. 另一种是在上面基础上添加一个操作日志,每次操作时记一下操作的行为,这样我们可以通过操作日志来进行数据恢复。因为操作日志是顺序追加的方式写的,所以不会出现操作日志也无法恢复的情况。这也类似于MongoDB开启了journaling日志的情况。
    3. 更保险的做法是数据库不进行老数据的修改,只是以追加方式去完成写操作,这样数据本身就是一份日志,这样就永远不会出现数据无法恢复的情况了。实际上CouchDB就是此做法的优秀范例。

    RDB快照

    下面我们说一下Redis的第一个持久化策略,RDB快照。Redis支持将当前数据的快照存成一个数据文件的持久化机制。而一个持续写入的数据库如何生成快照呢。Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件。

    我们可以通过Redis的save指令来配置RDB快照生成的时机,比如你可以配置当10分钟以内有100次写入就生成快照,也可以配置当1小时内有1000次写入就生成快照,也可以多个规则一起实施。这些规则的定义就在Redis的配置文件中,你也可以通过Redis的CONFIG SET命令在Redis运行时设置规则,不需要重启Redis。

    Redis的RDB文件不会坏掉,因为其写操作是在一个新进程中进行的,当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件,这样在任何时候出现故障,Redis的RDB文件都总是可用的。

    同时,Redis的RDB文件也是Redis主从同步内部实现中的一环。

    但是,我们可以很明显的看到,RDB有他的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。在某些业务下,这是可以忍受的,我们也推荐这些业务使用RDB的方式进行持久化,因为开启RDB的代价并不高。但是对于另外一些对数据安全性要求极高的应用,无法容忍数据丢失的应用,RDB就无能为力了,所以Redis引入了另一个重要的持久化机制:AOF 日志。

    AOF日志

    aof日志的全称是append only file,从名字上我们就能看出来,它是一个追加写入的日志文件。与一般数据库的binlog不同的是,AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令。比如我们进行如下实验,使用Redis2.6版本,在启动命令参数中设置开启aof功能:

    ./redis-server --appendonly yes

    然后我们执行如下的命令:

    redis 127.0.0.1:6379> set key1 Hello
    OK
    redis 127.0.0.1:6379> append key1 " World!"
    (integer) 12
    redis 127.0.0.1:6379> del key1
    (integer) 1
    redis 127.0.0.1:6379> del non_existing_key
    (integer) 0

    这时我们查看AOF日志文件,就会得到如下内容:

    $ cat appendonly.aof
    *2
    $6
    SELECT
    $1
    0
    *3
    $3
    set
    $4
    key1
    $5
    Hello
    *3
    $6
    append
    $4
    key1
    $7
     World!
    *2
    $3
    del
    $4
    key1

    可以看到,写操作都生成了一条相应的命令作为日志。其中值得注意的是最后一个del命令,它并没有被记录在AOF日志中,这是因为Redis判断出这个命令不会对当前数据集做出修改。所以不需要记录这个无用的写命令。另外AOF日志也不是完全按客户端的请求来生成日志的,比如命令INCRBYFLOAT在记AOF日志时就被记成一条SET记录,因为浮点数操作可能在不同的系统上会不同,所以为了避免同一份日志在不同的系统上生成不同的数据集,所以这里只将操作后的结果通过SET来记录。

    AOF重写

    你可以会想,每一条写命令都生成一条日志,那么AOF文件是不是会很大?答案是肯定的,AOF文件会越来越大,所以Redis又提供了一个功能,叫做AOF rewrite。其功能就是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像一份老文件那样,可能记录了对同一个值的多次操作。其生成过程和RDB类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程中,所有的写操作日志还是会写到原来老的AOF文件中,同时还会记录在内存缓冲区中。当重完操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的AOF文件取代老的AOF文件。

    从上面的流程我们能够看到,RDB和AOF操作都是顺序IO操作,性能都很高。而同时在通过RDB文件或者AOF日志进行数据库恢复的时候,也是顺序的读取数据加载到内存中。所以也不会造成磁盘的随机读。

    AOF可靠性设置

    AOF是一个写文件操作,其目的是将操作日志写到磁盘上,所以它也同样会遇到我们上面说的写操作的5个流程。那么写AOF的操作安全性又有多高呢。实际上这是可以设置的,在Redis中对AOF调用write(2)写入后,何时再调用fsync将其写到磁盘上,通过appendfsync 选项来控制,下面appendfsync的三个设置项,安全强度逐渐变强。

    appendfsync no

    当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。

    appendfsync everysec

    当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。 所以,结论就是,在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。

    这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。

    appednfsync always

    当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响。

    对于pipelining有什么不同

    对于pipelining的操作,其具体过程是客户端一次性发送N个命令,然后等待这N个命令的返回结果被一起返回。通过采用pipilining就意味着放弃了对每一个命令的返回值确认。由于在这种情况下,N个命令是在同一个执行过程中执行的。所以当设置appendfsync为everysec时,可能会有一些偏差,因为这N个命令可能执行时间超过1秒甚至2秒。但是可以保证的是,最长时间不会超过这N个命令的执行时间和。

    与postgreSQL和MySQL的比较

    这一块就不多说了,由于上面操作系统层面的数据安全已经讲了很多,所以其实不同的数据库在实现上都大同小异。 总之最后的结论就是,在Redis开启AOF的情况下,其单机数据安全性并不比这些成熟的SQL数据库弱。

    这些持久化的数据有什么用,当然是用于重启后的数据恢复。 Redis是一个内存数据库,无论是RDB还是AOF,都只是其保证数据恢复的措施。 所以Redis在利用RDB和AOF进行恢复的时候,都会读取RDB或AOF文件,重新加载到内存中。 相对于MySQL等数据库的启动时间来说,会长很多,因为MySQL本来是不需要将数据加载到内存中的。

    但是相对来说,MySQL启动后提供服务时,其被访问的热数据也会慢慢加载到内存中,通常我们称之为预热,而在预热完成前,其性能都不会太高。而Redis的好处是一次性将数据加载到内存中,一次性预热。这样只要Redis启动完成,那么其提供服务的速度都是非常快的。

    而在利用RDB和利用AOF启动上,其启动时间有一些差别。RDB的启动时间会更短,原因有两个,一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作。在CPU消耗上要远小于AOF日志的加载。

    好了,大概内容就说到这里。更详细完整的版本请看Redis作者的博文:Redis persistence demystified。本文如有描述不周之处,就大家指正。

  • jquery datepicker 中文显示

        var initDatepicker = function() {
            $('input[type=date]').each(function() {
                var $input = $(this);
                $.datepicker.regional['zh-CN'] = {
                    clearText: '清除',
                    clearStatus: '清除已选日期',
                    closeText: '关闭',
                    closeStatus: '不改变当前选择',
                    prevText: '<上月',
                    prevStatus: '显示上月',
                    prevBigText: '<<',
                    prevBigStatus: '显示上一年',
                    nextText: '下月>',
                    nextStatus: '显示下月',
                    nextBigText: '>>',
                    nextBigStatus: '显示下一年',
                    currentText: '今天',
                    currentStatus: '显示本月',
                    monthNames: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'],
                    monthNamesShort: ['一','二','三','四','五','六', '七','八','九','十','十一','十二'],
                    monthStatus: '选择月份',
                    yearStatus: '选择年份',
                    weekHeader: '周',
                    weekStatus: '年内周次',
                    dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
                    dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
                    dayNamesMin: ['日','一','二','三','四','五','六'],
                    dayStatus: '设置 DD 为一周起始',
                    dateStatus: '选择 m月 d日, DD',
                    dateFormat: 'yy-mm-dd',
                    firstDay: 1,
                    initStatus: '请选择日期',
                    isRTL: false
                };
                $.datepicker.setDefaults($.datepicker.regional['zh-CN']);
                $input.datepicker({
                    numberOfMonths: 2,
                    showButtonPanel: true,
                    dateFormat: 'yy-mm-dd'
                });
            });
        };
        $(document).ready(initDatepicker);
    

    http://jqueryui.com/datepicker/