Redis常见应用场景之拙见

前言

  • Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合),并 在 2.8.9 版本添加了 HyperLogLog 结构。如何根据不同的应用场景选择不同的数据类型呢?
  • Redis中的发布订阅,事务功能,又怎么应用于业务中呢?
  • Redis中的布隆过滤器用过吗?
    带着这些疑问,下文将逐条解答。

string

Strings 数据结构是简单的key-value类型,value其实不仅是String,也可以是数字.常用命令: set,get,decr,incr,mget 等
Redis基础教程请参照:
https://www.runoob.com/redis/redis-tutorial.html

应用场景

可以完全实现目前 Memcached 的功能。顺便提一下,与Memcached比较,Redis具有以下优缺点。

  • redis支持定时持久化,操作日志及 Replication等功能。
  • redis功能更强大,不仅仅支持k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • 当然Memcached也有自己的优点,就是支持多线程。Redis仅支持单线程(即不能同时处理多个客户端的请求)
    一些常见的应用场景,都可以应用string类型的键值对存储。
应用场景概述
全页面缓存如果你使用的是服务器端内容渲染,你又不想为每个请求重新渲染每个页面,就可以使用 Redis 把常被请求的内容缓存起来,能够大大的降低页面请求的延迟,已经有很多框架用Redis来缓存页面,这就是页面静态化的一种方式。

Hash

常用命令:hget,hset,hgetall 等。Hash特别适合存储 对象数据。
因为Redis的Hash存储的Value实际是一个HashMap,并提供了直接读写这个Map成员的接口,如下图:
在这里插入图片描述

应用场景

比如我们要存储一个用户信息对象数据,包含以下信息:
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:

  • 第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。
  • 第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。

如果采用hash,对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。
这里同时需要注意,Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。

list

常用命令:lpush,rpush,lpop,rpop,lrange等。
Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

应用场景

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

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 

每次我们需要获取最新评论的项目范围时,我们调用一个函数来完成(使用伪代码):

FUNCTION get_latest_comments(start, num_items):  
    id_list = redis.lrange("latest.comments",start,start+num_items - 1)  
    IF id_list.length < num_items  
        id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")  
    END  
    RETURN id_list  
END  

这里我们做的很简单。在Redis中我们的最新ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。
我们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。
另外,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。

set

常用命令:
sadd,spop,smembers,sunion 等。
set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
在这里插入图片描述

应用场景

Redis set对外提供的功能与list类似是一个不重复的列表的功能,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
Sets 集合的概念就是一堆不重复值的组合。利用Redis提供的Sets数据结构,可以存储一些集合性的数据,比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

zset

Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构。
常用命令:
zadd,zrange,zrem,zcard等
实现方式:
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证成员的唯一和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
在这里插入图片描述

应用场景

可以用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
另一个很普遍的需求是各种数据库的数据并非存储在内存中,因此在按得分排序以及实时更新这些几乎每秒钟都需要更新的功能上数据库的性能不够理想。典型的比如那些在线游戏的排行榜,比如一个Facebook的游戏,根据得分你通常想要:

  • 列出前100名高分选手
  • 列出某用户当前的全球排名

这些操作对于Redis来说小菜一碟,即使你有几百万个用户,每分钟都会有几百万个新的得分。模式是这样的,每次获得新得分时,我们用这样的代码:

ZADD leaderboard  <score>  <username> 

你可能用userID来取代username,这取决于你是怎么设计的。
得到前100名高分用户很简单:ZREVRANGE leaderboard 0 99。
用户的全球排名也相似,只需要:

ZRANK leaderboard <username>。

HyperLogLog

HyperLogLog,基于一种概率算法,如果用 bitmap 存储 1 个亿 统计数据大概需要 12 M 内存,而在 HyperLoglog 中,只需要不到 1 K 内存就能够做到!在 Redis 中实现的 HyperLoglog 也只需要 12 K 内存,在 标准误差 0.81% 的前提下,能够统计 264 个数据!
常会用命令:
pfadd,pfcount,pfmerge

应用场景

比如维护一个大型的网站,需要统计网站上每个网页的 UV(独立访客,每个用户每天只记录一次)。
如果统计 PV(浏览量,用户没点一次记录一次),那非常好办,给每个页面配置一个独立的 Redis 计数器就可以了,把这个计数器的 key 后缀加上当天的日期。这样每来一个请求,就执行 INCRBY 指令一次,最终就可以统计出所有的 PV 数据了。
但是 UV 不同,它要去重,同一个用户一天之内的多次访问请求只能计数一次。这就要求了每一个网页请求都需要带上用户的 ID,无论是登录用户还是未登录的用户,都需要一个唯一 ID 来标识。
你也许马上就想到了一个 简单的解决方案:那就是 为每一个页面设置一个独立的 set 集合 来存储所有当天访问过此页面的用户 ID。但这样的 问题 就是:
存储空间巨大, 如果网站访问量一大,你需要用来存储的 set 集合就会非常大,这种场景就适合用HyperLoglog;

发布订阅

pub/sub 是 Redis 内置的一个非常强大的特性,例如可以创建一个实时的聊天系统(群组聊天)、社交网络中的通知触发器(公众号推送)等等。

// Add a message to a channel
PUBLISH channel message

// Recieve messages from a channel
SUBSCRIBE channel

事务

虽然Redis的Transactions提供的并不是严格的ACID的事务(比如一串用EXEC提交执行的命令,在执行中服务器宕机,那么会有一部分命令执行了,剩下的没执行),但是这个Transactions还是提供了基本的命令打包执行的功能(在服务器不出问题的情况下,可以保证一连串的命令是顺序在一起执行的,中间有会有其它客户端命令插进来执行)。Redis还提供了一个Watch功能,你可以对一个key进行Watch,然后再执行Transactions,在这过程中,如果这个Watched的值进行了修改,那么这个Transactions会发现并拒绝执行。

布隆过滤器

布隆过滤器(Bloom Filter) 是 1970 年由布隆提出的。它实际上 是一个很长的二进制向量和一系列随机映射函数 (下面详细说),实际上你也可以把它 简单理解 为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判。
但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。
当布隆过滤器说某个值存在时,这个值 可能不存在;当它说不存在时,那么 一定不存在。打个比方,当它说不认识你时,那就是真的不认识,但是当它说认识你的时候,可能是因为你长得像它认识的另外一个朋友 (脸长得有些相似),所以误判认识你。
布隆过滤器有两个基本指令,bf.add 添加元素,bf.exists 查询元素是否存在,它的用法和 set 集合的 sadd 和 sismember 差不多。注意 bf.add 只能一次添加一个元素,如果想要一次添加多个,就需要用到 bf.madd 指令。同样如果需要一次查询多个元素是否存在,就需要用到 bf.mexists 指令。

默认参数的布隆过滤器,在我们第一次 add 的时候自动创建。Redis 也提供了可以自定义参数的布隆过滤器,只需要在 add 之前使用 bf.reserve 指令显式创建就好了。如果对应的 key 已经存在,bf.reserve 会报错。
bf.reserve 有三个参数,分别是 key、error_rate (错误率) 和 initial_size:
error_rate (错误率) ,越小越准,但需要的空间越大,对于不需要过于精确的场合,设置稍大一些也没有关系,比如上面说的推送系统,只会让一小部分的内容被过滤掉,整体的观看体验还是不会受到很大影响的;
initial_size(初始大小),越大越准,表示预计放入的元素数量,当实际数量超过这个值时,误判率就会提升,所以需要提前设置一个较大的数值避免超出导致误判率升高;
如果不适用 bf.reserve,默认的 error_rate 是 0.01,默认的 initial_size 是 100。

应用场景

应用场景概述
大数据判断是否存在这就可以实现出上述的去重功能,如果你的服务器内存足够大的话,那么使用 HashMap 可能是一个不错的解决方案,理论上时间复杂度可以达到 O(1)的级别,但是当数据量起来之后,还是只能考虑布隆过滤器。
解决缓存穿透我们经常会把一些热点数据放在 Redis 中当作缓存,例如产品详情。 通常一个请求过来之后我们会先查询缓存,而不用直接读取数据库,这是提升性能最简单也是最普遍的做法,但是 如果一直请求一个不存在的缓存,那么此时一定不存在缓存,那就会有 大量请求直接打到数据库 上,造成 缓存穿透,布隆过滤器也可以用来解决此类问题。
爬虫/ 邮箱等系统的过滤平时不知道你有没有注意到有一些正常的邮件也会被放进垃圾邮件目录中,这就是使用布隆过滤器 误判 导致的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值