存档

‘redis’ 分类的存档

在redis中对不同集合类型进行sort操作的性能粗测

2011年1月21日 wenhui 没有评论

由于考虑到网站运行后期的数据量比较大,使用redis的sort指令来获取查询结果的性能问题(见文章尾部题设1),在系统设计之初就得确定好哪一种集合类型的数据进行sort操作的速度最快.

以下测试均在虚拟机中完成,虚拟机ram为384M

在这里测试的数据类型:list/set/sorted set
测试方法: 对list键lpush 100w条数据 (1~1024*1024的整数)
对set键sadd 100w条数据(1~1024*1024的整数)
对sorted set键zadd 100w条数据(value为1~1024*1024的整数,score与value相同)

使用 sort {$keyname} limit 0 10 来测试sort的大致时间
采用python脚本粗测执行时间的结果为(多次测试的结果求平均值)

list                1.52050209045
set                1.97175097466
sorted set       2.02180504799

对于结果我表示纠结,sorted set反而最慢,预计对于所有的元素都重新进行了sort操作,而没有直接采用已经排好的结果
另:在这个测试之前做了个对1000w条数据的排序,虚拟机中的redis-server直接被block不给我响应了(内存太小)..

题设1:
我们所采用的开发环境为PHP+Redis,采用phpredis扩展作为php连redis的driver
假设我们的数据如下
用一个set(或list或sorted set)储存所有的照片的id(key定位photo:set),然后想提取特定数量的照片的基本信息(采用hash数据类型,假定内容为array(‘name’=>{$photoname},’addtime’=>{$nowtime}),key定为photo:{$photoid})
假设要求获取最新100张照片的名字
如果是set或sorted set,只有一种方式
$r->sort(“photo:set”,array(‘get’=>’name’,'sort’=>’desc’,'limit’=>array(0,10)));
如果是list那就有两种方式
1)$r->sort(“photo:set”,array(‘get’=>’photo:*->name’,'sort’=>’desc’,'limit’=>array(0,10)));
2)$photos = $r->lGetRange(“photo:set”,array(‘limit’=>array(0,100))); //这条速度块
forearch($photos as $photo){ //这里就不快了,这里的pipeline批量执行未测
$r->hGet(“photo:{$}”,”name”);
}
显然list的第二种方式的处理速度比较慢,它有100次请求(第一种只有一次请求),redis的内部处理速度显然快于外部的请求速度.
在这种情况下我们要做的就是对 list的sort方式及set的sort方式进行速度对比.

分类: redis 标签: , , , ,

用redis+swcs实现的简单搜索引擎

2011年1月10日 wenhui 没有评论

在上一篇文章:搜索引擎的多重分词算法及分词词典更新时对索引的重新生成的简单想法 中,我用十分纠结的语言描述了一种基于redis+swcs实现简单搜索引擎的思路,今天就做了一个简单的demo,测试了下搜索的可行性.
悲剧,下面的代码被wordpress过滤的很严重,大家先将就着看,我研究研究

首先是分词及索引:

//以下代码的使用前提是已经安装了scws的php扩展和phpredis扩展
//scws init
$nowtime =time(); //获取当前的系统时间,搜索引擎已内容的发表时间作为搜索结果的排序权重
$cws = scws_new();
$cws->set_charset('utf8'); //注意!!!!redis良好支持utf8!!!!!!gbk编码未测!!!!
$multi = 8;
$cws->set_duality(false); //是否开启二元分词
$cws->set_ignore(true); //是否忽略标点
$cws->set_multi($multi); //设置多元分词模式,8表示将句子拆成单字,返回的分词结果为"正常分词"+"所有单字"
//假设"魔兽争霸"的正常分词结果为"魔兽"和"争霸",它的单字为"魔","兽","争","霸"
//那么将multi设为8的时候的结果就是"魔兽","争霸","魔","兽","争","霸"
//更详细的内容参加scws的文档
//end
$r = Redis();
$r->connect('127.0.0.1');
$r->select(15);//用redis第15号数据库作为测试数据库
$postid = $r->incr("global:nextPostId"); //获取当前数据的id,很redis的写法
$post = $_POST['content'];
$r->set("post:{$postid}",$post); //$post中保存为获取到的内容

//word split and index
$cws->send_text($post);      //将$post中的内容发往scws分词程序
while ($res = $cws->get_result()){    //scws的结果并非一次性返回,而是分多次,若取到的结果为空则表示分词完毕
    foreach ($res as $tmp)
    {
        if ($tmp['len'] == 1 && $tmp['word'] == "\r"){
            continue;
        }elseif ($tmp['len'] == 1 && $tmp['word'] == "\n"){
            continue;
        }else{
            $r->zAdd("index:time:{$tmp['word']}",$nowtime,$postid);  //将分好的词作为索引的key,时间作为权重,$postid作为zset的内容
//在这里我采用zset(sorted sets)来保存索引的内容,方便对搜索结果进行排行
        }
    }
}
$cws->close();
//end

然后是搜索部分:

$r = Redis();  //初始化redis
$r->connect('127.0.0.1');
$r->select(15);

$keyword = $_GET['k'];  //获取搜索的关键字

// scws init
$nowtime = time();
$cws = scws_new();
$cws->set_charset('utf8');
$cws->set_duality(false);
$cws->set_ignore(true);
$cws->set_multi(0);   //这里的0表示,只返回正常的分词结果
//end

//word split and search
$cws->send_text($keyword);   //对关键字进行分词
$keyarray = array();        //用于保存用于查询的key的数组,这么做的原因是phpredis扩展在使用zinter来判断zset的相同内容时所传递的参数为key的数组
while ($res = $cws->get_result()){
    foreach ($res as $tmp)
    {
        if ($tmp['len'] == 1 && $tmp['word'] == "\r"){
            continue;
        }elseif ($tmp['len'] == 1 && $tmp['word'] == "\n"){
            continue;
        }else{
            $keyarray[] = "index:time:{$tmp['word']}";  //将需要查询的key放入数组
        }
    }
}
$cws->close();
$tmpkeyname = "tmpkey:{$nowtime}";  //用于保存zinter命令产生的临时搜索结果列表
//redis的另一个好处就是,你可以很方便的给搜索结果做缓存
$r->zInter($tmpkeyname ,$keyarray);  //获取搜索结果
//这里我们还是以"魔兽争霸"做例子,"魔兽争霸"被分词为"魔兽"和"争霸"
//那么在$keyarray中保存的内容就应该是 array("index:time:魔兽","index:time:争霸")
//zInter所返回的结果,就是post内容中既包含"魔兽",又包含"争霸"的$postid号,继续看下面
$data = $r->sort($tmpkeyname,array('get'=>"post:*"));//通过sort指令对$tmpkeyname中的$postid数据按score(这里是时间)排序
//并根据排序结果get到post的内容 参见redis 的sort指令
foreach($data as $d){   //输出结果
  echo "{$d}";
}

$r->delete($tmpkeyname );   //删除临时搜索结果,此例子没有用到缓存
//end

此代码仅仅实现了一个很简单的索引,和搜索功能,不过值得惊叹的是,就这么几十行代码就能实现一个简单的搜索引擎.
文章写的不是很清楚,各位看官有什么不清楚的地方可以跟我留言交流.谢谢:-)

搜索引擎的多重分词算法及分词词典更新时对索引的重新生成的简单想法

2011年1月9日 wenhui 没有评论

由于最近的项目中需要用到搜索引擎,由于之前采用的是mysql数据库,而且在对索引没什么要求的情况下,就直接采用的mysql的fulltext索引.

不过最近开始考虑到尝试新技术和对系统性能的担心,在系统中引入redis(一个性能及功能都十分强劲的NoSQL数据库),来承担一部分比较适合NoSQL数据库做的工作,如排行榜/关注列表等.

恰恰不小心看到redis的一个做搜索引擎的非常简单的思路(英文索引,参考:http://playnice.ly/blog/2010/05/05/a-fast-fuzzy-full-text-index-using-redis/),便留意起了在redis上实现一套简单的中文搜索引擎.

说道做搜索引擎就不得不提到两点十分关键的内容:一是分词,就是将一个句子拆成不同的词和字;二是索引(及排行),用于快速的定位到需要的内容.

在这里主要讲下我对分词和简单索引建立的一点思路:

分词方面
分词有现成的工具可以用 scws,一个中文分词系统,可以作为php扩展,自带的词库就有28W词量,准确率高达95%.
不过分词有一个很大的不确定性,比如说”魔兽争霸”这个词,它可以作为一个单独的词表示一款游戏,但是很多人把它直接简称为”魔兽”,这就导致了在分词的时候,必须将”魔兽争霸”分为不同的单词组合如(“魔兽争霸”)和(“魔兽”,”争霸”)这两种可能,而这这就意味着必须进行两次分词,来保证搜索结果的准确性.感谢scws的作者hightman,他已经实现了不同级别的分词算法,我们用起来就简单很多了.

索引重新建立方面
比如我之前不知道有”魔兽争霸”这么个词,然后很利索地把它给拆成了”魔兽”和”争霸”,于是乎我搜”魔兽争霸”的结果很有可能是”魔兽们平时都以争霸为乐趣”,这显然有点不靠谱.因此当我知道一个新的词的时候,需要对原有的索引进行一次重构,将原先即包含”魔兽”也包含”争霸”的句子提取出来,判断是否包含词组”魔兽争霸”,然后丢到索引”魔兽争霸”里面.

代码见这里:用redis+swcs实现的简单搜索引擎

[原创]redis将slave server转换为master server

2010年12月23日 wenhui 没有评论

假设当前有一台master server(配置较低)和一台slave server(配置较高),当你需要垂直扩容将原有的硬件配置较低的master server替换掉,而且要求不能停止服务.
可以这样干,在slave server上执行”SLAVEOF NO ONE”,将slave server转换为master server.
再将master server停掉,用一台配置较高的新server作为新的slave server~

分类: redis, 挨踢 标签:

[转]对redis sort 实现的排行的压力测试(与mysql对比)

2010年12月20日 wenhui 没有评论

文章来源:http://hi.baidu.com/icej/blog/item/86f9a77e4c2eda320dd7dac7.html

补充一句:Redis在Nginx上还有module支持

http://people.freebsd.org/~osa/ngx_http_redis-0.2.0.tar.gz

Redis官方压力测试数据http://code.google.com/p/redis/wiki/Benchmarks
由于我计划应用到排行榜功能是,功能需求:根据指定uid找出该用户的所有好友积分,并根据积分输出该用户和好友积分的排行榜,及该用户在排行榜中的位置
一般的Mysql设计方式
user表
uid
name
sort 积分
好友关系flist表
uid 用户
fuid 好友

用户加积分的时候需要update user set sort=100 where uid=1
需要获得排行榜的时候需要先获得select uid from flist where uid=1,获得好友关系,在组合成uid列表执行
select name,sort from test_user where uid in(uid1,uid2,uid3) order by sort desc获得排行榜

使用Redis操作这个事就简单了。
每次有新用户玩游戏先注册用户信息 set uid:1 {“uid”:1,”name”:tom}
每次游戏积分更新就操作set uid:sort:1 100
设置好友关系时操作 sadd uid:flist:1 uid2
sadd uid:flist:1 uid3
获得某个用户好友排行榜sort uid:flist:1 by uid:sort:* get uid:*
如果需要将自己的用户数据也做排行榜,则需要在好友关系中要加入自己的uid: sadd uid:flist:1 uid1

我对Mysql和Redis基于这个场景应用做了压力测试
测试并发时的性能.测试方法,5个前端机各启动一定数量PHP进程连接目标服务器执行测试项目,产生并发效果:
Redis 1000个并发持续向Redis增加100W用户数据平均每次set耗时0.12 s。条数据长度30-40字符。

1000个并发对100W用户,每个用户拥有100个好友,共计10KW的数据,根据uid排序好友积分平均0.07s

由于Redis基本都是内存操作,在配置时有设置操作多少次后flush到本地,压力测试cpu load average都非常低,内存占用都是存储使用,基本这个机器跑Redis很闲..

同样业务场合100W用户,累计好友关系数据量500W,根据uid排序好友积分平均:0.2s。

差了一个0。1000个并发持续操作Mysql….cpu狂高…IO也增长,load average也高…三高…

我使用的Redis php api是php实现的,php module的执行一会就会提示连接异常,还没了解是什么问题。
php-redis的redis.php 需要自行增加个方法,发送sort命令,redis.php里没有增加sort操作,也没有disconnect方法..需要实战的话建议自行先加上了。
public function get_sort($key)
{
$response = $this->execute_command( “SORT {$key}” );
$count = (int)substr($response, 1);
$list = array();
for ( $i = 0; $i get_response(), 1);
$value = $this->get_response();
$list[] = $this->unpack_value($value);
}
return $list;
}

分类: redis, 挨踢 标签: , , ,

[转]用redis sort操作实现排行榜

2010年12月20日 wenhui 没有评论

文章来源:http://hi.baidu.com/icej/blog/item/b1286a1ed5d8a4fe1ad576d3.html

http://code.google.com/p/redis/

redis相传性能不错,不仅结合了key value数据库的优点,又有满足一些必要的关系型数据库的功能。
我们有个产品最近想为每个用户出排行榜,APP,SNS产品常见功能,每个用户与好友做排行榜排序……无聊又必备功能。

还没测试redis性能。但功能是可以实现的。还挺不错的….稍后会测试测试性能,和PHP的API 要是不错….那可真是神物啊。

做个示例:

[root@xpdmediainc04 redis-1.02]# ./redis-cli sadd tom:friend:list 123 #tom的好友列表 里面是好友的uid
1
[root@xpdmediainc04 redis-1.02]# ./redis-cli sadd tom:friend:list 456
1
[root@xpdmediainc04 redis-1.02]# ./redis-cli sadd tom:friend:list 789
1
[root@xpdmediainc04 redis-1.02]# ./redis-cli sadd tom:friend:list 101
1
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:sort:123 1000 #uid对应的成绩
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:sort:456 6000
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:sort:789 100
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:sort:101 5999
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:123 “{‘uid’:123,’name’:'lucy’}” #增加uid对应好友信息
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:456 “{‘uid’:456,’name’:'jack’}”
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:789 “{‘uid’:789,’name’:'marry’}”
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli set uid:101 “{‘uid’:101,’name’:'icej’}”
OK
[root@xpdmediainc04 redis-1.02]# ./redis-cli sort tom:friend:list by uid:sort:* get uid:* #从好友列表中获得id与uid:sort字段匹配后排序,并根据排序后的顺序,用key在uid表获得信息
1. {‘uid’:789,’name’:'marry’}
2. {‘uid’:123,’name’:'lucy’}
3. {‘uid’:101,’name’:'icej’}
4. {‘uid’:456,’name’:'jack’}
[root@xpdmediainc04 redis-1.02]# ./redis-cli sort tom:friend:list by uid:sort:* get uid:* get uid:sort:*
1. {‘uid’:789,’name’:'marry’}
2. 100
3. {‘uid’:123,’name’:'lucy’}
4. 1000
5. {‘uid’:101,’name’:'icej’}
6. 5999
7. {‘uid’:456,’name’:'jack’}
8. 6000

分类: redis, 挨踢 标签: