上一篇文章里我简述了使用Keys作为Redis搜索的方式,确实感受到了社区的力量,写文章好处多。首先谢谢各位前辈的指导,我知道了拿Redis作为搜索是个错误的方向。本来这篇文章我觉得确实没必要发了,但是想想既然错了,那就将错就错,写出来给初学者一些思考吧。

本篇我将会讲讲,分词建立key索引和redis scan命令两种方式。

注意:这两种方式的搜索也不一定可行,具体场景要具体测试衡量,拿Redis做搜索要深思熟虑并且测试,甚至是要直接回避的。

另外,上篇评论也建议大家看一看,前辈们给了很多经验总结,有一些同学可能没明白。这些点我先整理下:

1. 我采用了StactkExchange.Redis,而不是ServiceStack.Redis。对于后者我觉得是个好工具,但是4.0开始收费了,3.9功能不是特别全,一些地方存在不足。

2. 有同学建议GetAll之类的方式,我觉得对于缓存应该还是不要StringSet(list)\StringGet(list)的方式吧,毕竟数据量大了,序列化反序列化就费时。这点不知道大家怎么看?我个人觉得每条记录应该是一个key-value,这个value应该是避免存成整个集合的,否则效率何在?

3. 上一篇中的Keys模糊匹配,请大家在实际运用的时候忽略掉。因为Keys会引发Redis锁,并且增加Redis的CPU占用,情况是很恶劣的。

分词索引法

这种方式是我实践过后,结合上篇的前辈给的观点觉得唯一比较可行且符合redis特性的方式,不过最终效率上还是比不过内存。

详细的实现思路清看Redis作者博客(参考资料1),这里的例子还是基于UserName,英文,并且只针对词组做了长度为3的分词,其他场景请自行扩展。

首先基于AutoComplete的字母搜索,那么我们需要对所有的Name做一个分词,即:

abc => (a, ab, abc)

形成一个Set的集合形式:

那么输入a,我们就直接取set a里的内容,输入ab就直接取ab集合的内容。那么我们开始转换,首先我们需要对User表的姓名进行分词:

var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase(); for (var i = ; i < ; i++)
{
var data = dbCon.Lookup<string, int>(string.Format(@"select words, id from (
select Row_number() over (partition by words order by name) as rn,id,words from (
select id, SUBSTRING(name, 1, {0}) as words, name from User
) as t
) t2 where rn <= {1} and words != '' and words is not null", i, )); data.ForEach((key, item) =>
{
db.SetAdd("capqueen:Cache:user:" + key.ToLower(), item.Select<int, RedisValue>(j => j).ToArray());
});
}

第一步:采用SQL,分组排序筛选出每个分词的前20条数据,这里使用的是OrmLite的语法。

第二部:存入RedisSet,注意这里其实只是做了一个索引,并不保存具体的User内容,效果如下:

接着搜索的时候我们可以实现如下:

public List<User> SearchWords(string keywords)
{
var redis = ConnectionMultiplexer.Connect("localhost");
var db = redis.GetDatabase();
var result = db.SetMembers("capqueen:Cache:user:" + keywords.ToLower());
var users = new List<User>(); if (result.Any())
{
//转换成ids
var ids = result.ToList().Select<RedisValue, RedisKey>(i => i.ToString());
//按照keys获取value ,事先已经存好了Users
var values = db.StringGet(ids.ToArray()); //构造List Json以加速解析
var portsJson = new StringBuilder("["); values.ToList().ForEach(item =>
{
if (!string.IsNullOrWhiteSpace(item))
{
portsJson.Append(item).Append(",");
}
}); portsJson.Append("]"); users = JsonConvert.DeserializeObject<List<User>>(portsJson.ToString());
}
}

经过实际的测试,这样的写法比前面的Keys确实好了不少,但是性能还是差强人意的。

Scan搜索法

这种方法是我在查阅了Redis的文档之后,发现的,但是也就是试验一下,估计也不能用做生产环境大规模查询。

Scan根据数据结构的不同分为了SCAN\HSCAN\SSCAN\ZSCAN,具体的信息请看文档。我们这里采用了ZSCAN:

ZSCAN key cursor [MATCH pattern] [COUNT count]

这里cursor是搜索的迭代的一个游标,具体还没弄明白,pattern就是匹配规则 count就是记录条数

由于我使用的是StackExchange.Redis,它提供的zscan方法是:

IEnumerable SortedSetScan(RedisKey key, RedisValue pattern = null, int pageSize = 10, long cursor = 0, int pageOffset = 0, CommandFlags flags = CommandFlags.None);

用过之后,我发现了这里的pageSize/pageOffset貌似没有效果,为此我还特地上github为作者留了言,他给我一些解释:

https://github.com/StackExchang, 我的英语比较差,请凑合看。

public void CreateTerminalCache(List<User> users)
{
if (users == null) return; var db = ConnectionMultiplexer.GetDatabase(); var sourceData = new List<KeyValuePair<RedisKey, RedisValue>>();
//构造集合数据
var list = users.Select(item =>
{
var value = JsonConvert.SerializeObject(item);
//构造原始数据
sourceData.Add(new KeyValuePair<RedisKey, RedisValue>("capqueen:users:" + item.Id, value)); //构造数据
return new SortedSetEntry(item.Name, item.Id);
}); //添加进有序集合,采用name - id
db.SortedSetAdd("capqueen:users:index", list.ToArray()); //添加港口数据key-value
db.StringSet(sourceData.ToArray(), When.Always, CommandFlags.None);
}

然后搜索的时候如下:

public List<User> GetUserByWord(string words)
{ var db = ConnectionMultiplexer.GetDatabase(); //搜索
var result = db.SortedSetScan("capqueen:users:index", words + "*", , , , CommandFlags.None).Take().ToList(); var users = new List<User>(); if (result.Any())
{
//转换成ids
var ids = result.ToList().Select<SortedSetEntry, RedisKey>(i => i.ToString()); //按照keys获取value
var values = db.StringGet(ids.ToArray()); //构造List Json以加速解析
var portsJson = new StringBuilder("["); values.ToList().ForEach(item =>
{
if (!string.IsNullOrWhiteSpace(item))
{
portsJson.Append(item).Append(",");
}
}); portsJson.Append("]"); users = JsonConvert.DeserializeObject<List<User>>(portsJson.ToString());
} return users;
}

总结

总的来说,通过这么一些列的研究和前辈们的指导,我对Redis有了一些了解。AutoComplete的场景是真的不适合使用Redis,可以说目前Redis用来做一些搜索可能还早,期待以后会有相关功能吧。上一篇文章里,有些前辈给的 意见很好,希望大家也可以学习一下。

  1. 分级缓存,该到内存的还是应该保存到appServer的内存,redis只是集中式缓存的一步。
  2. 多增加一个数据服务器,几种提供数据服务,这样可以把一些缓存直接统一到这个机器来做。链接
  3. 感谢前辈们的留言,尤其感谢@雷兽 前辈等

参考资料

  1. Redis作者博客,这是其中一篇讲如何基于Redis实现AutoComplete的文章:http://oldblog.antirez.com/post/autocomplete-with-redis.html
  2. Redis 第三方管理工具 For Windows:http://redisdesktop.com/
  3. Redis .NET链接工具的Top20:http://nugetmusthaves.com/Tag/Redis
  4. Redis命令中文文档:http://redisdoc.com/
  5. 知乎上的一个讨论:http://www.zhihu.com/question/19764056

Redis到底该如何利用(二)?的更多相关文章

  1. Redis到底该如何利用?

    Redis是个好东西,经过上两个星期的研究和实践,目前正在项目里大规模的替换掉原来的本地内存cache.但是替换过程中却发现,Redis这东西高端,大气上档次,似乎不是我想象里的使用方法. 在没有深入 ...

  2. Redis到底该如何利用?【转自:http://www.cnblogs.com/capqueen/p/HowToUseRedis.html】

    Redis是个好东西,经过上两个星期的研究和实践,目前正在项目里大规模的替换掉原来的本地内存cache.但是替换过程中却发现,Redis这东西高端,大气上档次,似乎不是我想象里的使用方法. 在没有深入 ...

  3. Redis到底该如何利用(三)?

    上两篇受益匪浅,秉着趁热打铁,不挖到最深不罢休的精神,我决定追加这篇.上一篇里最后我有提到实现分级缓存管理应该是个可行的方案,因此今天特别实践了一下.不过缓存分级之后也发现了一些问题,例如下图: 当a ...

  4. Redis源码阅读(二)高可用设计——复制

    Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...

  5. Redis07——Redis到底能用在什么地方(下)

    在前一篇文章中,我们已经介绍过Redis的一些实际应用.如KV缓存.分布式锁.消息队列,由于篇幅原因,并未介绍完全.接下来将继续为各位带来Redis的更多应用. bitmat(位图) 实现 位图的基本 ...

  6. redis成长之路——(二)

    redis操作封装 针对这些常用结构,StackExchange.Redis已经做了一些封装,不过在实际应用场景中还必须添加一些功能,例如重试等 所以对一些常功能做了一些自行封装SERedisOper ...

  7. Android 利用二次贝塞尔曲线模仿购物车加入物品抛物线动画

    Android 利用二次贝塞尔曲线模仿购物车加入物品抛物线动画 0.首先.先给出一张效果gif图. 1.贝塞尔曲线原理及相关公式參考:http://www.jianshu.com/p/c0d7ad79 ...

  8. Redis指令与数据结构(二)

    0.Redis目录结构 1)Redis介绍及部署在CentOS7上(一) 2)Redis指令与数据结构(二) 3)Redis客户端连接以及持久化数据(三) 4)Redis高可用之主从复制实践(四) 5 ...

  9. PHP利用二叉堆实现TopK-算法的方法详解

    前言 在以往工作或者面试的时候常会碰到一个问题,如何实现海量TopN,就是在一个非常大的结果集里面快速找到最大的前10或前100个数,同时要保证 内存和速度的效率,我们可能第一个想法就是利用排序,然后 ...

随机推荐

  1. 架构师养成记--3.synchronized细节问题

    一.synchronized有锁重入的特点,某个线程得到对象的锁后,再次请求此对象可以再次得到改对象的锁.如下示例,在method1中调用method2,在method2中调用method3,而met ...

  2. hosts manager——hosts配置管理工具

    引言 做web开发相关的经常会用到hosts修改的功能,各个平台好像都有hosts 配置GUI(据我所知windows有.MAC OX也有),但是命令行配置hosts的好像还没有,命令行配置的话有几个 ...

  3. 时间戳 时区 java mysql

    当一个时间 比如2016年5月6日,生成时间戳.这个运算是与时区有关的.首先得确认这个时间是哪个时区的,然后转换成utc时区的时间.再减去1970,得到的秒数,就是时间戳. 时间戳是个一定的值,他与时 ...

  4. BZOJ3331: [BeiJing2013]压力

    传送门 Tarjan的三大应用之一:求解点双联通分量. 求解点双联通分量.然后缩点,差分优化即可. //BZOJ 3331 //by Cydiater //2016.10.29 #include &l ...

  5. 用 Python、 RabbitMQ 和 Nameko 实现微服务

    用 Python. RabbitMQ 和 Nameko 实现微服务 原创 07-17 17:57 首页 Linux中国 "微服务是一股新浪潮" - 现如今,将项目拆分成多个独立的. ...

  6. 【原】十张javascript思维导图

    下面附上十张javascript思维导图,如果把里面的内容全部掌握,那就是高手了.思维导图提供给我们一个整体的知识体系,也可以说是一种工具,废话不多说,上图: 1.javascript数据类型 2.j ...

  7. 图解c/c++多级指针与“多维”数组

    声明:本文为原创博文,如有转载,请注明出处.若本文有编辑错误.概念错误或者逻辑错误,请予以指正,谢谢. 指针与数组是C/C++编程中非常重要的元素,同时也是较难以理解的.其中,多级指针与“多维”数组更 ...

  8. apache启动出错原因举例

    这是我这两天频繁遇到的问题.Apache服务器还真是问题少年!任何点改动都可能导致它无法使用. 原因一:80端口占用例如IIS,另外就是迅雷.我的apache服务器就是被迅雷害得无法启用! 原因二:软 ...

  9. ubuntu 系统使用

    1.ubuntu的鼠标,用起来总是感觉比windows的快一点儿,可以用以下命令来调整为默认的 root@admin-pc:~$ xset m default 2.mysql默认不允许远程连接,可以在 ...

  10. Holt-Winters原理和初始值的确定

      关于模型 (来自以下PPT,从第4页开始)   关于初始值: 以下文档给出了三个模型的初始值计算的思路. 大致思路如下,建立一个p阶移动平均模型,估计出参数即为初始值,具体的根据三种不同的模型,有 ...