Archive for 十月 2012

MariaDB 新特性介绍-thread pool

我们按照性能、功能、管理、NoSQL扩展各个方面来一一介绍MariaDB的新特性。首先是性能方面的线程池。如果你短连接较多,如果你的数据库CPU压力过高,如果你想降低多个sleep线程白白占用机器内存,thread pool都能够缓解。
MySQL会给每个过来的客户端连接分配一个线程,专门用于跟这个客户端交互。但是,随着并发连接的增加,线程会越来越多。CPU的上下文切换,cache命中率下降以及锁的竞争都会加剧。为了缓解这个问题,MariaDB引入的线程池的概念。这个对业务开发人员来说并不是一个陌生的概念,甚至有些开发人员会说,这个特性早就应该加上了!其实这个特性在MySQL的Enterprise早就有了这个特性,需要付费才能使用。但是MariaDB是开源免费的。当然线程池好处很多:
  • 可以让MySQL维护的线程数降下来。很多MySQL DBA在show processlist的时候,经常会看到一堆不做事,一直sleep在那里的线程,如果使用了线程池,这个数量可以明显减少。
  • 对于那些短连接,CPU bound的MySQL来说,是一个非常好的特性。MySQL不用每次在客户端发起连接的时候,临时申请一个新的线程,并且立刻就需要把这个连接的资源全部释放掉。
  • 节省MySQL内存占用。线程多了,内存占用当然比较多。用线程池,可以把有限的内存让给更需要内存的buffer pool或者keycache使用。减少磁盘IO,提高系统的响应时间。
  • 避免MySQL连接数不够。当mysql达到max_connections时,会拒绝连接。使用线程池的话,就可以不用担心这个问题了。
但是,事情总是有利有弊的,使用线程池在某些场景下并不会有多少性能的提升:
  • 线程池技术要求相对较高。线程池在连接太多的时候怎么动态扩展,在链接减少的时候怎么收缩;线程池本身的开销怎样控制;是否能够利用OS原生的线程池来直接提供服务等一系列问题都是引入线程池需要考虑的。
  • 线程相互等待的问题。如果有长链接和短链接同时存在,那么短链接可能要等到长链接执行完所有操作,才能被轮到,执行时间不可控。

MariaDB充分考虑和优化了thread pool的实现,并提供了一系列参数给用户调整线程池的配置(thread_pool_size, thread_pool_stall_limit, thread_pool_idle_timeout, thread_pool_oversubscribe )和一些状态值(threadpool_threads,threadpool_idle_threads)提供给用户查询线程池使用情况。从实现方面来看,MariaDB相对MySQL Enterprise的线程池会使用原生的系统提供的线程池,并且考虑到各个系统OS的特点,优化多路IO(IO multiplexing)接口的使用。 具体信息可以参考:http://kb.askmonty.org/en/thread-pool-in-mariadb-55/

MariaDB新特性简介

随着Oracle计划把MySQL搞成闭源的声音越来越多,对MySQL何去何从的讨论也越来越多。由于MySQL是遵循GPL协议的,那么Oracle需要分发、传播和发布的时候就必须要开源。但是如果Oracle一定要把MySQL变得封闭起来,它也不是没有办法,比如:逐渐把开源社区里不亲近oracle的清理出去;或者让MySQL必须依赖的一些oracle闭源的软件和功能,使得MySQL不配合这些功能就变得非常难以使用等。最近,MySQL的新版本里面就去掉了对bug修复后进行验证的测试用例;这样,社区的人就无法保证自己以后发布的版本,在下一个版本里不会出现之前用户之前report过的bug。

商业毕竟是商业,Sun花了10亿美元收购MySQL,然后Oracle花了74亿美元收购Sun,这些钱都是白花花的银子。Larry. Ellison,Oracle的CEO,曾经明确表示不会放弃MySQL,换言之,也就是说,不会放过MySQL这块蛋糕。因为Oracle是按照CPU核数来计算licence的,这个不能改变,也无法改变。这也限制了Oracle在分布式领域的发展,而MySQL在分布式领域的贡献有目共睹。据小道消息,Oracle已经把它的一部分核心源码开放给了MySQL的部分核心开发人员,以促进MySQL 5.6进一步的稳定和扩大影响力。我们不知道Oracle有什么样的商业企图,但是,你如果认为MySQL会一直这样白白给你使用的话,那么Larry. Ellison就是一个傻子。

Larry. Ellison不是傻子,所以后来涌现了MySQL的很多分支,包括MariaDB,Drizzle,goole,facebook,阿里集团等维护的自己的分支版本。除了MariaDB,Drizzle以外,这些分支版本都是各个商业公司为了满足各自公司的需求而对源码进行修改和调整的版本,适合不适合你的业务场景我无从分辨。Drizzle是2008年从MySQL 6.0分支出来的,并且它明确申明了,部分MySQL的蹩脚(Gotchas)特性,它都不会保留,也就是说它和MySQL是不完全兼容的。

MySQL前CTO,被称为MySQL之父的Michael “Monty” Widenius在2009年2月,创办了Monty Program AB,并建立了mariaDB这个MySQL高性能的分支。MariaDB是发展最快的MySQL分支版本,与MySQL兼容并且有很多新的功能。

MariaDB有个很性感的中文名玛莉亚DB,对宅男型的DBA来说,终于有一个女性DB陪伴了。下面就让我们来看看这些功能到底有哪些。   (MariaDB目前有时候会被墙,不过沃趣科技已经搭建好了镜像(mariadb.woqutech.com),将部分文档和资料搬到国内的服务器上来,以解各位技术男的相思之苦。)

MariaDB5.5有,而MySQL5.6没有的功能包括:

简单列表如下:

High Performance Developers DBAs NoSQL
Thread pool Microsecond precision & type Segmented MyISAM keycache HandleSocket
Group commit in the binary log SphinxSE for full-text search Authentication plugins – PAM, Active Directory Dynamic columns
Non-blocking client library Subqueries materialize LIMIT ROWS EXAMINED
GIS functionality Progress reporting

后面我们会详细分析一下这些新特性。

mysql服务器raid 0,raid 1+0 stripe size划分策略

        之前看到一篇讲raid 0划分条带大小的文章,对应的联想到在MySQL使用的场景下可以怎么划分条带的问题,特别做了一点分析。因为raid 1+0和raid 0只是多了一个镜像,对条带大小划分没有影响,所以后面只讨论raid 0的条带大小划分。

                   

        没有一个条带大小可以适合所有场景。比如上图是对不同条带大小划分的两个图。左边的条带大小为4KB,右边的条带大小为64KB。现在需要分配空间给四个不同大小的文件,分别为4KB,20KB,100KB和500KB。分别用红色,蓝色,绿色和品红来表示的话,在条带为4K和64K的划分下,表示如上图。

        可以看到,如果请求的文件相对比较小的时候(4KB,20KB),数据只会分布到部分磁盘上;如果足够大,那么就会分布到所有的磁盘上(500KB)。以20KB文件为例,在4KB条带的情况下,数据被分布到四块磁盘,所有的4块磁盘可以同时提供读写,而在64KB条带的情况下,20KB占用不到一个条带大小,只能使用一块磁盘的IO。当然,如果20KB的文件较多的话,数据也会分布到各个条带中去,并发操作也能充分利用到所有的四块磁盘。再看看500KB的情况,在4KB条带的情况下需要分布到125个条带中去,而如果是64K的,只需要8个条带就够了。上图中文件没有进行修改,看上去都是连续的,文件修改以后,4K条带的文件数据会打散得很多,导致随机IO非常多,读出文件的代价非常大;而如果采用64KB的条带大小,问题就小的多。(这里很大一部分跟具体采用的文件系统分配,修改策略相关,就不深入讨论了)。可以大致得出一个结论,在并发比较高的情况下,由于有很多小的读写操作,那么最好使用大一点的条带大小;如果文件数目比较少,但是文件比较大的,可以选择稍微小一点的条带,充分把多块磁盘的性能发挥出来。当然,具体调整到多大,多小,需要根据你的应用来做适当调整。而具体到数据库:对于oltp的数据库,条带不能太小,一个页面IO最好据在一个条带深度内,这样可以减少IO跨盘的磁盘竞争,对于olap,由于并发不是问题,需要的是吞吐量,所以数据最好跨盘,IO粒度也较大,可以充分发挥多盘的IO能力

        那么,对于MySQL来说应该怎么划分条带大小列?

        首先,InnoDB页大小一般是16K,在percona下可以设置页大小,一般也调整为8K,设置太小的话,一页内包含的数据太少,B-tree深度太深的话影响性能。因为InnoDB是按照页来组织的,条带的大小必须大于页的大小,并且是页大小的整数倍。对于16K来说,也就是16K,32K,48K,64K…这样一个页不用到多个磁盘中去读取;一个页在多个磁盘上,部分写成功,部分写失败的问题也不存在了。

        其次,MySQL预读时,都是按照64个页来预读的,对应的16K的页,就是1M大小,对应8K的页就是512K。这个预读的数据最好能够正好分布在所有的磁盘上,这样对于预读这种大IO能够充分调用起所有的磁盘性能。上图中是4块盘,那么16K的页条带大小应该就是:16K*64/4=256K。如果是raid 1+0,10块盘,由于做了镜像,所以可以认为是5块盘。对应的就是:16K*64/5=102.4K。取整以后为128K。

        对应的MySQL strip size大小的公式为:页大小*64/raid0磁盘个数

mysql server_id错误配置讨论

在ITPUB上看到一个面试题,感觉很有意思,不知道大家有没有遇到过这样的问题:

NewImage

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

两种情况,

第一种情况:两个slave有同样的server-id会有什么问题?

第二种情况:如果是级联复制,再级联复制的路径上有相同的server-id会有什么问题。

 

 

其实很简单,只要你搭建复制,做一些简单的操作,你就能发现问题。

在第一个场景下,你会发现两个slave在不断的重连master,日志里面也会有错误信息,说slave被断掉,尝试重连,并且也连上去了。然后又被断掉,又重连…循环往复。

第二个场景,最后一个slave不能得到master的变更,在master上做的任何操作都不会应用到最后一个slave上。

 

 

MySQL复制的原理这边就不详细描述。简单的说,就是如果开启了log-bin记录二进制日志,master会在自己的binlog中记录下变更发生的时间,query(如果是Row的话会转成具体的行变更),server-id等信息,然后当slave请求(请求的信息中包括连接master的ip,port等连接信息;以及需要从master的那个二进制文件的哪个位置开始,获得之后的所有master变更)发送到master之后,master启动一个binlog dump的线程用于发送变更信息给该slave。slave通过IO thread接受消息,记录到自己的relay log中,(如果发现server-id等于自己的,它就认为是在本地产生的,直接丢弃),然后SQL thread读取relay log应用到本地MySQL。我们理解了这个,其实上面的两个问题就能够迎刃而解了。

第二个问题更加直观,master和第三个slave的server-id是一样的,所以第三个slave发现master产生的binlog,误以为是它自己产生的,就会丢弃掉。这样的话,master产生的binlog在第三个slave就无法应用。

第一个问题比较复杂一点,牵涉到master怎么处理server-id相同的slave请求。这里冲突的焦点就是,master怎么区分各个slave的不同之处。在MySQL中,就是用server id来区分的。比如:左边的slave首先连上master,master会分配binlog dump线程并于该slave通讯,发送变更信息;这时,右边的slave连上来,server-id也是2,master以server-id来区别slave,所以它认为是同一个slave连过来,请求另外一个binlog和对应position处开始的数据,于是按照正常逻辑它清理了左边slave的binlog dump线程,并给右边的分配binlog dump线程。左边的slave发现复制断掉以后会自动重连,所以右边的又悲剧了…循环往复,两个slave一直这样相互冲突。源码参看:sql/sql_parse.cc dispatch_command()的COM_BINLOG_DUMP段,截取部分源码如下:

pos = uint4korr(packet);
flags = uint2korr(packet + 4);
thd->server_id=0; /* avoid suicide */
if ((slave_server_id= uint4korr(packet+6))) // mysqlbinlog.server_id==0
····kill_zombie_dump_threads(slave_server_id);
thd->server_id = slave_server_id;

 

原因分析完了,我们来说说具体的解决方案:那就是要尽量避免上面两种情况:master的各个slave必须有不同的id;级联复制各节点不能有相同的server-id。

不妨把条件升级一下:所有的MySQL server_id都不允许一样?

server-id是用4个字节的unsigned int来保存的,值的范围:0 .. 4294967295。有将近43亿的server-id可以用。足够我们使用了。

要保证唯一的话,手工分配是完全不靠谱的,自动分配的话,我们提供两种方法以供参考:

1、使用一个集中式的自增的公共服务。可以用oracle的sequece,mysql的自增值等。优点是比较简单,缺点就是需要一个额外的服务

2、使用IP+PORT来算出一个server-id,一个IP和PORT可以唯一定位一个MySQL实例,所以各个MySQL server-id不会有冲突。IP和PORT怎么跟server-id对应列。由于IP本身对应的就是一个4个字节,(比如:INET_ATON(‘209.207.224.40’)=209×2563 + 207×2562 + 224×256 + 40=3520061480),加上端口两个字节,所以做不到完全的一一对应的关系。但是IP地址中有很多保留的的地址(比如:127.0.0.0到127.255.255.255是保留地址),另外由于一个IP对应的实例毕竟有限(你不可能在一个IP上启动65535个MySQL实例吧?),所以针对你自己的业务场景做一套适应的IP,PORT对应server-id的算式也是一个解决办法。比如:如果你高位两个字节都相同,就可以把端口的两个字节和IP后两个字节拼接成一个4字节的无符号整数作为server-id。