2008-08-24 Sun
注:此文首发于 《程序员》杂志 2008 年 7 月刊。
从 Shard 到 Sharding
"Shard" 这个词英文的意思是"碎片",而作为数据库相关的技术用语,似乎最早见于大型多人在线角色扮演游戏(MMORPG)中。"Sharding" 姑且称之为"分片"。
Sharding 不是一门新技术,而是一个相对简朴的软件理念。如您所知,MySQL 5 之后才有了数据表分区功能,那么在此之前,很多 MySQL 的潜在用户都对 MySQL 的扩展性有所顾虑,而是否具备分区功能就成了衡量一个数据库可扩展性与否的一个关键指标(当然不是唯一指标)。数据库扩展性是一个永恒的话题,MySQL 的推广者经常会被问到:如在单一数据库上处理应用数据捉襟见肘而需要进行分区化之类的处理,是如何办到的呢? 答案是:Sharding。
Sharding 不是一个某个特定数据库软件附属的功能,而是在具体技术细节之上的抽象处理,是水平扩展(Scale Out,亦或横向扩展、向外扩展)的解决方案,其主要目的是为突破单节点数据库服务器的 I/O 能力限制,解决数据库扩展性问题。
事关数据库扩展性
说起数据库扩展性,这是个非常大的话题。目前的商业数据都有自己的扩展性解决方案,在过去相对来说比较成熟,但是随着互联网的高速发展,不可避免的会带来一些计算模式上的演变,这样很多主流商业系统也难免暴露出一些不足之处。比如 Oracle 的 RAC 是采用共享存储机制,对于 I/O 密集型的应用,瓶颈很容易落在存储上,这样的机制决定后续扩容只能是 Scale Up(向上扩展) 类型,对于硬件成本、开发人员的要求、维护成本都相对比较高。
Sharding 基本上是针对开源数据库的扩展性解决方案,很少有听说商业数据库进行 Sharding 的。目前业界的趋势基本上是拥抱 Scale Out,逐渐从 Scale Up 中解放出来。
Sharding 的应用场景
任何技术都是在合适的场合下能发挥应有的作用。 Sharding 也一样。联机游戏、IM、BSP 都是比较适合 Sharding 的应用场景。其共性是抽象出来的数据对象之间的关联数据很小。比如IM ,每个用户如果抽象成一个数据对象,完全可以独立存储在任何一个地方,数据对象是 Share Nothing 的;再比如 Blog 服务提供商的站点内容,基本为用户生成内容(UGC),完全可以把不同的用户隔离到不同的存储集合,而对用户来说是透明的。
这个 "Share Nothing" 是从数据库集群中借用的概念,举例来说,有些类型的数据粒度之间就不是 "Share Nothing" 的,比如类似交易记录的历史表信息,如果一条记录中既包含卖家信息与买家信息,如果随着时间推移,买、卖家会分别与其它用户继续进行交易,这样不可避免的两个买卖家的信息会分布到不同的 Sharding DB 上,而这时如果针对买卖家查询,就会跨越更多的 Sharding ,开销就会比较大。
Sharding 并不是数据库扩展方案的银弹,也有其不适合的场景,比如处理事务型的应用就会非常复杂。对于跨不同DB的事务,很难保证完整性,得不偿失。所以,采用什么样的 Sharding 形式,不是生搬硬套的。
Sharding与数据库分区(Partition)的区别
有的时候,Sharding 也被近似等同于水平分区(Horizontal Partitioning),网上很多地方也用 水平分区来指代 Sharding,但我个人认为二者之间实际上还是有区别的。的确,Sharding 的思想是从分区的思想而来,但数据库分区基本上是数据对象级别的处理,比如表和索引的分区,每个子数据集上能够有不同的物理存储属性,还是单个数据库范围内的操作,而 Sharding 是能够跨数据库,甚至跨越物理机器的。(见对比表格)

(转载别忘了此图。注明全文来自 http://www.dbanotes.net)
Sharding 策略
数据 Sharding 的策略与分区表的方式有很多类似的地方,有基于表、ID 范围、数据产生的时间或是SOA 下理念下的基于服务等众多方式可选择。而与传统的表分区方式不同的是,Sharding 策略和业务结合的更为紧密,成功的 Sharding 必须对自己的业务足够熟悉,进行众多可行性分析的基础上进行,"业务逻辑驱动"。
Sharding 实现案例分析:Digg 网站
作为风头正劲的 Web 2.0 网站之一的 Digg.com,虽然用户群庞大,但网站数据库数据并非海量,去年同期主数据大约只有 30GB 的样子,现在应该更大一些,但应该不会出现数量级上增长,数据库软件采用 MySQL 5.x。Digg.com的 IO 压力非常大,而且是读集中的应用(98%的 IO 是读请求)。因为提供的是新闻类服务,这类数据有其自身特点,最近时间段的数据往往是读压力最大的部分。
根据业务特点,Digg.com 根据时间范围对主要的业务数据做 Sharding,把不到 10% 的"热"数据有效隔离开来,同时对这部分数据用以更好的硬件,提供更好的用户体验。而另外 90% 的数据因用户很少访问,所以尽管访问速度稍慢一点,对用户来说,影响也很小。通过 Sharding,Digg 达到了预期效果。
现有的 Sharding 软件简介
现在 Sharding 相关的软件实现其实不少,基于数据库层、DAO 层、不同语言下也都不乏案例。限于篇幅,作一下简要的介绍。
MySQL Proxy + HSCALE
一套比较有潜力的方案。其中 MySQL Proxy (http://forge.mysql.com/wiki/MySQL_Proxy) 是用 Lua 脚本实现的,介于客户端与服务器端之间,扮演 Proxy 的角色,提供查询分析、失败接管、查询过滤、调整等功能。目前的 0.6 版本还做不到读、写分离。HSCALE 则是针对 MySQL Proxy 插件,也是用 Lua 实现的,对 Sharding 过程简化了许多。需要指出的是,MySQL Proxy 与 HSCALE 各自会带来一定的开销,但这个开销与集中式数据处理方式单条查询的开销还是要小的。
Hibernate Shards这是 Google 技术团队贡献的项目(http://www.hibernate.org/414.html),该项目是在对 Google 财务系统数据 Sharding 过程中诞生的。因为是在框架层实现的,所以有其独特的特性:标准的 Hibernate 编程模型,会用 Hibernate 就能搞定,技术成本较低;相对弹性的 Sharding 策略以及支持虚拟 Shard 等。
Spock Proxy
这也是在实际需求中产生的一个开源项目。Spock(http://www.spock.com/)是一个人员查找的 Web 2.0 网站。通过对自己的单一 DB 进行有效 Sharding化 而产生了Spock Proxy(http://spockproxy.sourceforge.net/ ) 项目,Spock Proxy 算得上 MySQL Proxy 的一个分支,提供基于范围的 Sharding 机制。Spock 是基于 Rails 的,所以Spock Proxy 也是基于 Rails 构建,关注 RoR 的朋友不应错过这个项目。
HiveDB
上面介绍了 RoR 的实现,HiveDB (http://www.hivedb.org/)则是基于Java 的实现,另外,稍有不同的是,这个项目背后有商业公司支持。
PL/Proxy
前面几个都是针对 MySQL 的 Sharding 方案,PL/Proxy 则是针对 PostgreSQL 的,设计思想类似 Teradata 的 Hash 机制,数据存储对客户端是透明的,客户请求发送到 PL/Proxy 后,由这里分布式存储过程调用,统一分发。 PL/Proxy 的设计初衷就是在这一层充当"数据总线"的职责,所以,当数据吞吐量支撑不住的时候,只需要增加更多的 PL/Proxy 服务器即可。大名鼎鼎的 Skype 用的就是 PL/Proxy 的解决方案。
Pyshards
http://code.google.com/p/pyshards/wiki/Pyshards 这是个基于 Python的解决方案。该工具的设计目标还有个 Re-balancing 在里面,这倒是个比较激进的想法。目前只支持 MySQL 数据库。
结束语
Sharding 是一项仍处于高速发展中的"老"技术,随着 Web 2.0 的发展,Sahrding逐渐从比较"虚"的概念变成比较"实"的运用思路,开放源代码软件大潮也给 Sharding 注入新的活力,相信会有越来越多的项目采用 Sharding 技术,也会有更多成熟的 Sharding 方案和数据库附加软件涌现。
你的站点 Sharding 了么?
--EOF--
另,本周末我讲参加这个活动:体验基于OpenSolaris的Web/企业应用,做一个题为《设计可扩展的面向互联网应用的MySQL数据库》的简单分享。欢迎杭州朋友光临指导。
2008-08-23 Sat
Author:NinGoo posted on NinGoo.net
在Linux中,如果要让进程在后台运行,一般情况下,我们在命令后面加上&即可,实际上,这样是将命令放入到一个作业队列中了:
$ ./test.sh & [1] 17208 $ jobs -l [1]+ 17208 Running ./test.sh &
对于已经在前台执行的命令,也可以重新放到后台执行,首先按ctrl+z暂停已经运行的进程,然后使用bg命令将停止的作业放到后台运行:
$ ./test.sh [1]+ Stopped ./test.sh $ bg %1 [1]+ ./test.sh & $ jobs -l [1]+ 22794 Running ./test.sh &
但是如上方到后台执行的进程,其父进程还是当前终端shell的进程,而一旦父进程退出,则会发送hangup信号给所有子进程,子进程收到hangup以后也会退出。如果我们要在退出shell的时候继续运行进程,则需要使用nohup忽略hangup信号,或者setsid将将父进程设为init进程(进程号为1)
$ echo $$ 21734 $ nohup ./test.sh & [1] 29016 $ ps -ef | grep test 515 29710 21734 0 11:47 pts/12 00:00:00 /bin/sh ./test.sh 515 29713 21734 0 11:47 pts/12 00:00:00 grep test
$ setsid ./test.sh & [1] 409 $ ps -ef | grep test 515 410 1 0 11:49 ? 00:00:00 /bin/sh ./test.sh 515 413 21734 0 11:49 pts/12 00:00:00 grep test
上面的试验演示了使用nohup/setsid加上&使进程在后台运行,同时不受当前shell退出的影响。那么对于已经在后台运行的进程,该怎么办呢?可以使用disown命令:
$ ./test.sh & [1] 2539 $ jobs -l [1]+ 2539 Running ./test.sh & $ disown -h %1 $ ps -ef | grep test 515 410 1 0 11:49 ? 00:00:00 /bin/sh ./test.sh 515 2542 21734 0 11:52 pts/12 00:00:00 grep test
另外还有一种方法,即使将进程在一个subshell中执行,其实这和setsid异曲同工。方法很简单,将命令用括号() 括起来即可:
$ (./test.sh &) $ ps -ef | grep test 515 410 1 0 11:49 ? 00:00:00 /bin/sh ./test.sh 515 12483 21734 0 11:59 pts/12 00:00:00 grep test
注:本文试验环境为Red Hat Enterprise Linux AS release 4 (Nahant Update 5),shell为/bin/bash,不同的OS和shell可能命令有些不一样。例如AIX的ksh,没有disown,但是可以使用nohup -p PID来获得disown同样的效果。
还有一种更加强大的方式是使用screen,首先创建一个断开模式的虚拟终端,然后用-r选项重新连接这个虚拟终端,在其中执行的任何命令,都能达到nohup的效果,这在有多个命令需要在后台连续执行的时候比较方便:
$ screen -dmS screen_test
$ screen -list
There is a screen on:
27963.screen_test (Detached)
1 Socket in /tmp/uscreens/S-jiangfeng.
$ screen -r screen_test
Related Articles
PermLink: http://www.ningoo.net/html/2008/how_to_run_processes_on_background_in_linux.html
Add Comments(0) | Follow NinGoo@Twitter | Google Reader

2008-08-22 Fri
作者:d.c.b.a, 订阅AnySQL, Oracle数据库恢复及服务, Sybase恢复, 磁盘及RAID恢复
一提到系统监控就会联想到Cacti这个优秀的开源软件, 或用Nagios. 不管什么样的监控软件平台, 监控可做的事大约有四个方面.
一定的报警机制. 对于特定的事件, 需要用特定的方式(手机, 邮件, 淘宝旺旺等)通知相关人员, 通知的事件大小由监制的机制来决定, 如果是7x24的, 那一般只有交易量下降多少比例时才会报警, 如果不是7x24的, 那么有任何错误发生时都需要报警.
一定的图表显示. 图表是最好的表现数据趋势的方式, 对于交易量或主机负荷之类的少数重要数据, 用图的方式显示. 缺点是一个屏幕内能提供的信息量比较少, 对于详细诊断问题所在起不了多少帮助.
很多的详细信息. 用网页方式显示某些方面详细的数据, 如将所有的消息滞留的情况记录下来, 用来查找发生的问题. 更多的如应用中关键的API调用次数, 显示一个当前值和历史平均值, 也可以确定某个点是不是有问题. 非常适合于详细问题的快速确定.
一定的自动响应机制. 管理出身的会很关注这一点, 是很好的设想, 但不容易实现, 最简单地说表空问不足这个问题吧, 让程序自动加文件? 还是做一个空间预测, 提前加好空间, 个人偏向于后者.
现在监时做的一些监控就是建立在自已开发的WebChart基础上的, 表格和图形并存的方式. 更适合于白天工作时段的监控, 好好保存一定历史信息, 还可用于事后问题查找.
相关文章 | Related Artiles
我要留言(当前0)
2008-08-21 Thu
Facebook 其实对待技术的态度其实挺开放的。今天阅读了这篇 Scale Out, 工程师 Jason Sobel 介绍了在对付跨地域 MySQL 复制网络延迟的问题。
Cache 一致性问题解决思路
大量的 MySQL + Memcached 服务器,布署简示:
California (主 Write/Read)............. Virginia (Read Only)
主数据中心在 California ,远程中心在 Virginia 。这两个中心网络延迟就有 70ms,MySQL 数据复制延迟有的时候会达到 20ms. 如果要让只读的信息从 Virginia 端发起,Memcached 的 Cache 数据一致性就是个问题。
- 1 用户发起更新操作,更名 "Jason" 到 "Monkey" ;
- 2 主数据库写入 "Monkey",删除主、从两端 Memcached 中的名字值;
- 3 在 Virginia 有人查看该用户 Profile ;
- 4 在 Memcached 中没发现用户名字,从 Virginia Slave 数据库读取,因为网络延迟,结果读到了 "Jason";
- 5 更新 Virginia Memcached 中的该用户名字为 "Jason";
- 6 复制追上了,更新名字为 ""Monkey";
- 7 又有人读取 Profile 了;
- 8 在 Memcached 中找到了键值,返回 "Jason" (实际上造成业务冲突了)
解决办法挺有意思,在 SQL 解析层嵌入了针对 Memcached 的操作。
- 1 用户发起更新操作,更名 "Jason" 到 "Monkey" ;
- 2 主数据库写入 "Monkey",删除主端 Memcached 中的名字值,但Virginia 端 Memcached 不删;(这地方在 SQL 解析上作了一点手脚,把更新的操作"示意"给远程);
- 3 在 Virginia 有人查看该用户 Profile ;
- 4 在 Memcached 中找到键值,返回值 "Jason";
- 5 复制追上更新 Slave 数据库用户名字为 "Monkey",删除 Virginia Memcached 中的键值;
- 6 在 Virginia 有人查看该用户 Profile ;
- 7 Memcache 中没找到键值,所以从 Slave 中读取,然后得到正确的 "Monkey" 。
这里面的一个简单的原则是: 更新后的数据,用户第一次读取要从数据库读,顺便扔一份到 Cache 里,而不是在写入的时候直接更新 Memcached 。避免写事务过大。
而写操作的原则是:一次写入,到处分发。
第二个问题是关于"Page Routing"的 ,也很有参考价值。感兴趣的自己读一下吧。
--EOF--
另推荐一下: 分布式系统中的一致性和可用性,该文是上次在支付宝 QClub 活动的总结之二。
由于IO的请求不是每次等待完成指令后再发送下一个请求,而是存在于队列中,且遵循FIFO。因此如果遇到存储掉电的情况,就可能会出现数据的不一致。虽然这种情况出现的可能性不大,因为存储中有电池,能保证cache中的信息写到存储中。但是在这里还是提一下数据丢失的风险。
由于我们的异步IO的队列中是针对使用裸设备的IO请求,即redo log、datafile和controlfile,这边分2种情况讨论存储掉电时候,数据的丢失情况(注:以下情况考虑的是cache中的IO请求丢失,但是文件未损坏的情况):
(1)当记录A insert到表中,已经commit,记录B insert到表中,未commit,此时checkpoint未发生时。此时的IO队列中有insert A的redo请求,insert B的redo请求,但未有buffer向dbfile写的请求。此时掉电,oracle端的情况是oracle认为redo的IO请求已经写到redo log,os端的情况redo的IO请求还没写到redo log中。重启数据库时候之前的oracle端认为redo和dbfiles的scn不一致,需要介质恢复,但是在启动的时候,读取redo log,由于在os级上尚未写入,redo log和dbfiles的scn一致,因此正常启动,但是丢失AB记录。
(2)当记录A insert到表中,已经commit,记录B insert到表中,未commit,此时发生了checkpoint。此时的IO队列中有insert A的redo请求,insert B的redo请求,A记录从db buffer向dbfile写的请求,还有控制文件被更新的请求。当都未写到裸设备时掉电,oracle端的情况是oracle认为redo的IO请求已经写到redo log中,db buffer的请求已经写入了数据文件,控制文件已经被更新,os端的情况是redo的IO请求和写dbfiles的IO请求均未完成,控制文件未更新。重启数据库时,由于scn还是一致的,因此能正常打开,但丢失cache中的A、B记录。
(3)当记录A insert到表中,已经commit,记录B insert到表中,未commit,此时发生了checkpoint。此时的IO队列中有insert A的redo请求,insert B的redo请求,A记录从db buffer向dbfile写的请求,还有控制文件被更新的请求。当redo的请求已经被写入redo log,但是A记录从db buffer向dbfiles写的请求和更新控制文件的请求还没写的时候掉电。Oracle端的情况是oracle认为redo的IO请求已经写到redo log中,db buffer的请求已经写入了数据文件,控制文件已经被更新,os端的情况是redo完成写入redo log,但是dbfiles未写,控制文件未更新。重启数据库,oracle读取了redolog、控制文件、dbfile,发现redo log、dbfile不一样,需要介质恢复。AB数据不丢失。
2008-08-20 Wed
这几天,在客户处实施Sybase ASE到Oracle 10g的变化数据捕获以及数据转换的前期测试工作,问题此起彼伏,但最终效果圆满,感觉上仿佛遇神杀神,遇鬼杀鬼。不拽了,总结一下遇到的问题以及相应的解决方法。
一. ODI连接数据库阶段
1. JDBC版本 - jConnect 5.5
ODI自带的JDBC驱动无法正常连接Sybase ASE数据库。
解决方法:需要去Sybase站点上下载jConnect 5.5版本,然后将其中的jconn2.jar文件拷贝进ODI安装目录的drivers文件夹中,之后再次选择com.sybase.jdbc2.jdbc.SybDriver,才可以连接。
2. 为什么不选择jConnect 6.05
因为在jConnect 6版本以后,”getColumnName”方法返回的是列的COLUMN Name,而之前的版本都是返回列的ALIAS,而ODI使用的都是列ALIAS,因此如果选用jConnect 6.05,那么在最后执行Interface的时候,将会碰到下面的错误:
com.sunopsis.sql.SnpsMissingParametersException: Missing parameter…
解决方法:使用jConnect 5.5,这也是Oracle lab test时推荐的JDBC驱动版本。
3. JDBC连接串的写法
如果写法如下:
Driver是:com.sybase.jdbc2.jdbc.SybDriver
连接串是:jdbc:sybase:Tds:172.22.224.106:4100/dbemp1
连接时将碰到JZ00L错误,已经确保用户名和密码一定正确:
java.sql.SQLException: JZ00L: Login failed. Examine the SQLWarnings chained to this exception for the reason(s).
解决方法:添加charset属性,修改连接串为 jdbc:sybase:Tds:172.22.224.106:4100/dbemp1?charset=eucgb
最后Physical Schema的设置应该类似如下界面(点击以后放大)。

二. Datastore创建阶段
1. Sun JDBC-ODBC Bridge驱动无法实施反向工程(Reverse Engineering)
因为一开始配置jConnect驱动的时候死活无法连通,因此尝试了Sun JDBC-ODBC Bridge驱动,这种方法需要首先在机器上创建一个ODBC连接,因此也就需要Sybase客户端,所以实际上是不推荐的,而且通过JDBC-ODBC Bridge连接进数据库以后,发现无法执行反向工程。
解决方法:放弃这种方法,换用jConnect连接Sybase ASE。
2. Changed Data Capture
对于创建了唯一聚簇索引的Sybase表也无法启动Journal,必须需要Primary Key。没有主键在启动Journal的时候会碰到如下错误:
com.sunopsis.tools.core.exception.SnpsSimpleMessageException: Journalizing requires a Primary Key on the Table:ODI_TEST
解决方法:在表上创建Primary Key。
三. Interface执行阶段
1. Oracle端表中包含Date或者Timestamp类型的字段时,执行时报ORA-30088错误
如果包含DATE或者TIMESTAMP类型字段的Datastore是由反向工程直接从数据库中reverse生成的,那么对于DAYE字段,默认的Logical Length是7,对于TIMESTAMP字段默认的Logical length是16,那么这样在执行阶段的create work table步骤中,将会按照这些Logical Length来在目标数据库端创建C$_表,而DATE(7)或者TIMESTAMP(16)这样的语法都会报ORA-30088错误。
java.sql.SQLException: ORA-30088: datetime/interval precision is out of range
解决方法:在reverse生成Datastore以后,手工修改DATE和TIMESTAMP类型的字段,将Logical length改为空,Scale也改为空。

2. 执行时,Loading data步骤时报7725错误
在执行Interface的时候,到Loading data这一步,报如下错误:
7725 : ZZZZZ : com.sybase.jdbc2.jdbc.SybSQLException: Cursor ‘jconnect_implicit_2′ was declared with a FOR UPDATE clause. This cursor was found to be read only.
这是花费了最长时间解决的错误,十分感谢Rich Ho何致亿,帮我发邮件到OracleDI的邮件列表中去提问。
解决方法:在Topology Manager中将Data Server的Array Fetch Size和Batch Update Size设置为0,默认是30。
到今天为止,ODI的大致架构和基本功能算是掌握了,更加深入的学习还要看以后这个项目是不是会继续下去了。
昨天下午,第一次降落在北京国际机场三号航站楼,从兰州返回北京,结束了这一周的旅程。
感谢兰州大学李仲贤老师的邀请,在兰大进行了一次为期两天左右的技术交流。
然后顺路西上,经嘉峪关、至敦煌,走青藏公路,出玉门关,深入戈壁,在归途中,我们甚至看到了难得一见的海市蜃楼。当时在茫茫戈壁的尽头,看到清晰的海中山,云水环绕,还有红砖墙逶迤环绕,司机师傅说,这样的景象在沙漠里是很常见的。
下面这张照片就是来自嘉峪关 - 天下第一雄关 的最后一道城门,出了这道门就是塞外:
在敦煌,丝绸之路的明珠之上,我们足登鸣沙山,掬水月牙泉,听大漠驼铃,彻底圆了我的西行梦。
至于在莫高窟,除了赞叹之外,更多的是气愤,众多的历史珍宝流落海外,不知何日能重演辉煌了。
这次的旅程非常开心,美中不足是我一直被感冒发烧所困扰,期待下一次再赴敦煌。
下面这张照片来自玉门关,以前出了这道门,就是茫茫戈壁,春风都不度的地方:
感谢李老师全家同行,李萱小美女的体力与耐力都是相当不错,一直陪我们走完了所有的旅程,感谢吴斌帮我负担了不少的重量,为我拍摄了大量的照片。
还要感谢公安系统的林科长,他的关照使我们的旅途顺利而舒畅,更重要的是他对软件及系统的了解让我对甲方的印象大为改观!
这次最远的地方到达玉门关外的雅丹地貌景区,距离新疆仅有8Km:
希望以后有机会再赴兰州,重走丝绸之路!
-The End-
相关文章|Related Articles
评论数量(0)|Add Comments
本文网址:http://www.eygle.com/archives/2008/08/lanzhou_silkroad.html

