升东运维是一家专业从事Linux/Freebsd Unix/Windows平台网站维护业务的公司,专业提供Linux(CentOS Redhat Ubuntu等),Unix(Freebsd),Nginx,Apache等系统及网站维护,七年从业经验

Archive for the ‘数据库/NOSQL’ Category

Redis几个认识误区(转)

星期日, 一月 22nd, 2012

前几天微博发生了一起大的系统故障,很多技术的朋友都比较关心,其中的原因不会超出James Hamilton在On Designing and Deploying Internet-Scale Service(1)概括的那几个范围,James第一条经验“Design for failure”是所有互联网架构成功的一个关键。互联网系统的工程理论其实非常简单,James paper中内容几乎称不上理论,而是多条实践经验分享,每个公司对这些经验的理解及执行力决定了架构成败。

题外话说完,最近又研究了Redis。去年曾做过一个MemcacheDB, Tokyo Tyrant, Redis performance test,到目前为止,这个benchmark结果依然有效。这1年我们经历了很多眼花缭乱的key value存储产品的诱惑,从Cassandra的淡出(Twitter暂停在主业务使用)到HBase的兴起(Facebook新的邮箱业务选用HBase(2)),当再回头再去看Redis,发现这个只有1万多行源代码的程序充满了神奇及大量未经挖掘的特性。Redis性能惊人,国内前十大网站的子产品估计用1台Redis就可以满足存储及Cache的需求。除了性能印象之外,业界其实普遍对Redis的认识存在一定误区。本文提出一些观点供大家探讨。

1. Redis是什么

这个问题的结果影响了我们怎么用Redis。如果你认为Redis是一个key value store, 那可能会用它来代替MySQL;如果认为它是一个可以持久化的cache, 可能只是它保存一些频繁访问的临时数据。Redis是REmote DIctionary Server的缩写,在Redis在官方网站的的副标题是A persistent key-value database with built-in net interface written in ANSI-C for Posix systems,这个定义偏向key value store。还有一些看法则认为Redis是一个memory database,因为它的高性能都是基于内存操作的基础。另外一些人则认为Redis是一个data structure server,因为Redis支持复杂的数据特性,比如List, Set等。对Redis的作用的不同解读决定了你对Redis的使用方式。

互联网数据目前基本使用两种方式来存储,关系数据库或者key value。但是这些互联网业务本身并不属于这两种数据类型,比如用户在社会化平台中的关系,它是一个list,如果要用关系数据库存储就需要转换成一种多行记录的形式,这种形式存在很多冗余数据,每一行需要存储一些重复信息。如果用key value存储则修改和删除比较麻烦,需要将全部数据读出再写入。Redis在内存中设计了各种数据类型,让业务能够高速原子的访问这些数据结构,并且不需要关心持久存储的问题,从架构上解决了前面两种存储需要走一些弯路的问题。

2. Redis不可能比Memcache快

很多开发者都认为Redis不可能比Memcached快,Memcached完全基于内存,而Redis具有持久化保存特性,即使是异步的,Redis也不可能比Memcached快。但是测试结果基本是Redis占绝对优势。一直在思考这个原因,目前想到的原因有这几方面。

  • Libevent。和Memcached不同,Redis并没有选择libevent。Libevent为了迎合通用性造成代码庞大(目前Redis代码还不到libevent的1/3)及牺牲了在特定平台的不少性能。Redis用libevent中两个文件修改实现了自己的epoll event loop(4)。业界不少开发者也建议Redis使用另外一个libevent高性能替代libev,但是作者还是坚持Redis应该小巧并去依赖的思路。一个印象深刻的细节是编译Redis之前并不需要执行./configure。
  • CAS问题。CAS是Memcached中比较方便的一种防止竞争修改资源的方法。CAS实现需要为每个cache key设置一个隐藏的cas token,cas相当value版本号,每次set会token需要递增,因此带来CPU和内存的双重开销,虽然这些开销很小,但是到单机10G+ cache以及QPS上万之后这些开销就会给双方相对带来一些细微性能差别(5)。

3. 单台Redis的存放数据必须比物理内存小

Redis的数据全部放在内存带来了高速的性能,但是也带来一些不合理之处。比如一个中型网站有100万注册用户,如果这些资料要用Redis来存储,内存的容量必须能够容纳这100万用户。但是业务实际情况是100万用户只有5万活跃用户,1周来访问过1次的也只有15万用户,因此全部100万用户的数据都放在内存有不合理之处,RAM需要为冷数据买单。

这跟操作系统非常相似,操作系统所有应用访问的数据都在内存,但是如果物理内存容纳不下新的数据,操作系统会智能将部分长期没有访问的数据交换到磁盘,为新的应用留出空间。现代操作系统给应用提供的并不是物理内存,而是虚拟内存(Virtual Memory)的概念。

基于相同的考虑,Redis 2.0也增加了VM特性。让Redis数据容量突破了物理内存的限制。并实现了数据冷热分离。

4. Redis的VM实现是重复造轮子

Redis的VM依照之前的epoll实现思路依旧是自己实现。但是在前面操作系统的介绍提到OS也可以自动帮程序实现冷热数据分离,Redis只需要OS申请一块大内存,OS会自动将热数据放入物理内存,冷数据交换到硬盘,另外一个知名的“理解了现代操作系统(3)”的Varnish就是这样实现,也取得了非常成功的效果。

作者antirez在解释为什么要自己实现VM中提到几个原因(6)。主要OS的VM换入换出是基于Page概念,比如OS VM1个Page是4K, 4K中只要还有一个元素即使只有1个字节被访问,这个页也不会被SWAP, 换入也同样道理,读到一个字节可能会换入4K无用的内存。而Redis自己实现则可以达到控制换入的粒度。另外访问操作系统SWAP内存区域时block进程,也是导致Redis要自己实现VM原因之一。

5. 用get/set方式使用Redis

作为一个key value存在,很多开发者自然的使用set/get方式来使用Redis,实际上这并不是最优化的使用方法。尤其在未启用VM情况下,Redis全部数据需要放入内存,节约内存尤其重要。

假如一个key-value单元需要最小占用512字节,即使只存一个字节也占了512字节。这时候就有一个设计模式,可以把key复用,几个key-value放入一个key中,value再作为一个set存入,这样同样512字节就会存放10-100倍的容量。

这就是为了节约内存,建议使用hashset而不是set/get的方式来使用Redis,详细方法见参考文献(7)。

6. 使用aof代替snapshot

Redis有两种存储方式,默认是snapshot方式,实现方法是定时将内存的快照(snapshot)持久化到硬盘,这种方法缺点是持久化之后如果出现crash则会丢失一段数据。因此在完美主义者的推动下作者增加了aof方式。aof即append only mode,在写入内存数据的同时将操作命令保存到日志文件,在一个并发更改上万的系统中,命令日志是一个非常庞大的数据,管理维护成本非常高,恢复重建时间会非常长,这样导致失去aof高可用性本意。另外更重要的是Redis是一个内存数据结构模型,所有的优势都是建立在对内存复杂数据结构高效的原子操作上,这样就看出aof是一个非常不协调的部分。

其实aof目的主要是数据可靠性及高可用性,在Redis中有另外一种方法来达到目的:Replication。由于Redis的高性能,复制基本没有延迟。这样达到了防止单点故障及实现了高可用。

小结

要想成功使用一种产品,我们需要深入了解它的特性。Redis性能突出,如果能够熟练的驾驭,对国内很多大型应用具有很大帮助。希望更多同行加入到Redis使用及代码研究行列。

参考文献

  1. On Designing and Deploying Internet-Scale Service(PDF)
  2. Facebook’s New Real-Time Messaging System: HBase To Store 135+ Billion Messages A Month
  3. What’s wrong with 1975 programming
  4. Linux epoll is now supported(Google Groups)
  5. CAS and why I don’t want to add it to Redis(Google Groups)
  6. Plans for Virtual Memory(Google Groups)
  7. Full of keys(Salvatore antirez Sanfilippo)

原文地址:http://timyang.net/data/redis-misunderstanding/

Linux下建立MySQL镜像数据库

星期六, 六月 11th, 2011

MySQL 版本:4.1

环境介绍:主库 192.168.0.205 从库 192.168.0.206

1、主库创建/etc/my.cnf,修改[mysqld]里边的键值打开 master项中

server-id=1

log-bin

2、主库增加用户,用于从库读取主库日志。

grant replication slave,reload,super on *.* to‘slave’@‘192.168.0.206’identified by ‘123456’

3、从库连接主库进行测试。

/opt/mysql/bin/mysql -u slave -p -h 192.168.0.205

4、停从库,修改从库/etc/my.cnf,增加选项:

[mysqld]

server-id=2

master-host=192.168.0.205

master-user=slave

master-password=123456

5、启动从库,进行主从库数据同步

/opt/mysql/share/mysql/mysql start

/opt/mysql/bin/mysql -u root -p

mysql>load data from master;

说明:这一步也可以用数据库倒入或者直接目录考过来。

6、进行测试:

主库创建表,

mysql>create database sampdb;

mysql>create table new (name char(20),phone char(20));

mysql>insert into new (‘abc,’0532555555‘);

打开从库,察看:

/opt/mysql/bin/mysql -u root -p

mysql>show database;

mysql

sampdb

test

mysql>use sampdb;

mysql>show tables;

new

说明主从数据库创建成功。

7、主从数据库相关命令:

slave stop; slave start ; 开始停止从数据库。

show slave status\G; 显示从库正读取哪一个主数据库二进制日志。

purge master logs to ‘binlog.000004’; 此命令非常小心,删除主数据库没用的二进制日志文件。如果误删除,那么从库就没有办法自动更新了。

change master; 从服务器上修改参数使用。

 

MySQL中的模式匹配(标准SQL匹配和正则表达式匹配)

星期五, 二月 25th, 2011

MySQL中的模式匹配(标准SQL匹配和正则表达式匹配)

一、 标准的SQL模式匹配:

1. 使用LIKE和NOT LIKE比较操作符(注意不能使用=或!=);

2. 模式默认是忽略大小写的;

3. 允许使用”_”匹配任何单个字符,”%”匹配任意数目字符(包括零字符);

二、 MySQL还提供象UNIX实用程序的扩展正则表达式模式匹配的格式:

1. 使用REGEXP和NOT REGEXP操作符(或RLIKE和NOT RLIKE,他们是同义词);

2. REGEXP模式匹配与被匹配字符的任何地方匹配,则匹配成功(即只要被匹配字符包含或者可以等于所定义的模式,就匹配成功);

不同于LIKE模式匹配,只有和整个值匹配,才匹配成功(即只有被匹配字符完全和所定义的模式匹配,才匹配成功)

3. REGEXP默认也是不区分大小写,可以使用BINARY关键词强制区分大小写;

如:SELECT * FROM pet WHERE name REGEXP BINARY ‘^B’;

4. 正则表达式为一个表达式,它能够描述一组字符串。REGEXP操作符完成MySQL的扩展正则表达式匹配。REGEXP实现的功能是如果被匹配字符中部分或完全符合所定义的表达式描述的字符,则表示匹配成功。

1)最简单的正则表达式是不含任何特殊字符的正则表达式,如hello。

SELECT * FROM pet WHERE name REGEXP ‘hello’;表示的意思是如果name这列的某一行包含hello这个单词,则匹配就成功了。(注意和LIKE的区别,LIKE要求name这列的某一行必须完全等于hello,才匹配成功)。

2)非平凡的正则表达式,除了含有最简单表达式那些东西,还需要采用特殊的特殊结构,用到的字符,往下看。(因此,通常的正则表达式是普通单词和这些正则表达式字符构成的表达式)

5. 扩展正则表达式的一些字符:

1) ‘.’匹配任何单个字符;

 

2) […]匹配在方括号内的任何字符,可以使用’-’表示范围,如[a-z],[0-9],而且可以混合[a-dXYZ]表示匹配a,b,c,d,X,Y,Z中的任何一个;(注意使用括号以及’|’的方法也可以达到相同的效果,如(a|b|c)匹配a,b,c中的任何一个);此外可以使用’^’表示否定,如[^a-z]表示不含有a-z中间的任何一个字符;

 

3) ‘*’表示匹配0个或多个在它前面的字符。如x*表示0个或多个x字符,.*表示匹配任何数量的任何字符;

 

4) 可以将模式定位必须匹配被匹配字符的开始或结尾,在匹配模式前加”^”:表示匹配从被匹配字符的最开头开始,在匹配模式后加”$”:表示匹配要进行到被匹配字符的最末尾。

 

5) ‘+’表示匹配1个或多个在它前面的字符。如a+表示1个或多个a字符。

 

6) ‘?’表示匹配0个或1个在它前面的字符。如a?表示0个或1个a字符。

 

7) ‘|’如de|abc表示匹配序列de或者abc。注意虽然[…]也可以表示匹配中的某一个,但是每次仅仅能表示单个字符及[a-bXYZ]实际每一次只代表了一个字符。

 

8) ()括号可以应用在表达式中,使得更容易理解。

 

9) a{5}表示匹配共5个a,a{2,8}表示匹配2~8个a。

a*可以写成a{0, } 第二个参数省略表示没有上界;a+可以写成a{1,};a?可以写成a{0,1}

更准确地讲,a{n}与a的n个实例准确匹配。a{n,}匹配a的n个或更多实例。a{m,n}匹配a的m~n个实例,包含m和n
m和n必须位于0~RE_DUP_MAX(默认为255)的范围内,包含0和RE_DUP_MAX。如果同时给定了m和n,m必须小于或等于n。

10) 标准类别[:character_class:]:

常用的一些标准类别,一般在[]中使用,由于用在[]中故和[a-z]类似,每一次只能顶替一个字符。(这个有点类似perl里面定义的常用的一些标准类别:\w表示一个单词字符即[a-zA-Z0-9];\W一个非单词字符与\w相反; \d一个数字即[0-9];\D一个非数字;\s一个白空间字符即[\t\f\r\n];\f为换页符;\S一个非白空间字符)

标准的类别名称:

alnum 文字数字字符
alpha 文字字符
blank 空白字符
cntrl 控制字符
digit 数字字符
graph 图形字符
lower 小写文字字符
print 图形或空格字符
punct 标点字符
space 空格、制表符、新行、和回车
upper 大写文字字符
xdigit 十六进制数字字符

使用实例:

SELECT 'justalnums' REGEXP '[[:alnum:]]+';

解释其中[[:alnum:]]由于[:alnum:]表示文字数字字符,它又用在[]中,故[[:alnum:]]代表一个字符它为一个文字或者数字。后面的+号表示1个或多个这样的文字或数字。

上述语句返回1.那是因为justalnums中是由字母组成的。

11)字边界:[[:<:]]表示开始,[[:>:]]表示结束:

其定义了一个单词的开始和结束边界,这个单词为字字符,这样[[:<:]]代表这个字字符前面的部分,[[:>:]]代表这个字字符后面的部分。字字符为alnum类的字母数字字符或下划线(_);因此[[:<:]], [[:>:]]均代表不是字字符的字符,即只要不是字母数字字符以及下划线(_)即可。因此其可以为什么都不是。因此[[:<:]]word[[:>:]]能够匹配如下的所有情况:

即word单词本身,word*** 解释***代表不是字母数字以及_的任何字符(如,word-net);***word(如,micorsoft word);***word***(如,this is a word program.)

举例:[[:<:]]word[[:>:]]:

SELECT 'a word a' REGEXP '[[:<:]]word[[:>:]]'; 结果为真
SELECT 'a xword a' REGEXP '[[:<:]]word[[:>:]]'; 结果为假

最后注意的注意:
要在正则表达式中使用特殊字符,需要在这些字符前面添加2个反斜杠’\’,
举例:
SELECT '1+2' REGEXP '1+2'; 结果为0
SELECT '1+2' REGEXP '1\+2'; 结果为0
SELECT '1+2' REGEXP '1\+2'; 结果为1
解释:
这是因为MySQL解析程序解析该SQL语句时:
首先将字符串’1\+2’解析为1\+2;
然后把1\+2当作正则表达式,由正则表达式库来解析,它代表1+2。
因此需要加上2个反斜杠。

不要经常犯加一个反斜杠的错误,加一个反斜杠会莫名其妙:
SELECT '1t2' REGEXP '1\t2';
结果会返回1
本来的意思是匹配1制表符\t以及2,但是由于只添加了一个\所以,解析以后编程了1t2,所以匹配成功。
12)
[.characters.]和[=character_class=]