存档

作者存档

使用Cacti监控MongoDB和Redis

2020年4月17日 没有评论

Cacti是一套基于PHP,MySQL,SNMP及RRDTool开发的网络流量监测图形分析工具。被广泛的用于对服务器的运维监控中,Cacti提供了一种插件式的管理,只要按要求写好特定的模板,那么你就可以对任何服务进行流量监控。本文就是要为大家介绍两个模板,分别是MongoDB和Redis的Cacti模板,使用它,你可以对你的MongoDB和Redis服务进行流量监控。

Redis template

MongoDB template

分类: redis头条 标签:

Redis复制与可扩展集群搭建

2020年4月17日 没有评论

Redis的主从复制策略是通过其持久化的rdb文件来实现的,其过程是先dump出rdb文件,将rdb文件全量传输给slave,然后再将dump后的操作实时同步到slave中。下面是一篇介绍Redis复制原理的文章,文章作者为新浪微博的田琪同学(@摇摆巴赫)。

本文会讨论一下Redis的复制功能以及Redis复制机制本身的优缺点以及集群搭建问题。

Redis复制流程概述

Redis的复制功能是完全建立在之前我们讨论过的基于内存快照的持久化策略基础上的,也就是说无论你的持久化策略选择的是什么,只要用到了Redis的复制功能,就一定会有内存快照发生,那么首先要注意你的系统内存容量规划,原因可以参考我上一篇文章中提到的Redis磁盘IO问题。

Redis复制流程在Slave和Master端各自是一套状态机流转,涉及的状态信息是:

Slave 端:

REDIS_REPL_NONE
REDIS_REPL_CONNECT
REDIS_REPL_CONNECTED

Master端:

REDIS_REPL_WAIT_BGSAVE_START
REDIS_REPL_WAIT_BGSAVE_END
REDIS_REPL_SEND_BULK
REDIS_REPL_ONLINE

整个状态机流程过程如下:

  • 1.Slave端在配置文件中添加了slave of指令,于是Slave启动时读取配置文件,初始状态为REDIS_REPL_CONNECT。
  • 2.Slave端在定时任务serverCron(Redis内部的定时器触发事件)中连接Master,发送sync命令,然后阻塞等待master发送回其内存快照文件(最新版的Redis已经不需要让Slave阻塞)。
  • 3.Master端收到sync命令简单判断是否有正在进行的内存快照子进程,没有则立即开始内存快照,有则等待其结束,当快照完成后会将该文件发送给Slave端。
  • 4.Slave端接收Master发来的内存快照文件,保存到本地,待接收完成后,清空内存表,重新读取Master发来的内存快照文件,重建整个内存表数据结构,并最终状态置位为 REDIS_REPL_CONNECTED状态,Slave状态机流转完成。
  • 5.Master端在发送快照文件过程中,接收的任何会改变数据集的命令都会暂时先保存在Slave网络连接的发送缓存队列里(list数据结构),待快照完成后,依次发给Slave,之后收到的命令相同处理,并将状态置位为 REDIS_REPL_ONLINE。

整个复制过程完成,流程如下图所示:

Redis复制机制的缺陷

从上面的流程可以看出,Slave从库在连接Master主库时,Master会进行内存快照,然后把整个快照文件发给Slave,也就是没有象MySQL那样有复制位置的概念,即无增量复制,这会给整个集群搭建带来非常多的问题。

比如一台线上正在运行的Master主库配置了一台从库进行简单读写分离,这时Slave由于网络或者其它原因与Master断开了连接,那么当Slave进行重新连接时,需要重新获取整个Master的内存快照,Slave所有数据跟着全部清除,然后重新建立整个内存表,一方面Slave恢复的时间会非常慢,另一方面也会给主库带来压力。

所以基于上述原因,如果你的Redis集群需要主从复制,那么最好事先配置好所有的从库,避免中途再去增加从库。

Cache还是Storage

在我们分析过了Redis的复制与持久化功能后,我们不难得出一个结论,实际上Redis目前发布的版本还都是一个单机版的思路,主要的问题集中在,持久化方式不够成熟,复制机制存在比较大的缺陷,这时我们又开始重新思考Redis的定位:Cache还是Storage?

如果作为Cache的话,似乎除了有些非常特殊的业务场景,必须要使用Redis的某种数据结构之外,我们使用Memcached可能更合适,毕竟Memcached无论客户端包和服务器本身更久经考验。

如果是作为存储Storage的话,我们面临的最大的问题是无论是持久化还是复制都没有办法解决Redis单点问题,即一台Redis挂掉了,没有太好的办法能够快速的恢复,通常几十G的持久化数据,Redis重启加载需要几个小时的时间,而复制又有缺陷,如何解决呢?

Redis可扩展集群搭建

1. 主动复制避开Redis复制缺陷。

既然Redis的复制功能有缺陷,那么我们不妨放弃Redis本身提供的复制功能,我们可以采用主动复制的方式来搭建我们的集群环境。

所谓主动复制是指由业务端或者通过代理中间件对Redis存储的数据进行双写或多写,通过数据的多份存储来达到与复制相同的目的,主动复制不仅限于用在Redis集群上,目前很多公司采用主动复制的技术来解决MySQL主从之间复制的延迟问题,比如Twitter还专门开发了用于复制和分区的中间件gizzard(https://github.com/twitter/gizzard) 。

主动复制虽然解决了被动复制的延迟问题,但也带来了新的问题,就是数据的一致性问题,数据写2次或多次,如何保证多份数据的一致性呢?如果你的应用对数据一致性要求不高,允许最终一致性的话,那么通常简单的解决方案是可以通过时间戳或者vector clock等方式,让客户端同时取到多份数据并进行校验,如果你的应用对数据一致性要求非常高,那么就需要引入一些复杂的一致性算法比如Paxos来保证数据的一致性,但是写入性能也会相应下降很多。

通过主动复制,数据多份存储我们也就不再担心Redis单点故障的问题了,如果一组Redis集群挂掉,我们可以让业务快速切换到另一组Redis上,降低业务风险。

2. 通过presharding进行Redis在线扩容。

通过主动复制我们解决了Redis单点故障问题,那么还有一个重要的问题需要解决:容量规划与在线扩容问题。

我们前面分析过Redis的适用场景是全部数据存储在内存中,而内存容量有限,那么首先需要根据业务数据量进行初步的容量规划,比如你的业务数据需要100G存储空间,假设服务器内存是48G,那么根据上一篇我们讨论的Redis磁盘IO的问题,我们大约需要3~4台服务器来存储。这个实际是对现有业务情况所做的一个容量规划,假如业务增长很快,很快就会发现当前的容量已经不够了,Redis里面存储的数据很快就会超过物理内存大小,那么如何进行Redis的在线扩容呢?

Redis的作者提出了一种叫做presharding的方案来解决动态扩容和数据分区的问题,实际就是在同一台机器上部署多个Redis实例的方式,当容量不够时将多个实例拆分到不同的机器上,这样实际就达到了扩容的效果。

拆分过程如下:

  • 1.在新机器上启动好对应端口的Redis实例。
  • 2.配置新端口为待迁移端口的从库。
  • 3.待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口。
  • 4.配置从库为新的主库。
  • 5.移除老的端口实例。
  • 6.重复上述过程迁移好所有的端口到指定服务器上。

以上拆分流程是Redis作者提出的一个平滑迁移的过程,不过该拆分方法还是很依赖Redis本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。

Redis复制的改进思路

我们线上的系统使用了我们自己改进版的Redis,主要解决了Redis没有增量复制的缺陷,能够完成类似Mysql Binlog那样可以通过从库请求日志位置进行增量复制。

我们的持久化方案是首先写Redis的AOF文件,并对这个AOF文件按文件大小进行自动分割滚动,同时关闭Redis的Rewrite命令,然后会在业务低峰时间进行内存快照存储,并把当前的AOF文件位置一起写入到快照文件中,这样我们可以使快照文件与AOF文件的位置保持一致性,这样我们得到了系统某一时刻的内存快照,并且同时也能知道这一时刻对应的AOF文件的位置,那么当从库发送同步命令时,我们首先会把快照文件发送给从库,然后从库会取出该快照文件中存储的AOF文件位置,并将该位置发给主库,主库会随后发送该位置之后的所有命令,以后的复制就都是这个位置之后的增量信息了。

Redis与MySQL的结合

目前大部分互联网公司使用MySQL作为数据的主要持久化存储,那么如何让Redis与MySQL很好的结合在一起呢?我们主要使用了一种基于MySQL作为主库,Redis作为高速数据查询从库的异构读写分离的方案。

为此我们专门开发了自己的MySQL复制工具,可以方便的实时同步MySQL中的数据到Redis上。

(MySQL-Redis 异构读写分离)

总结:

  • 1.Redis的复制功能没有增量复制,每次重连都会把主库整个内存快照发给从库,所以需要避免向在线服务的压力较大的主库上增加从库。
  • 2.Redis的复制由于会使用快照持久化方式,所以如果你的Redis持久化方式选择的是日志追加方式(aof),那么系统有可能在同一时刻既做aof日志文件的同步刷写磁盘,又做快照写磁盘操作,这个时候Redis的响应能力会受到影响。所以如果选用aof持久化,则加从库需要更加谨慎。
  • 3.可以使用主动复制和presharding方法进行Redis集群搭建与在线扩容。

本文加上之前的2篇文章基本将Redis的最常用功能和使用场景与优化进行了分析和讨论,实际Redis还有很多其它辅助的一些功能,Redis的作者也在不断尝试新的思路,这里就不一一列举了,有兴趣的朋友可以研究下,也很欢迎一起讨论,我的微博(http://weibo.com/bachmozart ) @摇摆巴赫。

来源:www.infoq.com

分类: redis头条 标签:

Redis + PHP 最佳实践!

2020年4月17日 没有评论

好吧,我承认我的标题起来太水了~但确实想表达的是字面的意思,本文分享给大家的是一个应用 PHP+Redis 构建类似于聊天室的整个过程,包括以下几个部分:

  • Redis 下载安装
  • Redis PHP 客户端 Rediska 安装使用
  • Redis key-value 存储结构设计
  • 代码重构,从原来的 MySQL 存储迁移到 Redis 存储
  • 数据迁移,将 MySQL 中的数据迁移到 Redis 中
  • 服务性能对比与描述

能把教程写得这么细的实在难得,但在这里提醒大家,这里只是一个应用教程,具体你的业务是否适合使用 Redis ,是否能承受其不足之处,还希望大家自己考虑。

原文链接:Creating Chatroom / Walls with Redis & PHP

分类: redis头条 标签:

Redis应用案例,查找某个值的范围

2020年4月17日 没有评论

本文来自Redis在Google Group上的一个问题,有一位同学发贴求助,说要解决如下的一个问题:他有一个IP范围对应地址的列表,现在需要给出一个IP的情况下,迅速的查找到这个IP在哪个范围,也就是要判断此IP的所有地。这个问题引来了Redis作者Salvatore Sanfilippo(@antirez)的回答。解答如下:

例如有下面两个范围,10-20和30-40

  • A_start 10, A_end 20
  • B_start 30, B_end 40

我们将这两个范围的起始位置存在Redis的Sorted Sets数据结构中,基本范围起始值作为score,范围名加start和end为其value值:

redis 127.0.0.1:6379> zadd ranges 10 A_start
(integer) 1
redis 127.0.0.1:6379> zadd ranges 20 A_end
(integer) 1
redis 127.0.0.1:6379> zadd ranges 30 B_start
(integer) 1
redis 127.0.0.1:6379> zadd ranges 40 B_end
(integer) 1

这样数据在插入Sorted Sets后,相当于是将这些起始位置按顺序排列好了。

现在我需要查找15这个值在哪一个范围中,只需要进行如下的zrangbyscore查找:

redis 127.0.0.1:6379> zrangebyscore ranges (15 +inf LIMIT 0 1
1) "A_end"

这个命令的意思是在Sorted Sets中查找大于15的第一个值。(+inf在Redis中表示正无穷大,15前面的括号表示>15而非>=15)

查找的结果是A_end,由于所有值是按顺序排列的,所以可以判定15是在A_start到A_end区间上,也就是说15是在A这个范围里。至此大功告成。

当然,如果你查找到的是一个start,比如咱们用25,执行下面的命令

redis 127.0.0.1:6379> zrangebyscore ranges (25 +inf LIMIT 0 1
1) "B_start"

返回结果表明其下一个节点是一个start节点,也就是说25这个值不处在任何start和end之间,不属于任何范围。

当然,这个例子仅适用于类似上面的IP范围查找的案例,因为这些值范围之间没有重合。如果是有重合的情况,这个问题本身也就变成了一个一对多的问题。好吧,如果真的是有重合的范围,我们又当如何解决呢?欢迎读者同学你来挑战。

如果你有更多有意思的应用场景,欢迎投稿给我们。

相关地址:groups.google.com

分类: redis头条 标签:

浅谈Redis数据库的键值设计

2020年4月17日 没有评论

NoSQL带给我们的东西很多,高性能,水平扩展性,还有不一样的思维方式。本文来自@hoterran的个人博客运维与开发,作者列举了几种常用的应用场景,分别描述了其关系型数据库和Redis下的不同存储设计方法。值得参考。

丰富的数据结构使得redis的设计非常的有趣。不像关系型数据库那样,DEV和DBA需要深度沟通,review每行sql语句,也不像memcached那样,不需要DBA的参与。redis的DBA需要熟悉数据结构,并能了解使用场景。

下面举一些常见适合kv数据库的例子来谈谈键值的设计,并与关系型数据库做一个对比,发现关系型的不足之处。

用户登录系统

记录用户登录信息的一个系统, 我们简化业务后只留下一张表。

关系型数据库的设计

mysql> select * from login;
 --------- ---------------- ------------- ---------------------
| user_id | name           | login_times | last_login_time     |
 --------- ---------------- ------------- ---------------------
|       1 | ken thompson   |           5 | 2011-01-01 00:00:00 |
|       2 | dennis ritchie |           1 | 2011-02-01 00:00:00 |
|       3 | Joe Armstrong  |           2 | 2011-03-01 00:00:00 |
 --------- ---------------- ------------- --------------------- 

user_id表的主键,name表示用户名,login_times表示该用户的登录次数,每次用户登录后,login_times会自增,而last_login_time更新为当前时间。

REDIS的设计

关系型数据转化为KV数据库,我的方法如下:

key 表名:主键值:列名

value 列值

一般使用冒号做分割符,这是不成文的规矩。比如在php-admin for redis系统里,就是默认以冒号分割,于是user:1 user:2等key会分成一组。于是以上的关系数据转化成kv数据后记录如下:

Set login:1:login_times 5
Set login:2:login_times 1
Set login:3:login_times 2

Set login:1:last_login_time 2011-1-1
Set login:2:last_login_time 2011-2-1
Set login:3:last_login_time 2011-3-1

set login:1:name ”ken thompson“
set login:2:name “dennis ritchie”
set login:3:name ”Joe Armstrong“

这样在已知主键的情况下,通过get、set就可以获得或者修改用户的登录次数和最后登录时间和姓名。

一般用户是无法知道自己的id的,只知道自己的用户名,所以还必须有一个从name到id的映射关系,这里的设计与上面的有所不同。

set "login:ken thompson:id"   1
set "login:dennis ritchie:id"    2
set "login: Joe Armstrong:id"  3

这样每次用户登录的时候业务逻辑如下(python版),r是redis对象,name是已经获知的用户名。

#获得用户的id
uid = r.get("login:%s:id" % name)
#自增用户的登录次数
ret = r.incr("login:%s:login_times" % uid)
#更新该用户的最后登录时间
ret = r.set("login:%s:last_login_time" % uid, datetime.datetime.now())

如果需求仅仅是已知id,更新或者获取某个用户的最后登录时间,登录次数,关系型和kv数据库无啥区别。一个通过btree pk,一个通过hash,效果都很好。

假设有如下需求,查找最近登录的N个用户。开发人员看看,还是比较简单的,一个sql搞定。

select * from login order by last_login_time desc limit N

DBA了解需求后,考虑到以后表如果比较大,所以在last_login_time上建个索引。执行计划从索引leafblock 的最右边开始访问N条记录,再回表N次,效果很好。

过了两天,又来一个需求,需要知道登录次数最多的人是谁。同样的关系型如何处理?DEV说简单

select * from login order by login_times desc limit N

DBA一看,又要在login_time上建立一个索引。有没有觉得有点问题呢,表上每个字段上都有素引。

关系型数据库的数据存储的的不灵活是问题的源头,数据仅有一种储存方法,那就是按行排列的堆表。统一的数据结构意味着你必须使用索引来改变sql的访问路径来快速访问某个列的,而访问路径的增加又意味着你必须使用统计信息来辅助,于是一大堆的问题就出现了。

没有索引,没有统计计划,没有执行计划,这就是kv数据库。

redis里如何满足以上的需求呢? 对于求最新的N条数据的需求,链表的后进后出的特点非常适合。我们在上面的登录代码之后添加一段代码,维护一个登录的链表,控制他的长度,使得里面永远保存的是最近的N个登录用户。

#把当前登录人添加到链表里
ret = r.lpush("login:last_login_times", uid)
#保持链表只有N位
ret = redis.ltrim("login:last_login_times", 0, N-1)

这样需要获得最新登录人的id,如下的代码即可

last_login_list = r.lrange("login:last_login_times", 0, N-1)

另外,求登录次数最多的人,对于排序,积分榜这类需求,sorted set非常的适合,我们把用户和登录次数统一存储在一个sorted set里。

zadd login:login_times 5 1
zadd login:login_times 1 2
zadd login:login_times 2 3

这样假如某个用户登录,额外维护一个sorted set,代码如此

#对该用户的登录次数自增1
ret = r.zincrby("login:login_times", 1, casino  uid)

那么如何获得登录次数最多的用户呢,逆序排列取的排名第N的用户即可

ret = r.zrevrange("login:login_times", 0, N-1)

可以看出,DEV需要添加2行代码,而DBA不需要考虑索引什么的。

TAG系统

tag在互联网应用里尤其多见,如果以传统的关系型数据库来设计有点不伦不类。我们以查找书的例子来看看redis在这方面的优势。

关系型数据库的设计

两张表,一张book的明细,一张tag表,表示每本的tag,一本书存在多个tag。

mysql> select * from book;
 ------ ------------------------------- ----------------
| id | name | author |
 ------ ------------------------------- ----------------
| 1 | The Ruby Programming Language | Mark Pilgrim |
| 1 | Ruby on rail | David Flanagan |
| 1 | Programming Erlang | Joe Armstrong |
 ------ ------------------------------- ---------------- 

mysql> select * from tag;
 --------- ---------
| tagname | book_id |
 --------- ---------
| ruby | 1 |
| ruby | 2 |
| web | 2 |
| erlang | 3 |
 --------- --------- 

假如有如此需求,查找即是ruby又是web方面的书籍,如果以关系型数据库会怎么处理?
select b.name, b.author  from tag t1, tag t2, book b
where t1.tagname = "web" and t2.tagname = "ruby" and t1.book_id = t2.book_id and b.id = t1.book_id

tag表自关联2次再与book关联,这个sql还是比较复杂的,如果要求即ruby,但不是web方面的书籍呢?

关系型数据其实并不太适合这些集合操作。

REDIS的设计

首先book的数据肯定要存储的,和上面一样。

set book:1:name    ”The Ruby Programming Language”
Set book:2:name     ”Ruby on rail”
Set book:3:name     ”Programming Erlang”

set book:1:author    ”Mark Pilgrim”
Set book:2:author     ”David Flanagan”
Set book:3:author     ”Joe Armstrong”

tag表我们使用集合来存储数据,因为集合擅长求交集、并集

sadd tag:ruby 1
sadd tag:ruby 2
sadd tag:web 2
sadd tag:erlang 3

那么,即属于ruby又属于web的书?

inter_list = redis.sinter("tag.web", "tag:ruby")

即属于ruby,但不属于web的书?

inter_list = redis.sdiff("tag.ruby", "tag:web")

属于ruby和属于web的书的合集?

inter_list = redis.sunion("tag.ruby", "tag:web")

简单到不行阿。

从以上2个例子可以看出在某些场景里,关系型数据库是不太适合的,你可能能够设计出满足需求的系统,但总是感觉的怪怪的,有种生搬硬套的感觉。

尤其登录系统这个例子,频繁的为业务建立索引。放在一个复杂的系统里,ddl(创建索引)有可能改变执行计划。导致其它的sql采用不同的执行计划,业务复杂的老系统,这个问题是很难预估的,sql千奇百怪。要求DBA对这个系统里所有的sql都了解,这点太难了。这个问题在oracle里尤其严重,每个DBA估计都碰到过。对于MySQL这类系统,ddl又不方便(虽然现在有online ddl的方法)。碰到大表,DBA凌晨爬起来在业务低峰期操作,这事我没少干过。而这种需求放到redis里就很好处理,DBA仅仅对容量进行预估即可。

未来的OLTP系统应该是kv和关系型的紧密结合。

来源:www.hoterran.info

分类: redis头条 标签:

案例:用Redis来存储关注关系

2020年4月17日 没有评论

Redis提供了丰富的数据类型,比起关系型数据库或者简单的Key-Value存储(比如Memcached)来,Redis的数据模型与实际应用的数据模型更相近。比如下面说到的好友关系的存储,原作者使用了Redis的 Sets(集合)数据结构。

具体存储方式如下:对于每一个用户,其关注关系存储两份列表,一份为此用户关注的人的UID列表,另一份为此用户粉丝的UID列表,这两个列表都使用Sets(集合)。比如对于用户ID为123的用户,graph:user:123:following 保存的是其关注人的列表,graph:user:1:followed_by 保存的是关注他的人的列表。

下面是一个PHP代码的关注关系类,包括了常规的关注关系操作查询等方法,具体可看注释:


/*
 * This example would probably work best if you're using
 * an MVC framework, but it can be used standalone as well.
 *
 * This example also assumes you are using Predis, the excellent
 * PHP Redis library available here:
 * https://github.com/nrk/predis
 */
class UserNode {
	// The user's ID, probably loaded from MySQL
	private $id;

	// The redis server configuration
	private $redis_config = array(
		array('host' => 'localhost', 'port' => 6379 )
	);

	// Stores the redis connection resource so that
	// we only need to connect to Redis once
	private $redis;

	public function __construct($userID) {
		$this->id = $userID;
	}

	private function redis() {
		if (!$this->redis) {
			$this->redis = new Predis\Client($redis_config);
		}

		return $this->redis;
	}

	/*
	 * Makes this user follow the user with the given ID.
	 * In order to stay efficient, we need to make a two-way
	 * directed graph. This means when we follow a user, we also
	 * say that that user is followed by this user, making a forward
	 * and backword directed graph.
	 */
	public function follow($user) {
		$this->redis()->sadd("graph:user:{$this->id}:following", $user);
		$this->redis()->sadd("graph:user:$user:followed_by", $this->id);
	}

	/*
	 * Makes this user unfollow the user with the given ID.
	 * First we check to make sure that we are actually following
	 * the user we want to unfollow, then we remove both the forward
	 * and backward references.
	 */
	public function unfollow($user) {
		if ($this->is_following()) {
			$this->redis()->srem("graph:user:{$this->id}:following", $user);
			$this->redis()->srem("graph:user:$user:followed_by", $this->id);
		}
	}

	/*
	 * Returns an array of user ID's that this user follows.
	 */
	public function following() {
		return $this->redis()->smembers("graph:user:{$this->id}:following");
	}

	/*
	 * Returns an array of user ID's that this user is followed by.
	 */
	 public function followed_by() {
	 	return $this->redis()->smembers("graph:user:{$this->id}:followed_by");
	 }

	/*
	 * Test to see if this user is following the given user or not.
	 * Returns a boolean.
	 */
	public function is_following($user) {
		return $this->redis()->sismember("graph:user:{$this->id}:following", $user);
	}

	/*
	 * Test to see if this user is followed by the given user.
	 * Returns a boolean.
	 */
	public function is_followed_by($user) {
		return $this->redis()->sismember("graph:user:{$this->id}:followed_by", $user);
	}

	/*
	 * Tests to see if the relationship between this user and the given user is mutual.
	 */
	public function is_mutual($user) {
		return ($this->is_following($user) && $this->is_followed_by($user));
	}

	/*
	 * Returns the number of users that this user is following.
	 */
	public function follow_count() {
		return $this->redis()->scard("graph:user:{$this->id}:following");
	}

	/*
	 * Retuns the number of users that follow this user.
	 */
	public function follower_count() {
		return $this->redis()->scard("graph:user:{$this->id}:followed_by");
	}

	/*
	 * Finds all users that the given users follow in common.
	 * Returns an array of user IDs
	 */
	public function common_following($users) {
		$redis = $this->redis();
		$users[] = $this->id;

		$keys = array();
		foreach ($users as $user) {
			$keys[] = "graph:user:{$user}:following";
		}

		return call_user_func_array(array($redis, "sinter"), $keys);
	}

	/*
	 * Finds all users that all of the given users are followed by in common.
	 * Returns an array of user IDs
	 */
	public function common_followed_by($users) {
		$redis = $this->redis();
		$users[] = $this->id;

		$keys = array();
		foreach ($users as $user) {
			$keys[] = "graph:user:{$user}:followed_by";
		}

		return call_user_func_array(array($redis, "sinter"), $keys);
	}

}

下面是使用这个类来操作关注关系的例子:

// create two user nodes, assume for this example
// they're users with no social graph entries.
$user1 = UserNode(1);
$user2 = UserNode(2);

$user1->follows(); // array()

// add some followers
$user1->follow(2);
$user1->follow(3);

// now check the follow list
$user1->follows(); // array(2, 3)

// now we can also do:
$user2->followed_by(); // array(1)

// if we do this...
$user2->follow(3);

// then we can do this to see which people users #1 and #2 follow in common
$user1->common_following(2); // array(3)

来源:blog.meltingice.net

分类: redis头条 标签:

用Redis存储好友关系-python版

2020年4月17日 没有评论

这是一个用Redis存储好友关系的python下使用Redis的例子。作者是Eric Florenzano (@ericflo)。相信对各位人生苦短用python的同学是一个好的入门例子。
例子说明:其中虽然用了follow和block等字眼,但是他的关系不是一个微博系统的单向关注,而是类似SNS的双向关系。

"""
I've been thinking lately about how perfect Redis would be for storing a
simple social graph. I posited that it would be relatively few lines of code,
and that it'd be clean code too. So here it is: a basic social graph built on Redis.
"""

class FriendGraph(object):

    def __init__(self, ring):
        self.ring = ring

        # These keys are intentionally short, so as to save on memory in redis
        self.FOLLOWS_KEY = 'F'
        self.FOLLOWERS_KEY = 'f'
        self.BLOCKS_KEY = 'B'
        self.BLOCKED_KEY = 'b'

    def follow(self, from_user, to_user):
        forward_key = '%s:%s' % (self.FOLLOWS_KEY, from_user)
        forward = self.ring.sadd(forward_key, to_user)
        reverse_key = '%s:%s' % (self.FOLLOWERS_KEY, to_user)
        reverse = self.ring.sadd(reverse_key, from_user)
        return forward and reverse

    def unfollow(self, from_user, to_user):
        forward_key = '%s:%s' % (self.FOLLOWS_KEY, from_user)
        forward = self.ring.srem(forward_key, to_user)
        reverse_key = '%s:%s' % (self.FOLLOWERS_KEY, to_user)
        reverse = self.ring.srem(reverse_key, from_user)
        return forward and reverse

    def block(self, from_user, to_user):
        forward_key = '%s:%s' % (self.BLOCKS_KEY, from_user)
        forward = self.ring.sadd(forward_key, to_user)
        reverse_key = '%s:%s' % (self.BLOCKED_KEY, to_user)
        reverse = self.ring.sadd(reverse_key, from_user)
        return forward and reverse

    def unblock(self, from_user, to_user):
        forward_key = '%s:%s' % (self.BLOCKS_KEY, from_user)
        forward = self.ring.srem(forward_key, to_user)
        reverse_key = '%s:%s' % (self.BLOCKED_KEY, to_user)
        reverse = self.ring.srem(reverse_key, from_user)
        return forward and reverse

    def get_follows(self, user):
        follows = self.ring.smembers('%s:%s' % (self.FOLLOWS_KEY, user))
        blocked = self.ring.smembers('%s:%s' % (self.BLOCKED_KEY, user))
        return list(follows.difference(blocked))

    def get_followers(self, user):
        followers = self.ring.smembers('%s:%s' % (self.FOLLOWERS_KEY, user))
        blocks = self.ring.smembers('%s:%s' % (self.BLOCKS_KEY, user))
        return list(followers.difference(blocks))

    def get_blocks(self, user):
        return list(self.ring.smembers('%s:%s' % (self.BLOCKS_KEY, user)))

    def get_blocked(self, user):
        return list(self.ring.smembers('%s:%s' % (self.BLOCKED_KEY, user)))

分类: redis头条 标签:

WebSockets+Redis构建EventMachine

2020年4月17日 没有评论

EventMachine 是一个Ruby的事件驱动网络库,本文不是要介绍它,而是要介绍一个以Redis 的 Pub/Sub 机制为后端,以WebSockets为前端的类EventMachine实现。

前端代码,创建Socket连接到本地8081端口,当有消息push过来的时候,将消息打印到指定的div里:





	Websockets!
	
	function onMessage(evt) {
		con = document.getElementById("console");
		con.innerHTML += evt.data;
		con.innerHTML += '
'; } websocket = new WebSocket("ws://localhost:8081"); websocket.onmessage = function(evt) { onMessage(evt); };

后端代码:

require 'redis'
require 'em-websocket'

SOCKETS = []
@redis = Redis.new(:host => '127.0.0.1', :post => 6379)

# Creating a thread for the EM event loop
Thread.new do
  EventMachine.run do
    # Creates a websocket listener
    EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8081) do |ws|
      ws.onopen do
        # When someone connects I want to add that socket to the SOCKETS array that
        # I instantiated above
        puts 'creating socket'
        SOCKETS << ws
      end

      ws.onclose do
        # Upon the close of the connection I remove it from my list of running sockets
        puts 'closing socket'
        SOCKETS.delete ws
      end
    end
  end
end

# Creating a thread for the redis subscribe block
Thread.new do
  @redis.subscribe('ws') do |on|
    # When a message is published to 'ws'
    on.message do |chan, msg|
     puts "sending message: #{msg}"
     # Send out the message on each open socket
     SOCKETS.each {|s| s.send msg}
    end
  end
end

sleep

开启8081端口接受连接,同时连到Redis上订阅ws这个key的消息

当前后端都启动并连接上后,你就可以用如下代码往Redis的ws这个key上写消息,页面上就能看到push过来的消息了:

require 'redis'
@redis = Redis.new(:host => '127.0.0.1', :post => 6379)
@redis.publish 'ws', 'Something witty'

来源:jessedearing.com have fun!

分类: redis头条 标签:

django-redis-cache:用Redis作django的缓存层

2020年4月17日 没有评论

django-redis-cache 是一个开源项目,代码不多,主要功能是提供 Redis 作为 django 缓存层的功能。下面是其用法的简单介绍。使用方法:

  • 1. 运行 setup.py 安装 django-redis-cache 扩展
  • 2. 按如下方法修改django的设置

Django 版本 < 1.3,修改如下配置:

CACHE_BACKEND = 'redis_cache.cache://< host >:< port >'

Django 版本 > 1.3,修改如下配置:

CACHES = {
'default': {
'BACKEND': 'redis_cache.RedisCache',
'LOCATION': '< host >:< port >',
'OPTIONS': { # optional
'DB': 1,
'PASSWORD': 'yadayada',
},
},
}

项目地址:code.google.com

分类: redis头条 标签:

Reds:一个Redis加Node.js的全文搜索引擎

2020年4月17日 没有评论

Reds是由LearnBoost公司的TJ Holowaychuk开发的一个基于Redis的Node.js全文搜索引擎,其代码加上注释也只有300行。不得不说又是一个Redis的最佳实践,它的主要原理是通过Redis的sets数据结构将分词后的词语碎片进行存储。这里的分词仅仅是对英文按空格进行切分(中文分词就不要想了~)。

例子:
先添加几个句子到搜索引擎中建立索引

var strs = [];
strs.push('Tobi wants four dollars');
strs.push('Tobi only wants $4');
strs.push('Loki is really fat');
strs.push('Loki, Jane, and Tobi are ferrets');
strs.push('Manny is a cat');
strs.push('Luna is a cat');
strs.push('Mustachio is a cat');

strs.forEach(function(str, i){ search.index(str, i); });

然后在Tobi dollars这个组合进行搜索

search.query(query = 'Tobi dollars', function(err, ids){
  if (err) throw err;
  console.log('Search results for "%s":', query);
  ids.forEach(function(id){
    console.log('  - %s', strs[id]);
  });
  process.exit();
});

下面是其搜索结果

Search results for "Tobi dollars":
  - Tobi wants four dollars

更多相关信息及源码请上其在GitHub上的项目地址:https://github.com/visionmedia/reds

同时感谢@xu_lele的分享,分享NoSQL相关技术与新闻,只需要在twitter,新浪微博,腾讯微博上@nosqlfan 即可。

分类: redis头条 标签: