NoSQL - Redis应用场景

     问题的引入

      DB(Oracle、MySQL、Postgresql等)+Memcached 这种架构模式在咱们生产环境中十分常见,通常咱们经过Memcached将热点数据加载到cache,应用层首先向Memcached请求数据,若是缓存中存在数据,那么直接返回应用层;但随着业务数据量的不断增长,和访问量的持续增加,咱们也会遇到不少问题:redis

  1.在DB和Memcached之间如何保证数据的一致性。算法

  2.Memcached数据命中率低或down机,应用直接访问DB,造成雪崩效应,数据库压力瞬间暴增,直接致使数据库响应慢,或者crash掉。sql

  3.跨机房cache同步问题。数据库

  

    Redis

      在众多NoSQL中咱们通常拿Redis替换Memecached使用,缘由有下:api

     1 、Redis 支持更多的数据类型(strings、map、 list、sets、 sorted sets等)缓存

     2 、Redis 支持复制功能。服务器

     3 、Redis 支持数据的持久化,能够将内存中的数据保持在磁盘中,重启的时候能够再次加载进行使用。数据结构

     4 、Redis 支持Sharding技术, 很容易将数据分布到多个Redis实例中,方便快速扩展。架构

     5 、Redis 在内存分配时采用申请分配方式, 内存使用更高效。并发

 

    Redis最为经常使用的数据类型主要有如下:

    • String
    • Hash
    • List
    • Set
    • Sorted set
    • pub/sub
    • Transactions 

 

   数据类型应用

    1.  String 

经常使用命令:get、set、incr、decr mget等。

应用场景:String是最经常使用的一种数据类型,普通的key/ value 存储均可以归为此类.便可以彻底实现目前 Memcached 的功能,而且效率更高。还能够享受Redis的定时持久化,操做日志及 Replication等功能。除了提供与 Memcached 同样的get、set、incr、decr 等操做外,Redis还提供了下面一些操做: 

        • 获取字符串长度
        • 往字符串append内容
        • 设置和获取字符串的某一段内容
        • 设置及获取字符串的某一位(bit)
        • 批量设置一系列字符串的内容 

2.  Hash

 

经常使用命令:hget,hset,hgetall 等。

应用场景:在Memcached中,咱们常常将一些结构化的信息打包成HashMap,在客户端序列化后存储为一个字符串的值,好比用户的昵称、年龄、性别、积分等,这时候在须要修改其中某一项时,一般须要将全部值取出反序列化后,修改某一项的值,再序列化存储回去。这样不只增大了开销,也不适用于一些可能并发操做的场合(好比两个并发的操做都须要修改积分)。而Redis的Hash结构可使你像在数据库中Update一个属性同样只修改某一项属性值。

好比咱们要存储一个用户信息对象数据,包含如下信息:

    用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,若是用普通的key/value结构来存储,主要有如下2种存储方式:

    第一种方式将用户ID做为查找key,把其余信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增长了序列化/反序列化的开销,而且在须要修改其中一项信息时,须要把整个对象取回,而且修改操做须要对并发进行保护,引入CAS等复杂问题。 

    {"ID":"xxxxxx","username,age,birthday"}

    第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称做为惟一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,可是用户ID为重复存储,若是存在大量这样的数据,内存浪费仍是很是可观的。

     {"xxxIDusername":"xxxxx","xxxIDage":"xxxx","xxxIDbirthday":"xxxxx"}

    那么Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口,

     {"ID":"xxxxxx":"userinfo":"\"username\":\"xxxxName\",\"age\":\"xxxx\",\"birthday\":\"xxxxxx\""}

也就是说,Key仍然是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取均可以直接经过其内部Map的Key(Redis里称内部Map的key为field), 也就是经过 key(用户ID) + field(属性标签) 就能够操做对应属性数据了,既不须要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。

 

3. List

经常使用命令:lpush,rpush,lpop,rpop,lrange等。

应用场景:

Redis list的应用场景很是多,也是Redis最重要的数据结构之一,好比twitter的关注列表,粉丝列表等均可以用Redis的list结构来实现。

Lists 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用Lists结构,咱们能够轻松地实现最新消息排行等功能。Lists的另外一个应用就是消息队列,

能够利用Lists的PUSH操做,将任务存在Lists中,而后工做线程再用POP操做将任务取出进行执行。Redis还提供了操做Lists中某一段的api,你能够直接查询,删除Lists中某一段的元素。

 

4. Set

经常使用命令:sadd,spop,smembers,sunion 等。

应用场景:

Redis set对外提供的功能与list相似是一个列表的功能,特殊之处在于set是能够自动排重的,当你须要存储一个列表数据,又不但愿出现重复数据时,set是一个很好的选择,而且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

Sets 集合的概念就是一堆不重复值的组合。利用Redis提供的Sets数据结构,能够存储一些集合性的数据,好比在微博应用中,能够将一个用户全部的关注人存在一个集合中,将其全部粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操做,能够很是方便的实现如共同关注、共同喜爱、二度好友等功能,对上面的全部集合操做,你还可使用不一样的命令选择将结果返回给客户端仍是存集到一个新的集合中。

 

5. Sorted Set

经常使用命令:zadd,zrange,zrem,zcard等

使用场景:

Redis sorted set的使用场景与set相似,区别是set不是自动有序的,而sorted set能够经过用户额外提供一个优先级(score)的参数来为成员排序,而且是插入有序的,即自动排序。当你须要一个有序的而且不重复的集合列表,那么能够选择sorted set数据结构,好比twitter 的public timeline能够以发表时间做为score来存储,这样获取时就是自动按时间排好序的。

另外还能够用Sorted Sets来作带权重的队列,好比普通消息的score为1,重要消息的score为2,而后工做线程能够选择按score的倒序来获取工做任务。让重要的任务优先执行。

 

6. Pub/Sub

 Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你能够设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,全部订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用做实时消息系统,好比普通的即时聊天,群聊等功能。 

 

实际应用场景

一、显示最新的项目列表

下面这个语句经常使用来显示最新项目,随着数据多了,查询毫无疑问会愈来愈慢。

  1. SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10   

        在Web应用中,“列出最新的回复”之类的查询很是广泛,这一般会带来可扩展性问题。这使人沮丧,由于项目原本就是按这个顺序被建立的,但要输出这个顺序却不得不进行排序操做。

        相似的问题就能够用Redis来解决。好比说,咱们的一个Web应用想要列出用户贴出的最新20条评论。在最新的评论边上咱们有一个“显示所有”的连接,点击后就能够得到更多的评论。

        咱们假设数据库中的每条评论都有一个惟一的递增的ID字段。

        咱们可使用分页来制做主页和评论页,使用Redis的模板,每次新评论发表时,咱们会将它的ID添加到一个Redis列表: LPUSH latest.comments <ID>   

       咱们将列表裁剪为指定长度,所以Redis只须要保存最新的5000条评论: LTRIM latest.comments 0 5000 

       每次咱们须要获取最新评论的项目范围时,咱们能够先从Redis 取得这个范围的 ID List, 而后拿这个ID list 到DB里边直接去取数据 ,这个ID通常就是咱们RDBMS里边的 uniq key 或者primary key , 这样以来咱们省去了在 RDBMS 排序的时间,直接使用 consat 方式

       咱们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其余类型数据库)只是在用户须要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。

二、删除与过滤

      咱们可使用LREM来删除评论。若是删除操做很是少,另外一个选择是直接跳过评论条目的入口,报告说该评论已经不存在。

       有些时候你想要给不一样的列表附加上不一样的过滤器。若是过滤器的数量受到限制,你能够简单的为每一个不一样的过滤器使用不一样的Redis列表。毕竟每一个列表只有5000条项目,但Redis却可以使用很是少的内存来处理几百万条项目。

三、排行榜相关

      另外一个很广泛的需求是各类数据库的数据并不是存储在内存中,所以在按得分排序以及实时更新这些几乎每秒钟都须要更新的功能上数据库的性能不够理想。

      典型的好比那些在线游戏的排行榜,好比一个Facebook的游戏,根据得分你一般想要:

         - 列出前100名高分选手

         - 列出某用户当前的全球排名

      这些操做对于Redis来讲小菜一碟,即便你有几百万个用户,每分钟都会有几百万个新的得分。

      模式是这样的,每次得到新得分时,咱们用这样的代码:ZADD leaderboard  <score>  <username> 

     你可能用userID来取代username,这取决于你是怎么设计的。

      获得前100名高分用户很简单:ZREVRANGE leaderboard 0 99

      用户的全球排名也类似,只须要:ZRANK leaderboard <username>

 

四、按照用户投票和时间排序

      排行榜的一种常见变体模式就像Reddit或Hacker News用的那样,新闻按照相似下面的公式根据得分来排序:

       score = points / time^alpha 

      所以用户的投票会相应的把新闻挖出来,但时间会按照必定的指数将新闻埋下去。下面是咱们的模式,固然算法由你决定。

      模式是这样的,开始时先观察那些多是最新的项目,例如首页上的1000条新闻都是候选者,所以咱们先忽视掉其余的,这实现起来很简单。

      每次新的新闻贴上来后,咱们将ID添加到列表中,使用LPUSH + LTRIM,确保只取出最新的1000条项目。

      有一项后台任务获取这个列表,而且持续的计算这1000条新闻中每条新闻的最终得分。计算结果由ZADD命令按照新的顺序填充生成列表,老新闻则被清除。这里的关键思路是排序工做是由后台任务来完成的。

 

五、处理过时项目

      另外一种经常使用的项目排序是按照时间排序。咱们使用unix时间做为得分便可。

      模式以下:

       - 每次有新项目添加到咱们的非Redis数据库时,咱们把它加入到排序集合中。这时咱们用的是时间属性,current_time和time_to_live。

       - 另外一项后台任务使用ZRANGE…SCORES查询排序集合,取出最新的10个项目。若是发现unix时间已通过期,则在数据库中删除条目。

 

六、计数

       Redis是一个很好的计数器,这要感谢INCRBY和其余类似命令。

       我相信你曾许屡次想要给数据库加上新的计数器,用来获取统计或显示新信息,可是最后却因为写入敏感而不得不放弃它们。

       好了,如今使用Redis就不须要再担忧了。有了原子递增(atomic increment),你能够放心的加上各类计数,用GETSET重置,或者是让它们过时。

       例如这样操做:  INCR user:<id> EXPIRE  

       你能够计算出最近用户在页面间停顿不超过60秒的页面浏览量,当计数达到好比20时,就能够显示出某些条幅提示,或是其它你想显示的东西。

七、特定时间内的特定项目

        另外一项对于其余数据库很难,但Redis作起来却垂手可得的事就是统计在某段特色时间里有多少特定用户访问了某个特定资源。好比我想要知道某些特定的注册用户或IP地址,他们到底有多少访问了某篇文章。

      每次我得到一次新的页面浏览时我只须要这样作:  SADD page:day1:<page_id> <user_id> 

      固然你可能想用unix时间替换day1,好比time()-(time()%3600*24)等等。

      想知道特定用户的数量吗?只须要使用 SCARD page:day1:<page_id>

       须要测试某个特定用户是否访问了这个页面?SISMEMBER page:day1:<page_id>

 

八、实时分析正在发生的状况,用于数据统计与防止垃圾邮件等

        咱们只作了几个例子,但若是你研究Redis的命令集,而且组合一下,就能得到大量的实时分析方法,有效并且很是省力。使用Redis原语命令,更容易实施垃圾邮件过滤系统或其余实时跟踪系统。

 

九、Pub/Sub

       Redis的Pub/Sub很是很是简单,运行稳定而且快速。支持模式匹配,可以实时订阅与取消频道。

十、队列

        你应该已经注意到像list push和list pop这样的Redis命令可以很方便的执行队列操做了,但能作的可不止这些:好比Redis还有list pop的变体命令,可以在列表为空时阻塞队列。

       现代的互联网应用大量地使用了消息队列(Messaging)。消息队列不只被用于系统内部组件之间的通讯,同时也被用于系统跟其它服务之间的交互。消息队列的使用能够增长系统的可扩展性、灵活性和用户体验。非基于消息队列的系统,其运行速度取决于系统中最慢的组件的速度(注:短板效应)。而基于消息队列能够将系统中各组件解除耦合,这样系统就再也不受最慢组件的束缚,各组件能够异步运行从而得以更快的速度完成各自的工做。

    此外,当服务器处在高并发操做的时候,好比频繁地写入日志文件。能够利用消息队列实现异步处理。从而实现高性能的并发操做。

 

附录:

Redis 和Memcached 对比:http://my.oschina.net/junn/blog/280218

为何使用Redis 及其产品: http://www.infoq.com/cn/articles/tq-why-choose-redis