`
willko
  • 浏览: 383813 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

NoSQL数据转储(同步)方式

阅读更多
在使用数据库方面,我们经常会遇到读/写瓶颈,只要不是达到门户级的瓶颈,多数情况都能很好的解决。这段时间NoSQL的崛起,极大的方便解决写瓶颈。写瓶颈的解决方法是先把数据缓冲到NoSQl中,然后定时转储回RMDB里,毕竟RMDB的查询能力更强。所以,会遇到一个问题,就是数据怎么同步回RMDB?

假如,有这样的应用场景,我们使用了NoSQL来存储文章点击数(pv),并定时将点击数存储到MySQL里。

一.用户触发转储
首先给文章增加一个最后同步数据的时间戳,用于记录上次同步数据的时间戳,每次都先对比下最后同步时间是否大于n,大于n的话就同步回MySQL,然后将这次同步的时间戳保存回去。这个最后同步时间戳不一定要保存到MySQL里,如果使用了Memcached的话,可以保存到Memcached就可以了。

缺点:
1.如果静态化了,用户将触发不到这个机制
2.每次会有少量数据没同步到MySQL,因为没有达到时间间隔N的话就触发不了
3.并发大话,可能同一时间内会同步多次,所以要尽快保存最后同步时间戳,或者利用memcached的add方法来模拟锁,add方法如果key存在的话返回false,返回false表示被锁,可以不同步点击数。

二.Redis
Redis最明显的优势是支持数据结构,其次就是高性能。

1.Multiple DB
Redis可以更换db,一个Redis我们可以划分多个db。用于存储不同维度的数据,避免混合在一起,例如,日志相册分db存储,更管各的。

2.KEYS
KEYS可以返回所有的复合模式的Key,Redis天生对遍历Key提供了很良好的支持。

3.SADD和SMEMBERS
Redis支持set结构,可以用set结构来存储有更新的文章id,然后通过这个列表去同步数据。
SADD对于已经存在的元素是不会添加到集合中的,也就是元素在set里是唯一的。SMEMBERS可以返回set里的所有元素。
也就是说,set里只是用来保存点击数有变动的文章id,然后通过这些文章id,去其它NoSQL里读取点击数,同步到数据库。

缺点:
1.专门部署Redis似乎不太值得
2.Redis是基于定时内存转储了,可能丢失部分数据
3.Redis太依赖内存,数据大于内存,使用swap,性能下降

三.遍历Memcached
虽然遍历Memcached不是一个好的方法,但是相信有其适用的场景。使用stats items和stats cachedump能得到所有的key。

1.扩展memcache
class Memcache_Plus extends Memcache {

	public function getKeys() {
		$items = $this->getStats('items');
		$keys = array();

			$serverItems = $items['items'];

			foreach ($serverItems as $slabId => $item) {
				$slabKeys = $this->getStats('cachedump', $slabId, 0);

					foreach ($slabKeys as $slabKey => $slabKeyStatus) {
						$keys[] = $slabKey;
					}
			}

		return $keys;
	}

	public function getExtendedKeys() {
		$items = $this->getExtendedStats('items');
		$keys = array();

		foreach ($items as $server) {
			$serverItems = $server['items'];

			foreach ($serverItems as $slabId => $item) {
				$slabKeys = $this->getExtendedStats('cachedump', $slabId, 0);

				foreach ($slabKeys as $slabServer => $slabServerKeys) {
					foreach ($slabServerKeys as $slabServerKey => $slabServerKeyStatus) {
						$keys[] = $slabServerKey;
					}
				}
			}
		}

		return $keys;
	}
}}

使用:
$memcache = new Memcache_Plus;
$memcache->addServer('192.168.80.128', 11211);

$keys = $memcache->getKeys();

getKeys是返回单台服务器的key,getExtendedKeys是返回全部服务器的key。

2.扩展Memcached
到目前为止,Memcached::getStats不支持参数("items", "sizes", "slabs"...)

3.山寨socket版
写了个socket版,作为后备使用。用正则来匹配,主要是不想循环去解析每行。
function memcachedGetKeys($host, $port) {
	$keys = array();
	$fp = fsockopen($host, $port, $errno, $errstr, 30);

	if ($fp) {
		fwrite($fp, "stats items\r\n");

		$response = '';

		while (substr($response, -5) != "END\r\n" && substr($response, -5) != "ERR\r\n") {
			$response .= fread($fp, 1024);
		}

		preg_match_all("/STAT items:(\d+):/", $response, $matches);
		
		$slabIds = array_flip(array_flip($matches[1]));

		foreach ($slabIds as $slabId) {
			$response = '';

			fwrite($fp, "stats cachedump $slabId 0\r\n");

			while (substr($response, -5) != "END\r\n" && substr($response, -5) != "ERR\r\n") {
				$response .= fread($fp, 1024);
			}

			preg_match_all("/ITEM (.+) \[\d+ b; \d+ s\]/", $response, $matches);

			$keys = array_merge($keys, $matches[1]);
		}
	}
	
	return $keys;
}


缺点:
1.需要专门启动一个memcached来保存,遍历才不会有多余Key
2.memcached是保存在内存,数据丢失概率大。
3.memcached的LRU机制,内存不足的时候会导致数据被挤出

四.全量更新、增量更新以及Key的命名
1.Key的命名
因为value是用来保存点击数,我们可以用key来保存逻辑数据,这样我们可以从key来判断出属于哪篇文章,例如:前缀_文章id(article_1)、前缀_文章id_某天的日期(article_1_2010-05-01)

2.全量更新
如果只是想知道点击总数,那在统计和同步方面会比较简单,同步的时候直接全量更新就可以了。例如 UPDATE article SET view_count = ? WHERE article_id = ?

3.增量更新
有时候,我们的需求是细化到每天的点击数和总点击数,那总点击数的更新就不得不使用增量更新了,因为我们只记录了每天的点击数,没有一个总点击数,当然也可以同时递增2个key,那总点击数依然可以使用全量更新。

增量更新的话,我们只增量昨天和昨天之前的数据,因为今天的数据还在更新。同步完后可以把昨天和昨天之前的数据删除了。例如 UPDATE article SET view_count = view_count + ? WHERE article_id = ?

有时候,需要及时知道今天的数据,那今天的数据就不删除了,等到明天删除,如果是全量更新的话就很好办了。但是如果是增量更新的话,那只能在程序里加一下点击数,而不同步到MySQL里。

说了很多,需求不一致,或许不能直接解决问题,只希望提供一点思路。

参考资料:
如何对memcache的数据(key-value)进行遍历操作
2
1
分享到:
评论
9 楼 willko 2010-05-06  
nightsailer 写道
willko 写道

如果是讀取的話可以用nginx代替php讀取文件,效果會好點吧。


nginx的mogilefs module太弱,不支持etag,range,生产环境还是回避为好。
对于小文件也许问题不大,但是如果要serve 稍大的(比如》100Mb)文件就不行了。

对于小文件,我们的部署方案是nginx+proxy store+psgi backend. 效果很好。



谢谢你的分享
8 楼 nightsailer 2010-05-05  
willko 写道

如果是讀取的話可以用nginx代替php讀取文件,效果會好點吧。


nginx的mogilefs module太弱,不支持etag,range,生产环境还是回避为好。
对于小文件也许问题不大,但是如果要serve 稍大的(比如》100Mb)文件就不行了。

对于小文件,我们的部署方案是nginx+proxy store+psgi backend. 效果很好。


7 楼 willko 2010-05-05  
nightsailer 写道
GridFS最大的问题就是异步IO,尤其是PHP/Perl的driver不支持,无法streming,对于并发量很大的话,prefork模式有瓶颈。

不过node.js有相应的driver也可以解决,总体来说不太成熟。

我最近还在做这方面的尝试。

如果是讀取的話可以用nginx代替php讀取文件,效果會好點吧。
6 楼 nightsailer 2010-05-05  
GridFS最大的问题就是异步IO,尤其是PHP/Perl的driver不支持,无法streming,对于并发量很大的话,prefork模式有瓶颈。

不过node.js有相应的driver也可以解决,总体来说不太成熟。

我最近还在做这方面的尝试。
5 楼 willko 2010-05-05  
nightsailer 写道
我用tc做过一个和你类似的简单的统计。我用Perl做了一个FASTCGI,将点击数更新到tc里。tc的健就是你文章的id了,假设。然后另外用一个脚本,定时将所有的tc中的数据更新到mysql中。因为tc中永远只更新当日的数据,之后可以简单的计算后刷新总数就行了。 用2个脚本的好处是,读写两不误。很多时候我在维护mysql的同时,点击数仍然可以记录下来。

不过后来我用mongoDB替代了这个方式,更加快捷简便。

MongoDB確實是好東西,支持查詢,性能也可以,還支持sharding。
最近還在研究GirdFS的優缺點和應用場景。
4 楼 nightsailer 2010-05-05  
我用tc做过一个和你类似的简单的统计。我用Perl做了一个FASTCGI,将点击数更新到tc里。tc的健就是你文章的id了,假设。然后另外用一个脚本,定时将所有的tc中的数据更新到mysql中。因为tc中永远只更新当日的数据,之后可以简单的计算后刷新总数就行了。 用2个脚本的好处是,读写两不误。很多时候我在维护mysql的同时,点击数仍然可以记录下来。

不过后来我用mongoDB替代了这个方式,更加快捷简便。
3 楼 willko 2010-05-04  
nightsailer 写道
使用memcached不是一个好的方式。可以考虑Flare/TT。

如果仅仅是更新点击数的话完全可以用tc,然后刷新到mysql。

整体方案太繁琐了,似乎无需同步回mysq,仅仅是出于排序的目的?



这些方案是分开来的,可能是用一种就够了,看场合了。
比较像知道如果用tc的话,怎么同步回去?

之前问过MemcacheDB的作者,是用BDB工具导出一份,然后倒入MySQL。
2 楼 willko 2010-05-04  
nightsailer 写道
使用memcached不是一个好的方式。可以考虑Flare/TT。

如果仅仅是更新点击数的话完全可以用tc,然后刷新到mysql。

整体方案太繁琐了,似乎无需同步回mysq,仅仅是出于排序的目的?



只是一个例子,想讲的是一些可以考虑的方式。并不单指“点击数”
也就是从NoSQL同步到MySQL一些方式。
1 楼 nightsailer 2010-05-04  
使用memcached不是一个好的方式。可以考虑Flare/TT。

如果仅仅是更新点击数的话完全可以用tc,然后刷新到mysql。

整体方案太繁琐了,似乎无需同步回mysq,仅仅是出于排序的目的?


相关推荐

Global site tag (gtag.js) - Google Analytics