Tac say

只想做个程序演奏家

对Memory Reordering Caught in the Act的学习 续 - 关于go的部分

这篇主要解决上一篇遗留下来的问题,问题的简要描述请参看我发在SO上的帖子

主要的问题是用c++可以重现memory reordering,但go的程序没有重现

主要的结论是写go的时候我忘记设置GOMAXPROC,在目前这个go版本(1.2 rc2)下,不设置GOMAXPROC goroutine不会并发的,自然也没法设置memory reordering

此篇主要内容到此结束,以下是这两天的一些探索过程和技巧,觉得还是挺有意思的


go tool生成的汇编码和真实的汇编码是有很大差距的

这个结论并不奇怪,但是差异的程度还是会影响诸如lock-free的代码的使用前提

对以下代码做对比

x = 1
r1 = y

使用go tool 6g -S xxx.go反编译后的代码

0246 (a.go:25) MOVQ    $1,x+0(SB)   //X=1
0247 (a.go:26) MOVQ    y+0(SB),BX
0248 (a.go:26) MOVQ    BX,r1+0(SB)  //r1=Y

而真实运行在cpu上的代码(ndisasm -b 32 xxx)为

000013EB  C70425787F170001  mov dword [0x177f78],0x1     //X=1
         -000000
000013F6  48                dec eax
000013F7  8B1C25807F1700    mov ebx,[0x177f80]
000013FE  48                dec eax
000013FF  891C25687F1700    mov [0x177f68],ebx          //r1=Y
00001406  48                dec eax

可以看到在访问共享内存的前后多出了dec eax作为margin,这个原因不明,也没有找到相应的资料

但总的来说ndisasm产生的汇编代码更方便于对go行为的理解


一个小技巧快速定位汇编码

我对intel指令集和go的编译器知之甚少,读起汇编码来颇为费劲。

快速定位源码对应的汇编码的位置,比较方便的就是修改一个数值,比如x=1改为x=2,前后生成的汇编码diff一下,就可以大概确定位置了


替换c++生成文件的指令

在探索过程中,我想做个对比实验来证明是否上面所说的dec eax引起了c++和go在memory reordering上的差异,于是就想将dec eax也加到c++的生成文件中,这样就可以对比效果

碰到的问题是如果我直接将asm volatile("dec %eax")直接加到c++源码中,生成的汇编代码不是48,而是FExxxx。翻看Intel® 64 and IA-32 Architectures Software Developer’s Manual,可知dec有多种形式

但是我不想研究为什么编译器会选择FExxxx而不是48,而是想尽快将c++生成的汇编代码形式做成和go一样。于是就有了下面的步骤:

  1. 48有两个字节,我也选取两个字节的op写在c++源码中,比如asm volatile("cli")
  2. c++编译生成,然后用16进制编辑器将cli生成的两个字节换成48即可

之所以选择替换是因为怕有checksum或者内存位置的偏移,我也不知道有还是没有…

对比实验证明dec eax不是引起差异的原因

对Memory Reordering Caught in the Act的学习

最近迷上了preshing.com,真的是非常专业的blog,每篇深浅合适而且可以相互印证,达到出书的质量了

学习了Memory Reordering Caught in the Act,内容很简单,主要是说“即使汇编码是顺序的,CPU执行时会对Load-Save进行乱序执行,导致无锁的两线程出现意料之外的结果”

简述一下:

  • 首先我们有两个线程,Ta和Tb,且有四个公共变量,a,b,r1,r2
  • Ta的代码是 a=1, r1=b
  • Tb的代码是 b=1, r2=a
  • 保证编译器不做乱序优化
  • 由于两个线程的读都在写之后,那么理论上,r1和r2中至少有一个应为1,或者都为1
  • 但实际并非如此

原因是CPU会做乱序执行,因为Ta/Tb的代码乱序后,比如r1=b, a=1,从单线程的角度来看对结果没有影响。而对于多线程,就会出现r1=r2=0的状况

解决方案是在两句之间插入Load-Save fence,参看这里

我自己用go想重现这个场景,代码参看最后。但是奇怪的是go的编译码跟文章描述的差不多

[thread 1]
...
MOVQ    $1,a+0(SB)
MOVQ    b+0(SB),BX
MOVQ    BX,r1+0(SB)

[thread 2]
MOVQ    $1,b+0(SB)
MOVQ    a+0(SB),BX
MOVQ    BX,r2+0(SB)

但是在MBP (Intel Core i7)上跑并没有出现CPU乱序的现象,希望有同学能帮我提供线索,谢谢

(2013.11.11 更新:关于以上现象的原因参看续 - 关于go的部分)

go 代码:

package main

import (
    "fmt"
    "math/rand"
)

var x, y, r1, r2 int
var detected = 0

func randWait() {
    for rand.Intn(8) != 0 {
    }
}

func main() {
    beginSig1 := make(chan bool, 1)
    beginSig2 := make(chan bool, 1)
    endSig1 := make(chan bool, 1)
    endSig2 := make(chan bool, 1)
    go func() {
        for {
            <-beginSig1
            randWait()
            x = 1
            r1 = y
            endSig1 <- true
        }
    }()
    go func() {
        for {
            <-beginSig2
            randWait()
            y = 1
            r2 = x
            endSig2 <- true
        }
    }()
    for i := 1; ; i = i + 1 {
        x = 0
        y = 0
        beginSig1 <- true
        beginSig2 <- true
        <-endSig1
        <-endSig2
        if r1 == 0 && r2 == 0 {
            detected = detected + 1
            fmt.Println(detected, "reorders detected after ", i, "iterations")
        }
    }
}

对heartbeat φ累积失败检测算法的学习

偶尔读到了这篇”φ累积失败检测算法“,写的非常不错。藉此了解了这个用于heartbeat检测的算法,在此记录一下我自己理解的简单版本

heartbeat时我们使用固定的时间限制t0,当heartbeat的返回时长超过t0时,就认为heartbeat失败。这个方法的弊端是:固定的t0是在事先测定的,不会随网络状况的变化而智能变化。φ累积失败检测算法就是要解决这个问题

失败检验算法的基本思想就是:成功判定“heartbeat失败”的概率符合正态分布曲线,x轴是本次心跳距上次心跳的差距时间,y轴是差距为x的心跳的概率。


也就是说,假设我们已经有一条正态分布的曲线,当前时间是Tnow,上次心跳成功的时间是Tlast,那么从(Tlast-Tnow) ~ +∞这个区间内的积分(设为w,w<1)就代表某心跳间隔从Tlast维持到大于Tnow的时间的概率,即在Tnow时判定“heartbeat失败”的失败率,就是说如果我们在Tnow这个时间点判定“heartbeat失败”,那么有w的概率我们做出了错误的判定(heartbeat本该是成功的,也许只是被延迟了= =)

臆测这个算法的基本步骤是:

  1. 我们假设判定失败率的阈值是<=10%,也就是允许我们判定“heartbeat失败”时最大失败率为10%。
  2. 取样本空间,比如前N次心跳的差距时间(心跳接收时间-上次心跳的接收时间)。计算这个样本空间的均值和方差,就可以计算出正态分布曲线
  3. 在某时间Tnow,计算(Tlast-Tnow) ~ +∞这个区间内的积分(设为w),即为判定“heartbeat失败”的失败率,若大于阈值10%,则可以判定“heartbeat”失败
  4. 重复取样,继续算法

到此基本结束,以下是对原文”φ累积失败检测算法“的一些个人补充

  • 原文有φ这个变量,主要是因为计算出来的判定失败率可能经常是非常小的小数,所以φ取其负对数,方便比较
  • 在此不再重复引用原文的公式

最后,可参考论文 The φ Accrual Failure Detector

  • 这篇论文非常详细(啰嗦)地描述了要解决的问题场景
  • 这篇论文给出了一般性的累积失败检测法要满足的特性
  • 这篇论文给出了用正态分布曲线来计算的步骤
  • 这篇论文给出了算法正确性的比较结果

最后的最后,推荐这个大牛陈国庆的blog,其中文章写的质量高,里面也有对Paxos算法的介绍,配合paxos的wiki,解析的很到位

对Mysql Bug #70307 的学习

之前描述Mysql 5.6.15 Replication中碰到的死锁的情况,这次尝试debug下原因。

debug的过程

用参数–gdb启动mysql,按照步骤重现bug(让slave “show slave status”时卡住)。然后用gdb attach到slave mysql实例上。

(gdb) thread apply all bt

输出所有线程的backtrace,找到show slave status卡住的线程和位置

Thread 2 (Thread 0x7f583c166700 (LWP 2440)):
#0  0x00007f583f484054 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f583f47f3be in _L_lock_995 () from /lib64/libpthread.so.0
#2  0x00007f583f47f326 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000aa3cde in safe_mutex_lock (mp=0x3516ae8, try_lock=0 '\000', file=0xfb8e58 "/home/vagrant/mysql-5.6.12/sql/rpl_slave.cc", line=2611) at /home/vagrant/mysql-5.6.12/mysys/thr_mutex.c:152
#4  0x0000000000a4b993 in inline_mysql_mutex_lock (that=0x3516ae8, src_file=0xfb8e58 "/home/vagrant/mysql-5.6.12/sql/rpl_slave.cc", src_line=2611) at /home/vagrant/mysql-5.6.12/include/mysql/psi/mysql_thread.h:686
#5  0x0000000000a53cb3 in show_slave_status (thd=0x352e3d0, mi=0x34b4f20) at /home/vagrant/mysql-5.6.12/sql/rpl_slave.cc:2611
#6  0x00000000007d45f4 in mysql_execute_command (thd=0x352e3d0) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:2766
#7  0x00000000007ddc46 in mysql_parse (thd=0x352e3d0, rawbuf=0x7f57ec005010 "show slave status", length=17, parser_state=0x7f583c165660) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:6187
#8  0x00000000007d1019 in dispatch_command (command=COM_QUERY, thd=0x352e3d0, packet=0x3534e51 "", packet_length=17) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:1334
#9  0x00000000007d017b in do_command (thd=0x352e3d0) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:1036
#10 0x0000000000797a08 in do_handle_one_connection (thd_arg=0x352e3d0) at /home/vagrant/mysql-5.6.12/sql/sql_connect.cc:977
#11 0x00000000007974e4 in handle_one_connection (arg=0x352e3d0) at /home/vagrant/mysql-5.6.12/sql/sql_connect.cc:893
#12 0x0000000000aea87a in pfs_spawn_thread (arg=0x351b510) at /home/vagrant/mysql-5.6.12/storage/perfschema/pfs.cc:1855
#13 0x00007f583f47d851 in start_thread () from /lib64/libpthread.so.0
#14 0x00007f583e3e890d in clone () from /lib64/libc.so.6

可以看到show slave status卡在

#5  0x0000000000a53cb3 in show_slave_status (thd=0x352e3d0, mi=0x34b4f20) at /home/vagrant/mysql-5.6.12/sql/rpl_slave.cc:2611

查找源码可以看到show slave status卡在获取锁mi->rli->data_lock上
(科普下缩写: mi=master info, rli=relay log info

在gdb中运行命令

(gdb) thread 2
(gdb) f 5
(gdb) print mi->rli->data_lock

切换到thread 2堆栈第5层的上下文,打印出mi->rli->data_lock变量,输出如下

$1 = {m_mutex = {global = {__data = {__lock = 0, __count = 0, __owner = 0, __nusers = 0, __kind = 2, __spins = 0,
    __list = {__prev = 0x0, __next = 0x0}},
  __size = '\000' <repeats 16 times>, "\002", '\000' <repeats 22 times>, __align = 0}, mutex = {__data = {
    __lock = 2, __count = 0, __owner = 2435, __nusers = 1, __kind = 3, __spins = 0, __list = {__prev = 0x0,
      __next = 0x0}},
  __size = "\002\000\000\000\000\000\000\000\203\t\000\000\001\000\000\000\003", '\000' <repeats 22 times>,
  __align = 2}, file = 0xfa4520 "/home/vagrant/mysql-5.6.12/sql/log_event.cc", line = 7259, count = 1,
thread = 140016942216960}, m_psi = 0x0}

看到锁的owner是线程(LWP 2435),为Thread 3

Thread 3的backtrace如下

Thread 3 (Thread 0x7f583c1a7700 (LWP 2435)):
#0  0x00007f583f4817bb in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x0000000000aa429d in safe_cond_timedwait (cond=0x7f57f4000ba8, mp=0x7f57f4000b38, abstime=0x7f583c1a60f0, file=0xedc960 "/home/vagrant/mysql-5.6.12/include/mysql/psi/mysql_thread.h", line=1199) at /home/vagrant/mysql-5.6.12/mysys/thr_mutex.c:278
#2  0x00000000007121f4 in inline_mysql_cond_timedwait (that=0x7f57f4000ba8, mutex=0x7f57f4000b38, abstime=0x7f583c1a60f0, src_file=0xedcb98 "/home/vagrant/mysql-5.6.12/sql/mdl.cc", src_line=1306) at /home/vagrant/mysql-5.6.12/include/mysql/psi/mysql_thread.h:1199
#3  0x0000000000713111 in MDL_wait::timed_wait (this=0x7f57f4000b38, owner=0x7f57f4000a50, abs_timeout=0x7f583c1a60f0, set_status_on_timeout=true, wait_state_name=0x14d0488) at /home/vagrant/mysql-5.6.12/sql/mdl.cc:1306
#4  0x0000000000714811 in MDL_context::acquire_lock (this=0x7f57f4000b38, mdl_request=0x7f583c1a6180, lock_wait_timeout=31536000) at /home/vagrant/mysql-5.6.12/sql/mdl.cc:2241
#5  0x000000000063656a in ha_commit_trans (thd=0x7f57f4000a50, all=true) at /home/vagrant/mysql-5.6.12/sql/handler.cc:1396 (COMMIT LOCK)
#6  0x00000000008a010b in trans_commit (thd=0x7f57f4000a50) at /home/vagrant/mysql-5.6.12/sql/transaction.cc:228
#7  0x0000000000a081bb in Xid_log_event::do_commit (this=0x7f57f4004730, thd=0x7f57f4000a50) at /home/vagrant/mysql-5.6.12/sql/log_event.cc:7174
#8  0x0000000000a0886e in Xid_log_event::do_apply_event (this=0x7f57f4004730, rli=0x3516650) at /home/vagrant/mysql-5.6.12/sql/log_event.cc:7310 (rli->data_lock)
#9  0x00000000009fd956 in Log_event::apply_event (this=0x7f57f4004730, rli=0x3516650) at /home/vagrant/mysql-5.6.12/sql/log_event.cc:3049
#10 0x0000000000a55e31 in apply_event_and_update_pos (ptr_ev=0x7f583c1a68a0, thd=0x7f57f4000a50, rli=0x3516650) at /home/vagrant/mysql-5.6.12/sql/rpl_slave.cc:3374
#11 0x0000000000a56e45 in exec_relay_log_event (thd=0x7f57f4000a50, rli=0x3516650) at /home/vagrant/mysql-5.6.12/sql/rpl_slave.cc:3742
#12 0x0000000000a5c334 in handle_slave_sql (arg=0x34b4f20) at /home/vagrant/mysql-5.6.12/sql/rpl_slave.cc:5552
#13 0x0000000000aea87a in pfs_spawn_thread (arg=0x350a800) at /home/vagrant/mysql-5.6.12/storage/perfschema/pfs.cc:1855
#14 0x00007f583f47d851 in start_thread () from /lib64/libpthread.so.0
#15 0x00007f583e3e890d in clone () from /lib64/libc.so.6

可以看到Thread 3卡在commit lock上,同时查源码看到Thread 3同时占有了rli->data_lock (log_event.cc:7259)

锁的状态

按照bug的描述,

  1. flush tables with read lock; 会持有commit lock
  2. IO thread (Thread 3)会持有rli->data_lock,并等待commit lock
  3. show slave status; 会等待rli->data_lock

结果导致show slave status卡住不可用

臆测一下解决方法

鉴于功底不深,只能臆测一下

  1. IO thread持有锁rli->data_lock的原因是要更新relay log的状态,然后进行commit(Xid_log_event::do_apply_event (log_event.cc:7248))。在commit的时候不会更新rli的数据。
  2. show slave status不会更新rli的数据,需要锁rli->data_lock的原因是要一致性数据。

因此可能的解决方案是IO thread持有读写锁,进行commit时转为持有读锁。show slave status只使用读锁。

只是臆测下解决方法,待bug #70307修掉时再学习。

Mysql 5.6.12 Master上flush Logs在slave上产生两个relay-log

现象

一个碰巧观察到的有趣的现象:mysql 5.6.12 在master上flush logs,在slave上会观察到两个新的relay-log file

举例:

slave-relay-bin.000092

 FD event
 Rotate to mysql-bin.000056
 Rotate to slave-relay-bin.000093

slave-relay-bin.000093

 FD event slave
 Rotate to mysql-bin.000056
 FD event master
 bla bla…

可以看到000092这个relay log相当多余。这个现象并不会影响replication的正确性,只是让有强迫症的人有点狂躁

探索

在master上net_serv.cc:my_net_write打断点,可以观察到master的确发出了以下三个事件

  • ROTATE_EVENT

backtrace

#0  my_net_write (net=0x1ea2858, packet=0x7fffa4002b70 "", len=48)
    at /home/vagrant/mysql-5.6.12/sql/net_serv.cc:284
#1  0x0000000000a48b05 in mysql_binlog_send (thd=0x1ea2600, log_ident=0x7fffa4004c60 "mysql-bin.000052", pos=167,
    slave_gtid_executed=0x0) at /home/vagrant/mysql-5.6.12/sql/rpl_master.cc:1336
#2  0x0000000000a46ad2 in com_binlog_dump (thd=0x1ea2600, packet=0x1ea5d21 "", packet_length=26)
    at /home/vagrant/mysql-5.6.12/sql/rpl_master.cc:746
#3  0x00000000007d1ab9 in dispatch_command (command=COM_BINLOG_DUMP, thd=0x1ea2600, packet=0x1ea5d21 "",
    packet_length=26) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:1534
#4  0x00000000007d017b in do_command (thd=0x1ea2600) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:1036
#5  0x0000000000797a08 in do_handle_one_connection (thd_arg=0x1ea2600)
    at /home/vagrant/mysql-5.6.12/sql/sql_connect.cc:977
#6  0x00000000007974e4 in handle_one_connection (arg=0x1ea2600)
    at /home/vagrant/mysql-5.6.12/sql/sql_connect.cc:893
#7  0x0000000000aea87a in pfs_spawn_thread (arg=0x1e7aa80)
    at /home/vagrant/mysql-5.6.12/storage/perfschema/pfs.cc:1855
#8  0x00007ffff7bc7851 in start_thread () from /lib64/libpthread.so.0
#9  0x00007ffff6b3290d in clone () from /lib64/libc.so.6
  • 第二个ROTATE_EVENT

backtrace

#0  my_net_write (net=0x1ea2858, packet=0x7fffa4002ab0 "", len=48)
    at /home/vagrant/mysql-5.6.12/sql/net_serv.cc:284
#1  0x0000000000a45f04 in fake_rotate_event (net=0x1ea2858, packet=0x1ea2be8,
    log_file_name=0x7fffc94ff270 "./mysql-bin.000056", position=4, errmsg=0x7fffc94ffdb0,
    checksum_alg_arg=1 '\001') at /home/vagrant/mysql-5.6.12/sql/rpl_master.cc:395
#2  0x0000000000a4a33d in mysql_binlog_send (thd=0x1ea2600, log_ident=0x7fffa4004c60 "mysql-bin.000052", pos=167,
    slave_gtid_executed=0x0) at /home/vagrant/mysql-5.6.12/sql/rpl_master.cc:1728
#3  0x0000000000a46ad2 in com_binlog_dump (thd=0x1ea2600, packet=0x1ea5d21 "", packet_length=26)
    at /home/vagrant/mysql-5.6.12/sql/rpl_master.cc:746
#4  0x00000000007d1ab9 in dispatch_command (command=COM_BINLOG_DUMP, thd=0x1ea2600, packet=0x1ea5d21 "",
    packet_length=26) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:1534
#5  0x00000000007d017b in do_command (thd=0x1ea2600) at /home/vagrant/mysql-5.6.12/sql/sql_parse.cc:1036
#6  0x0000000000797a08 in do_handle_one_connection (thd_arg=0x1ea2600)
    at /home/vagrant/mysql-5.6.12/sql/sql_connect.cc:977
#7  0x00000000007974e4 in handle_one_connection (arg=0x1ea2600)
    at /home/vagrant/mysql-5.6.12/sql/sql_connect.cc:893
#8  0x0000000000aea87a in pfs_spawn_thread (arg=0x1e7aa80)
    at /home/vagrant/mysql-5.6.12/storage/perfschema/pfs.cc:1855
#9  0x00007ffff7bc7851 in start_thread () from /lib64/libpthread.so.0
#10 0x00007ffff6b3290d in clone () from /lib64/libc.so.6
  • FORMAT_DESCRIPTION_EVENT

可以看到第一个ROTATE_EVENT是由flush logs发出的,第二个ROTATE_EVENT是fake_rotate_event

关于fake_rotate_event

以前也吐槽过fake_rotate_event

master在binlog切换时(不一定是手工flush,也可能是重启,或者容量达到限制)一定要多发一个rotate event,原因如源码rpl_master.cc:mysql_binlog_send中的注释

  /*
    Call fake_rotate_event() in case the previous log (the one which
    we have just finished reading) did not contain a Rotate event.
    There are at least two cases when this can happen:

    - The previous binary log was the last one before the master was
      shutdown and restarted.

    - The previous binary log was GTID-free (did not contain a
      Previous_gtids_log_event) and the slave is connecting using
      the GTID protocol.

    This way we tell the slave about the new log's name and
    position.  If the binlog is 5.0 or later, the next event we
    are going to read and send is Format_description_log_event.
  */
  if ((file=open_binlog_file(&log, log_file_name, &errmsg)) < 0 ||
      fake_rotate_event(net, packet, log_file_name, BIN_LOG_HEADER_SIZE,
                        &errmsg, current_checksum_alg))

主要是解决之前没有rotate event发送的场景

虽然非常想吐槽,但是我也想不出更好的办法

Mysql rpl_slave.cc:handle_slave_io 源码的一些个人分析

读了rpl_slave.cc:handle_slave_io的源码(Mysql 5.6.11),总结一下

函数概述

handle_slave_io是slave io_thread的主函数,函数逻辑入口为rpl_slave.cc:start_slave_threads

主体结构

源码的主体结构
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
handle_slave_io(master_info) {
     3955 bla bla
     4016 fire HOOK binlog_relay_io.thread_start
     4032 master建立连接
    (4047 设置max_packet_size)
     4073 get_master_version_and_clock,
          master上:
          通过SELECT UNIX_TIMESTAMP()获取server timestamp
          通过SHOW VARIABLES LIKE 'SERVER_ID'获取server id
          SET @master_heartbeat_period= ?
          SET @master_binlog_checksum= @@global.binlog_checksum
          SELECT @master_binlog_checksum获取master binlog checksum
          SELECT @@GLOBAL.GTID_MODE
     4075 get_master_uuid
          master上“SHOW VARIABLES LIKE 'SERVER_UUID'”
     4077 io_thread_init_commands
          master上“SET @slave_uuid= '%s'”
     4106 register_slave_on_master
          master发送COM_REGISTER_SLAVE
     4133 while (!io_slave_killed(thd,mi))
     4134 {
     4136      request_dump
               master发送COM_BINLOG_DUMP_GTID/COM_BINLOG_DUMP
     4159      while (!io_slave_killed(thd,mi))
     4160      {
     4169           read_event,此为阻塞方法,会阻塞等待有新数据包传入
     4184          {
                         一些包错误的处理,包括packet too large / out of resource
     4213          }
     4219          fire HOOK binlog_relay_io.after_read_event
     4232          queue_event,将event放入relay logbuf
     4240          fire HOOK binlog_relay_io.after_queue_event
     4250          flush_master_info,将master_inforelay log刷到disk
                   此处,先刷relay log,后刷master_info。这样意外的故障可以通过重连恢复机制来恢复。
                   若先刷master_info,后刷relay log,意外故障时master_info已经更新,比如(0-100, 100-200),而数据丢失,仅有(0-100),恢复的replication会从200开始。整个relay log会成为(0-100, 200-),中间数据会丢失。

     4286          relay log达到容量限制,则wait_for_relay_log_space
     4292      }
     4293 }
     4296 之后都是收尾操作
}

一些重点

  1. 此处不分析锁什么的,因为看不懂
  2. 4047 设置max_packet_size的目的不明
  3. 4073 开始slave会向master直接发送一些sql,然后解析返回。而不是包装在某个包的某个字段里,用一些预定义的变量来传递结果。
    这种设计一下就觉得山寨起来。
    后经同事 @神仙 指点,mysql这样做貌似是为了兼容性,免得数据包格式被改来改去。
    (看到mysql里大量的兼容代码都拿来处理包结构的问题,最极品的可能是莫过于LOG_EVENT_MINIMAL_HEADER_LEN了)
    在对流量影响不大的情况下,直接用sql反复查询的确是个好的解决手法
  4. 4250 将master_info和relay log刷到disk上。
    先刷relay log,后刷master_info。这样意外的故障可以通过relay log恢复机制来恢复。
    若先刷master_info,后刷relay log,意外故障时master_info已经更新,比如(0-100, 100-200),而数据(100-200)丢失,仅有(0-100),恢复的replication会从200开始。整个relay log会成为(0-100, 200-),中间数据会丢失。

start slave时slave向master发送的事件

  • SELECT UNIX_TIMESTAMP() (rpl_slave.cc:get_master_version_and_clock)

  • SHOW VARIABLES LIKE ‘SERVER_ID’ (rpl_slave.cc:get_master_version_and_clock)
  • SET @master_heartbeat_period=? (rpl_slave.cc:get_master_version_and_clock)
  • SET @master_binlog_checksum= @@global.binlog_checksum (rpl_slave.cc:get_master_version_and_clock)
  • SELECT @master_binlog_checksum (rpl_slave.cc:get_master_version_and_clock)
  • SELECT @@GLOBAL.GTID_MODE (rpl_slave.cc:get_master_version_and_clock)
  • SHOW VARIABLES LIKE ‘SERVER_UUID’ (rpl_slave.cc:get_master_uuid)

  • SET @slave_uuid= ‘%s’(rpl_slave.cc:io_thread_init_commands)

  • COM_REGISTER_SLAVE(rpl_slave.cc:register_slave_on_master)
  • COM_BINLOG_DUMP(rpl_slave.cc:request_dump)

master与slave的时间差

可以看到slave获得master的时间方法就是直接下sql,完全忽略网络延迟等等等等,属于不精准的时间

这篇文章从源码级别分析了Seconds_Behind_Master的来源,也给出了备库延迟跳跃的原因。总的来说就是Seconds_Behind_Master不可信。

Mysql rpl_master.cc:mysql_binlog_send 源码的一些个人分析和吐槽

读了两天rpl_master.cc:mysql_binlog_send的源码(Mysql 5.6.11),总结一下

函数的入口是rpl_master.cc:com_binlog_dump,当slave向master请求数据时,在master上调用

函数参数说明:
log_ident为slave请求的binlog文件名,如”mysql-bin.000001”
pos为slave请求的binlog位置
slave_gtid_executed为gtid相关,在此忽略

在此吐槽:

  1. 这个函数将近1k行,且缩进混乱,代码折叠困难。最后附的我的笔记中,有整理好的源码下载
  2. 这个函数有两大段近百行的重复代码(1179 & 1553)

源码的主体结构

源码的主体结构
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
42
43
44
45
46
47
48
49
50
51
52
53
mysql_binlog_send()
{
     0814  bla bla
     1011 fake_rotate_event
     1028 max_alloed_packet= MAX_MAX_ALLOWED_PACKET
     1038 if (请求的POS不是从binlog开头开始)
     1039 {
               binlog开头中找到一个FD event(FORMAT_DESCRIPTION_EVENT), 并发送给slave
     1123 }
     1124 else
     1125 {
               FD event可以从正常的replication中传送给slave,此处不做操作
     1127 }
     1132 while (netthe都在运转)
     1133 {
     1143      while (binlog中读取一个event)
     1144      {
     1178           switch (event_type)
     1179           {
                         分类型处理event
     1281           }
     1283           event需跳转到下一个binlog(goto_next_binlog), break
     1291           fire HOOK before_send_event
     1300           记录skip_group
     1306           {
                         send last skip group heartbeat?
     1326           }
     1331           slave发送event
     1348           {
                         处理LOAD_EVENT
     1356           }
     1358           fire HOOK after_send_event
     1369      }
     1391      if (!goto_next_binlog)
     1392      {
                   发送完所有binlog,未发生binlog切换时
     1437          加锁尝试再读取一个event(此时其他进程不能更新binlog),目的是试探之前处理过程中master上是否有更多的binlog写入,若有,则跳转1553处理read_packet
     1451          若没有更多的binlog
                   {
                        等待更多的binlog写入,等待时发送心跳
     1545          }
     1553          处理read_packet
                   {
                        分类型处理event
     1682          }
     1683      }
     1685      if (goto_next_binlog)
               {
                    切换到下一个binlog
               }
     1733 }
     1735 之后是收尾处理
}

重点步骤

  1. 补发Format Description event。
    如果传送从binlog开头开始,那么FD event会正常随着binlog传送;
    若传送不从binlog开头开始,则需要补发一个FD event,才开始传送
  2. 如何判断binlog读取完
    函数先不加锁读取binlog中的event,读完后,再加锁尝试读取一个event(加锁过程中,没有其他进程写进binlog),若有数据,则继续处理,若没有数据,则说明binlog读取完了,master会阻塞等待新的binlog写入。
    这样做主要为了:
    1. 不需要一直加锁读取binlog,保障性能;
    2. 无锁读取时会有其他进程写binlog,加锁可以保障这些新加的binlog得到妥善安置
  3. 心跳
    仅在不传送binlog时(master穷尽了binlog,开始阻塞等待新的binlog写入时)才进行心跳
  4. Fake Rotate Event
    Fake Rotate Event在开始传送和切换binlog时发送到slave。主要作用是通知slave binlog filename,原因在源码comment里写的很清楚。但是很疑惑的是为什么在FD event里并没有binlog filename,这个问题发到了StackoverFlow,未有答案。(诶,看看我的stackoverflow的记录就知道,我的问题都是死题)

TODO

有一些东西还是没弄懂,得慢慢读懂其他机制才可以,比如

  1. max_alloed_packet是如何作用的
  2. send last skip group heartbeat的作用
  3. 不同类型的event的具体处理,需要和slave端结合在一起

我的笔记

我的笔记在此

MHA Failover时做了些啥

读了MHA failover部分的源码整理一下

代码位置注解的格式为file_name@function_name

设定场景

假设集群里有四个mysql实例分别是old_master和slave 0,1,2 slave 0,1,2 执行failover中的角色分别为 Latest slave, Oldest slave 和 New master

如图

Failover的步骤代码

(MasterFailover@do_master_failover)

1 check_settings

2 force_shutdown

3.1 check_set_latest_slaves

3.2 save_master_binlog

3.3 Determining New Master Phase

3.3.1 find_latest_base_slave

3.3.2 select_new_master

3.3.3 recover_master

3.3.4 $new_master->{activated} = 1;

4 Slaves Recovery Phase

4.1 Starting Parallel Slave Diff Log Generation Phase.

4.2 Starting Parallel Slave Log Apply Phase.

5 cleanup

Failover的一些步骤说明

2 force_shutdown

2.1  对所有slave, stop io thread.(MasterFailover@force_shutdown) 这里仅stop io thread, 而不是stop slave, 尽可能让sql thread运行。sql thread是在recover必要时(要生成差异数据时)才停下。 

2.2  在Old master上回收动态ip, shutdown(并可选用power manager对Old master进行关机). (MasterFailover@force_shutdown_internal)

3.1 check_set_latest_slaves, 确定latest_slave和oldest_slaves. latest_slave指的是slave中relay log最超前的slave, 相反oldest_slave指的是relay log最落后的slave

3.2 save_master_binlog, 保存Latest slave到Old master的binlog差异数据, 如图中蓝色部分. 由于master可能是硬件故障等, 不一定能响应save_master_binlog, 所以蓝色部分不一定能保存下来. 若失败, 之后的步骤中用到蓝色部分的地方都可以忽略, 最终结果也是会有部分数据的丢失

3.3.1 find_latest_base_slave, 这里最理想的状况是能从所有的latest slave中能找到一个relay log可以用于补齐oldest slave的 (如果oldest slave可以被补齐, 那其他的slave都可以被补齐). 如场景图中所示,Slave 0可以作为latest_base_slave.

另一种情况是不能用于补齐oldest slave, 比如下图的状况, 集群里最全最新的relay log也无法和oldest slave对接上. 这种情况的处理跟ignore_fail配置有关, 若所有努力都失败只好failover失败

关于ignore_fail的处理逻辑,可参看源码或者文后我附的源码笔记

3.3.2 select_new_master, 选举new master.

选举的策略是尽量在candidate slave列表中,尽量在latest slave列表中,不可以在bad列表中. 注意,new master可以不是latest slave, 场景图中列举的是这种情况(尽管应该比较少见)

3.3.3 recover_master. 将差异数据补齐到new master, 让new master成为集群里数据最全最新的节点.

生成old_master上exec relay_log到read relay_log的差异数据(原因是sql thread落后于io thread), 源码中中称为diff_from_exec_to_read, 如图中绿色部分.

生成old_master到latest_base_slave的差异数据(MasterFailover@recover_relay_logs), 如图中黄色部分.

重放: 在old_master上重放绿色部分, 然后重放黄色部分(MasterFailover@send_binlog & MasterFailover@recover_slave), 最后重放蓝色部分(MasterFailover@apply_diff)

4 对所有的slave, 这里是以old_slave为例, 与recover_master类似:生成diff_from_exec_to_read(黑色部分), 生成与latest_base_slave的差异数据(红色部分). 然后重放黑色部分,红色部分和蓝色部分. (MasterFailover@recover_slaves)

最后让所有slave连到新的master上

一点说明

  1. 只是简单记录了failover的基本操作, 仅在2.1里说明了stop io thread. 代码中其他操作如change master的操作可以直接翻代码翻到, 不穷举
  2. 我阅读代码的原始笔记放在evernote公开页面, 里面有一些细节, 不再整理

Jruby Backtick + Jre 6 会卡住

最近在jruby 1.7.5 + jre 6上碰到的土亢

现象

用backtick调用命令,比如

用backtick调用命令
1
`./some_script`

在调用命令之前/同时在terminal输入一些回车,有一定概率backtick的调用会卡住不返回。 此时再输入一个回车,调用会继续执行并返回。

解决

一切靠猜

jruby有个bug:Gaps in STDIN pipe stream if backtick is used

Charles Oliver Nutter在comment中写到”For JRuby 1.7pre1 on Java 7, this should be fixed; TTY should be handled correctly. For other Java versions, we can’t fix this.”,于是最方便的就是升级jre到7

经验证升级jre可以从土亢中爬出来。 如果难以升级jre,参看这里,这个兄弟做了很全的测试。可以用IO.popen或者Open3.popen3替换backtick。

经验

jruby有坑,同时也提供了便捷的手段将现有的java项目改成比较爽的样子。这些坑是难以预料的,做好准备,然后一如既往踩过去。

Mysql 5.6.15 Replication中碰到的死锁

简述下今天在mysql 5.6.15上碰到的土亢

现象

mysql开启主从复制时,用meb(MySQL Enterprise Backup)做备份会卡住。同时在slave上show slave status也会卡住。

查看slave上show processlist,可以看到sql thread的状态为 “Waiting for commit lock”

猜测

无论是”SHOW ENGINE INNODB STATUS”还是”SHOW OPEN TABLES”都没有提供有用的信息,还是一切靠猜

夜观天象猜到mysql存在bug “Another deadlock on FLUSH TABLES WITH READ LOCK + SHOW SLAVE STATUS”

其中描述了sql thread开始执行了transaction,但是没有commit的间隙,在slave上FLUSH TABLES WITH READ LOCK,会出现死锁

于是猜测,如果meb恰好在slave上某个transaction commit之前做了FLUSH TABLES WITH READ LOCK,然后调用了与”SHOW SLAVE STATUS”类似的机制获取slave info,那么就会如bug所述卡住。然后mysql由于TABLE LOCk的存在,sql thread也就会卡住。

BTW:搜一下mysql bug库,会有一些描述类似的bug,其中70307描述最靠谱,且有详细的重现步骤,我也成功在mysql 5.6.15上重现了bug。

结果

实验后证明猜对了…

Jruby重写java项目的一些总结

久不更新了,6月换了工作

最近将一个小项目从java迁移到了jruby,在此总结一下

从结果开始

1.代码量上的比较

* 纯Java项目 Jruby项目
主代码行数 4073 2689 - 454 |
测试代码行数 1707 1485 - 319 |

其中Jruby项目中有454行主代码和319行测试代码为新加功能

结论是在迁移了所有功能后,主代码量减少了45%+,测试代码比例从41%增加到52%,测试case数也增加

2.DSL

在迁移过程中,加入了一些DSL,让代码变得更可读,类似于

1
2
3
4
5
6
7
8
9
unless can_load local_node.config.from_file
  load local_node.config, :sip_ip
  cluster.join local_node.config.sip_ip
  cluster.lock "lock_config" do
      load_remote_global_config
      load local_node.config, with(@remote_global_config)
      dump local_node.config
  end
else

如果了所有符号,就变成稍微(!)可读的一篇描述

3.部署

jruby可以被编译成class,打成jar,跑在一切有jvm和jruby jar的地方。与现有java项目的融合不成问题,此次也是迁移了整个项目中的一部分,其他部分保留java

一些细节

1.代码量的减少

抛弃脚本语言的优势论不谈,实践中,代码量的明显减少来自于以下几个方面:

1.1.调用命令行更方便,在纯java中调用命令行比较烦,即使封装半天也很不爽。代码量差别不是很大,主要是不爽

1.2.闭包。java中传递闭包得靠实现匿名接口,冗长麻烦,影响函数的复用。(要不定义多个函数,要不到处new interface)

1.3.mixin。奇怪的是我混入的往往不是特性,而是辅助函数… 尽管这种方法不正规,但对于小类来说非常实用。比较以下两段代码

1
2
3
4
5
6
class A
  def aaaa
      XxxUtils.blabla ...
      YyyHelper.blublu ...
  end
end
1
2
3
4
5
6
7
class A
  include XxxUtils, YyyHelper
  def aaaa
      blabla ...
      blublu ...
  end
end

如果你跟我一样厌恶到处都是Utils、Helper,又比较烦static import…

“mixin太多很容易命名冲突引起问题”,不得不承认这个担心是对的,我爽我的,谁爱操心谁操心吧。顺便提一句,名空间冲突解决最好的方法还数node.js的。

1.4.标准库。不得不承认,ruby标准库的人性化做的非常好,链调用能节省很多无用的代码

1.5.rspec mock。以前用java尽量避免用mock framework,都是用继承注入,太费代码以至于自己都觉得烦(尽管有IDEA的自动代码折叠,还是觉得烦),到处都是注入点的代码也很乱。 这是一个很难解决的平衡,如果每一段代码都是上下文封闭的,那么代码很容易测试,但处理输入输出需要大量工作,如果不是,那测试就需要mock。 尝试了rspec mock后(我相信任何mock framework都一样),觉得还不错,目前还没有失控的主要原因是每次mock不超过两层。

最后,”代码行数不是XXX标准”,是的,我只是减少了无用代码元素在项目中的分布

2.DSL/HSL(Human-specific language)

关于DSL的尝试还很初级,主要目的是读起来通顺,拘泥于以下几种形式:

2.1.函数名alias。每次写代码的时候是不是纠结于用[].exist、[].exists,或者[].exists?。实践中都是先流畅的(!)写函数梗概,不纠结调用的函数是不是存在,爱怎么用怎么用吧,不存在就alias一下。一切以写作顺畅为目的。

2.2.mixin。如前面提到的,mixin提供了忽略类名的偏门,可以写出一句流畅的人话,比如cp file,而不是FileUtils.cp file

2.3.动词函数。比较array.collect和collect array,我喜欢后面那款。动词函数,让”宾.动”的OO非人话,转换成”动宾”的人话。当然除非你是古文爱好者

2.4.”介词”空函数。就是些输出=输入的空函数,比如之前例中”unless can_load local_node.config.from_file”的can_load就是空函数,较之”unless local_node.config.load_from_file?”更有人味

2.5.最后,以上几种形式都没有Domain-specific,而是Human-specific。关于DSL的尝试还没有深入到domain的阶段,先从让程序说人话开始

2.6.难以否认的部分:像所有的城市规划一样,整洁的背后都会藏着付出代价的区域,DSL的背后也会有支持代码,初次读支持代码会发现他们怪异、畸形、目的非常不明确,配合用例才能读懂。 如何更好的管理这部分付出牺牲的代码值得讨论

3.部署和测试

简单描述一下当前部署方案中的要点

3.1.跑测试时不用编译,直接跑rb脚本。部署时才编译。可以节省测试时间。

3.2.程序运行是用java -cp …/jruby.jar com.xxx.XXX。测试运行是用java -cp …/jruby.jar org.jruby.Main -S blabla

3.3.GEM_HOME 和 GEM_PATH 指向特定folder,用上面的命令安装gem即可将gem安装到指向的folder

3.4.编译用jrubyc,打jar包的脚本需要自己写

PS: 写个简单的watchr脚本,可以让主文件和相应的测试文件保存时,自动跑相应的测试,非常省事

4.一些缺陷

不得不承认的缺陷还有很多

4.1.不是所有项目能平滑接入jruby,之前的确碰到过jruby和EclipseLink的冲突,与boot classpath相关,具体原因不祥。建议迁移前先搭原型进行测试

4.2.(这条来自于实践经验)HSL(Human-specific language) 不是能全面实现的,只能在程序里的一部分实现,而且随着代码量的增加,支持代码的维护估计会显得吃力。(但是写起来的确很爽)

4.3.改进后的代码也不是完全可读,难以忽略一些语言元素,也没法忽略业务背景

4.4.写单元测试吧,懒不是个办法…

有意思的javascript笔误

前两天被拉去查一个很怪的错,描述是“一个js文件压缩前和压缩后执行结果不一样”

查了很久锁定以下代码

1
   s = s + +("...")

一看就是笔误了,压缩后为

1
   s=s++("...")

空格被压缩后显然会抛语法错。但没压缩能正常运行就有点意思了,做了以下尝试

1
2
3
4
5
6
7
8
+('...')
> NaN
-('...')
> NaN
'1' + +('...')
> "1NaN"
+(NaN)
> NaN

那个多出来的加号,被解释成取数字正值,就像减号在数字前是取数字的负值一样。

Java Off-heap的一些参考

读了Hazelcast的文档,很有意思的部分是”Elastic Memory”,为了减少GC,用到了java off-heap(off-heap允许Java直接操作内存空间, 类似于C的malloc和free)。之前孤陋寡闻,记录一些off-heap的参考。

1.做了以下对比试验,来对比Heap和Non-heap

Heap
1
2
3
4
5
6
7
public class HeapAllocation {
    public static void main(String[] args) {
        while (true) {
            Integer[] a = new Integer[1000000];
        }
    }
}
Off-heap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.reflect.Field;

public class OffHeapAllocation {

    private static Unsafe unsafe;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe)field.get(null);
        } catch(Exception e) {
        }
    }

    public static void main(String[] args) {
        while (true) {
            long addr = unsafe.allocateMemory(8 * 1000000);
            unsafe.freeMemory(addr);
        }
    }
}

Heap GC的测试结果:

Off-heap GC的测试结果:

尽管这种测试没啥意义,只能给个直观感受,还是可以看到Heap GC Pause Time还是很多的。

2.这篇文章 对off-heap的性能做了全面的对比。

结论是heap access要快于off-heap,但off-heap在躲开GC pause和开大内存的时候明显优秀。

有趣的是在评论一楼Peter Lawrey指出JIT会影响这个测试,于是作者重做测试以证明JIT不影响结论。

3.这篇文章 讨论了如何让Java避开GC并提供了memory的测试类GCUtils。

4.在这里 Peter Lawrey谈到了如何测量一个Java对象的大小和TLAB对测量的影响。仅供参考。

实验:Mysql Master-slave-standby将source从master切换到standby

尝试了<Mysql High Availability>第四章热备份一节的实验,记录步骤.

先统一原语,master/slave/standby表示三台机器名,source/target代表replication关系的两端(不适用master/slave用以和机器名区分).”master(3)”表示master机器的db里有三条数据1,2,3.

实验开始.

  1. 初始状态是存在master->slave, master->standby的replication
  2. standby在切换成source时,需要有bin-log和replication user. 在此重新设置master->standby的replication, 让standby满足要求.

忽略replication user的部分.

bin-log的部分在my.cnf里要设置log-bin和log-slave-updates(默认情况下,master->standby的replication不会写standby的bin-log,需开始standby的log-slave-updates才会写).

1
2
3
4
5
6
server-id               = 3
log_bin                 = /var/log/mysql/mysql-bin.log
...
relay-log-index         = /var/log/mysql/slave-relay-bin.index
relay-log               = /var/log/mysql/slave-relay-bin
log-slave-updates
  1. 测试一下standby binlog设置成功。可以在master插入一条数据,在standby查看
1
2
3
4
5
6
7
8
9
standby> show binlog events;
+------------------+------+-------------+-----------+-------------+------------------------------------------------------------------------+
| Log_name         | Pos  | Event_type  | Server_id | End_log_pos | Info                                                                   |
+------------------+------+-------------+-----------+-------------+------------------------------------------------------------------------+
| mysql-bin.000001 |    4 | Format_desc |         3 |         107 | Server ver: 5.5.31-0ubuntu0.12.04.1-log, Binlog ver: 4                 |
| mysql-bin.000001 |  107 | Query       |         1 |         166 | BEGIN                                                                  |
| mysql-bin.000001 |  166 | Query       |         1 |         257 | use `tac`; insert into test values(8889)                               |
| mysql-bin.000001 |  257 | Xid         |         1 |         284 | COMMIT /* xid=111 */    
...
  1. 将replication调整至状态master(3),standby(2),slave(1). 人工造成各db的状态不一致
1
2
3
4
5
master> insert into test values(1);
slave> stop slave;
master> insert into test values(2);
standby> stop slave;
master> insert into test values(3);
  1. 想象此时master挂掉,开始将source从master切换成standby

  2. 在建立standby->slave的replication之前,需要将standby和slave数据同步(此时slave落后于standby)。

1
2
3
4
5
6
7
8
9
10
-- 先查看standby从master拿了多少数据
standby> show slave status \G
*************************** 1. row ***************************
...
        Master_Log_File: master-bin.000023
...
        Exec_Master_Log_Pos: 1391
        
-- 让slave从master上同步到跟standby同样的位置
slave> start slave until master_log_file = 'master-bin.000023', master_log_pos = 1391;

有意思的是此处用了master(其实我们假设master已经坏了…)。

  1. 此时可以讲slave的source从master切换到standby. 一个问题就是standby->slave的开始位置可能是和master->slave不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 查看standby binlog的当前位置
mysql> show master status \G
*************************** 1. row ***************************
    File: mysql-bin.000001 
    Position: 796
    Binlog_Do_DB:
  Binlog_Ignore_DB:
-- 注意与master上的文件名和位置都不同

-- 切换slave的source
slave> change master to 
            master_host = '192.168.50.4', 
            master_port = 3306, 
            master_user = 'repl', 
            master_password = 'repl', 
            master_log_file = 'mysql-bin.000001', 
            master_log_pos = 796;
  1. 测试一下standby->slave replication.

总的思路就是讲master(3),standby(2),slave(1)同步成master(3),standby(2),slave(2),然后将master->slave切换成standby->slave.

遗留了两个问题,其一是slave和standby同步时使用了”坏掉”的master;其二是master超前了standby和slave, 也就是standby->slave丢失了master的超前数据。留待慢慢学习。

关于Mysql Binlog的一点学习

差不多一个月没更新了。除了忙些琐事,就是偷点懒。

在读<Mysql High Availability>,扫了一遍,读第二遍的时候开始做些实验,所以这之后的blog写的也会没什么章法。

<Mysql High Availability>第三章介绍binlog时特地提到了Rand()/Now()/User variable/Password()在基于sql复制时的行为,简单做些实验。

  1. Rand()

Rand() 在replication中,值会被正确传递。如下查看binlog,发现pos 209处rand_seed会被传给slave,保证rand生成的值保持一致。

1
2
3
4
5
6
7
8
9
10
mysql> show binlog events in 'master-bin.000007';
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
| Log_name          | Pos | Event_type  | Server_id | End_log_pos | Info                                                   |
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
| master-bin.000007 |   4 | Format_desc |         1 |         107 | Server ver: 5.5.31-0ubuntu0.12.04.1-log, Binlog ver: 4 |
| master-bin.000007 | 107 | Query       |         1 |         174 | BEGIN                                                  |
| master-bin.000007 | 174 | RAND        |         1 |         209 | rand_seed1=598597315,rand_seed2=24268577               |
| master-bin.000007 | 209 | Query       |         1 |         302 | use `tac`; insert into test values(rand())             |
| master-bin.000007 | 302 | Xid         |         1 |         329 | COMMIT /* xid=151 */                                   |
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
  1. Now()

Now() 在replication中,值会被正确传递。如下查看binlog,pos 283处,貌似这个语句传给slave,会由于master和slave的时间不同步,导致问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
master> flush logs;
     master> SET TIMESTAMP=unix_timestamp('2010-10-01 12:00:00');
     master> insert into test values(now());
     master> show binlog events in 'master-bin.000007';

+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
| Log_name          | Pos | Event_type  | Server_id | End_log_pos | Info                                                   |
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
| master-bin.000012 |   4 | Format_desc |         1 |         107 | Server ver: 5.5.31-0ubuntu0.12.04.1-log, Binlog ver: 4 |
| master-bin.000012 | 107 | Query       |         1 |         182 | BEGIN                                                  |
| master-bin.000012 | 182 | Query       |         1 |         283 | use `tac`; insert into test values (now())             |
| master-bin.000012 | 283 | Xid         |         1 |         310 | COMMIT /* xid=131 */                                   |
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+

但通过mysqladmin查看binlog,可以看到binlog中会不断插入TIMESTAMP来保证now()函数的执行结果在master和slave是相同的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
master> sudo mysqlbinlog --short-form master-bin.000017
... 
DELIMITER /*!*/;
SET TIMESTAMP=1285934400/*!*/;
...
BEGIN
/*!*/;
use `tac`/*!*/;
SET TIMESTAMP=1285934400/*!*/;
insert into test values(now())
/*!*/;
COMMIT/*!*/;
SET TIMESTAMP=1368372377/*!*/;
BEGIN
/*!*/;
SET TIMESTAMP=1368372377/*!*/;
insert into test values(now())
/*!*/;
COMMIT/*!*/;
...
  1. User variable

User variable会被编码成十六进制串,含义不明,保密性不明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> flush logs;
Query OK, 0 rows affected (0.02 sec)

mysql> set @foo = now();
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test values (@foo);
Query OK, 1 row affected (0.01 sec)

mysql> show binlog events in 'master-bin.000014';
+-------------------+-----+-------------+-----------+-------------+-----------------------------------------------------------------------------------+
| Log_name          | Pos | Event_type  | Server_id | End_log_pos | Info                                                                              |
+-------------------+-----+-------------+-----------+-------------+-----------------------------------------------------------------------------------+
| master-bin.000014 |   4 | Format_desc |         1 |         107 | Server ver: 5.5.31-0ubuntu0.12.04.1-log, Binlog ver: 4                            |
| master-bin.000014 | 107 | Query       |         1 |         174 | BEGIN                                                                             |
| master-bin.000014 | 174 | User var    |         1 |         229 | @`foo`=_latin1 0x323031302D31302D30312031323A30303A3030 COLLATE latin1_swedish_ci |
| master-bin.000014 | 229 | Query       |         1 |         321 | use `tac`; insert into test values (@foo)                                         |
| master-bin.000014 | 321 | Xid         |         1 |         348 | COMMIT /* xid=148 */                                                              |
+-------------------+-----+-------------+-----------+-------------+-----------------------------------------------------------------------------------+
5 rows in set (0.00 sec)
  1. Password()

直接内嵌使用password,会在binlog里暴露密码,就像下面的测试。可以使用user variable,但是不知道user variable的编码保密性如何。

1
2
3
4
5
6
7
8
9
10
11
mysql> insert into test values(password('tac'));
mysql> show binlog events in 'master-bin.000015';
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
| Log_name          | Pos | Event_type  | Server_id | End_log_pos | Info                                                   |
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
| master-bin.000015 |   4 | Format_desc |         1 |         107 | Server ver: 5.5.31-0ubuntu0.12.04.1-log, Binlog ver: 4 |
| master-bin.000015 | 107 | Query       |         1 |         174 | BEGIN                                                  |
| master-bin.000015 | 174 | Query       |         1 |         276 | use `tac`; insert into test values(password('tac'))    |
| master-bin.000015 | 276 | Xid         |         1 |         303 | COMMIT /* xid=158 */                                   |
+-------------------+-----+-------------+-----------+-------------+--------------------------------------------------------+
4 rows in set (0.01 sec)

简单一点学习如上。

Lisp一个大写的坑

最近一直掉在一个坑里,今天刚出坑

想用宏定义不同的函数,类似于:

1
2
(defmacro macro (name)
     `(defmethod ,(intern (format nil "set-~a" name)) ()))

跑(macro test),结果就是

函数名是”|set-TEST|”,而不是需要的”set-test”
1
#<STANDARD-METHOD |set-TEST| NIL>

几天的困惑以后(尝试换过lisp的实现去测试),找到了这篇文章,发现是符号名大小写引起的问题

简单测试一下
1
2
3
4
5
6
7
8
9
10
CL-USER> (eq (intern "test") 'test)
NIL
CL-USER> (intern "test")
|test|
:INTERNAL
CL-USER> (eq (intern "TEST") 'test)
T
CL-USER> (intern "TEST")
TEST
:INTERNAL

大小写通过intern生成的符号是不一样的,全大写才会生成正确的符号。

参考:

How to handle symbols in LISP

我写了半辈子程序 & Java的重载方法选择基于编译期参数类型

一段时间没更新过blog,因为花了些时间在读lisp的入门,还将继续一段时间

先庆祝下自己25岁,可以正式对外宣称”我写了半辈子程序”

读lisp的入门时,有一个java的对比例子觉得很有意思(虽然事后想想也就那么回事)…

简单的说,一个call传递给object(根据运行时的类型找到需要处理这个call的类),并找到对应的方法(根据call参数的编译时类型,找到需要处理这个call的函数),并执行

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
public class A {
    public void foo(A a) {
        System.out.println("A/A");
    }

    public void foo(B b) {
        System.out.println("A/B");
    }
}

public class B extends A {
    public void foo(A a) {
        System.out.println("B/A");
    }

    public void foo(B b) {
        System.out.println("B/B");
    }
}

public class C {
    public static void main(String[] params) {
//        A obj = new A();
        A obj = new B();
        obj.foo(obj);
    }
}

运行结果是”B/A”,B这个类是根据运行类型找到的,foo(A)这个方法是根据编译类型找到的。

读了“There Is No Simple Solution for Local Storage”

作为一个非专职Client开发,偶然读了读关于localStorage的这篇有趣的文字,非常有意思,引文也很值得一读。

作者的思考方向很全面。摘取其中重要的部分:

  1. (性能) localStorage是会阻止渲染(同步),会写I/O

  2. (浏览器行为) localStorage会被浏览器预载入,会被永久存储,浏览器支持良好

  3. (开发接口) 接口简单。缺少getSize这样的接口

  4. (用户接口) 用户授权简单

解决localStorage问题的方案,可能是升级或者寻找替代品,衡量的方向也是上面几点。

值得一读的引文:

这篇引文 描述了LocalStorage的几个限制。值得注意的是http和https的localStorage不能互通。

Saving images and files in localStorage

略学习Lisp的quote和backquote

略学习了lisp里面奇怪的符号集,解释这些符号上,人类的语言基本是苍白的。

第一份参考来自lispworks的文档,对[`’@,]这几种符号做了定义。

第二份中文参考 被到处抄袭。对quote和backquote做了很好的中文说明,嵌套quote部分惨不忍睹。

第三份参考 对嵌套quote做了很好地解释。

贴一些自己的学习代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CL-USER> (list 1 2)
(1 2)
CL-USER> '(1 2)
(1 2) ;Quote act as list
CL-USER> `(1 2)
(1 2) ;Backquote act as list

CL-USER> (let ((x 1)) '(,x))
; Evaluation aborted on #<CCL::SIMPLE-READER-ERROR #xC78878E>. ;Quote can't work with comma
CL-USER> (let ((x 1)) `(,x))
(1) ;Backquote can work with comma

CL-USER> (let ((x `(1 2))) `(,@x))
(1 2) ;BackQuote can work with comma-at-sign

CL-USER> (let ((x `(1 2))) `(,x))
((1 2)) ;x will not be "expand" when use comma instead of comma-at-sign

读了‘谈谈 jQuery 中的防冲突(noConflict)机制’

文章在此:http://ued.taobao.com/blog/2013/03/jquery-noconflict/

倒是也没什么大不了的,略感叹jquery的api设计的周到。