Redis总结

一、数据类型

String
    常用命令:set,get,decr,incr,mget 等。
    应用场景:String是最常用的一种数据类型,普通的key/value存储都可以归为此类,这里就不所做解释了。
Hash
    常用命令:hget,hset,hgetall 等。
List
    常用命令:lpush,rpush,lpop,rpop,lrange等。
    应用场景:Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现,比较好理解,这里不再重复。
    Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
Set
    常用命令:sadd,spop,smembers,sunion 等。
    应用场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
    实现方式:set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
Sorted set
    使用场景:zadd,zrange,zrem,zcard等
    应用场景:Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么 可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
    实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的 是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

pub/sub
    Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。用作实时消息系统,比如普通的即时聊天,群聊等功能。
Transactions
    Redis的Transactions提供的并不是严格的ACID的事务(比如一串用EXEC提交执行的命令,在执行中服务器宕机,那么会有一部分命令执行了,剩下的没执行),
    但是这个Transactions还是提供了基本的命令打包执行的功能(在服务器不出问题的情况下,可以保证一连串的命令是顺序在一起执行的,中间有会有其它客户端命令插进来执行)。
    Redis还提供了一个Watch功能,你可以对一个key进行Watch,然后再执行Transactions,在这过程中,如果这个Watched的值进行了修改,那么这个Transactions会发现并拒绝执行。

二、使用场景

1、在主页中显示最新的项目列表
    lpush 用来插入一个内容id,作为关键字存储在列表头部
    ltrim 用来限制列表中的项目条数。如果用户需要的检索的数据量超越这个缓存容量,这时才需要把请求发送到数据库。 
2、删除和过滤
    如果一篇文章被删除,可以使用lrem从缓存中彻底清除掉。
3、排行榜及相关问题
    排行榜(leader board)按照得分进行排序。zadd命令可以直接实现这个功能,而zrevrange命令可以用来按照得分来获
    取前100名的用户,zrank可以用来获取用户排名,非常直接而且操作容易。
4、按照用户投票和时间排序
    这就像reddit的排行榜,得分会随着时间变化。lpush和ltrim命令结合运用,把文章添加到一个列表中。
    一项后台任务用来获取列表,并重新计算列表的排序,zadd命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索,即使是负载很重的站点。
5、过期项目处理
    使用unix时间作为关键字,用来保持列表能够按时间排序。对current_time和time_to_live进行检索,完成查找过期项目的艰巨任务。
    另一项后台任务使用zrange...withscores进行查询,删除过期的条目。
6.计数。
    统一主键
    进行各种数据统计的用途是非常广泛的,比如想知道什么时候封锁一个ip地址。incrby命令让这些变得很容易,通过原子递增保持计数;getset用来重置计数器;过期属性用来确认一个关键字什么时候应该删除。
7.特定时间内的特定项目。
    这是特定访问者的问题,可以通过给每次页面浏览使用sadd命令来解决。sadd不会将已经存在的成员添加到一个集合。    
8.实时分析正在发生的情况,用于数据统计与防止垃圾邮件等。
    使用redis原语命令,更容易实施垃圾邮件过滤系统或其他实时跟踪系统。
9.pub/sub。
    在更新中保持用户对数据的映射是系统中的一个普遍任务。redis的pub/sub功能使用了subscribe、unsubscribe和publish命令,让这个变得更加容易。 
10.队列。
    在当前的编程中队列随处可见。除了push和pop类型的命令之外,redis还有阻塞队列的命令,能够让一个程序在执行时被另一个程序添加到队列。
    你也可以做些更有趣的事情,比如一个旋转更新的rss feed队列。
11.缓存。
    redis缓存使用的方式与memcache相同。
    
12.分布式锁
    在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

13.社交网络
    点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。

二、redis缓存使用总结

1、管道 pipeline 提升效率
    Redis是一个cs模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。
    每执行一个命令需要2个tcp报文才能完成,由于通信会有网络延迟,假如从client和server之间的包传输时间需要0.125秒,那么执行四个命令8个报文至少会需要1秒才能完成,这样即使redis每秒能处理100k命令,而我们的client也只能一秒钟发出四个命令。
    这显示没有充分利用 redis的处理能力。因此我们需要使用管道(pipeline)的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端(它能够让(多条)执行命令简单的,
    更加快速的发送给服务器,但是没有任何原子性的保证)

    //管道,批量发送多条命令,但是不支持namespace需要手动添加namespace
    Pipeline pipelined = redisClient.pipelined();
    pipelined.set(key, value);
    pipelined.get(key);
    pipelined.syncAndReturnAll(); //发送命令并接受返回值
    pipelined.sync();//发送命令不接受返回值

    使用管道注意事项: 
    1. tcp报文过长会被拆分。 
    2. 如果使用pipeline服务器会被迫使用内存队列来发送应答(服务器会在处理完命令前先缓存所有的命令处理结果)
    3. 打包的命令越多,缓存消耗内存也越多,所以并不是打包命令越多越好,需要结合测试找到合适我们业务场景的量(双刃剑) 
    4. 不保证原子性,因此在Redis中没有数据需要走DB获取数据,Redis也支持事务(multi、watch)但是会影响性能(没有事务和有事务相差还是蛮大的),不是非要强一致的场景请不要使用。

2、expire对于key过期时间来控制垃圾回收
    (LRU)如果你想更精准的控制你的数据过期,你可以用一个ZSET来维护你的数据更新程度,你可以用时间戳作为score值,每次更新操作时更新一下score,这样你就得到了一个按更新时间排序序列串,你可以轻松地找到最老的数据,并且从最老的数据开始进行删除,一直删除到你的空间足够为止。
    redisClient.setex(bizkey, 60, value);//set一个key并设置ttl60秒
3、缓存设计的误区
    如果取出为null不放入cache会有什么结果?很显然每次取cache没有走db返回null,很容易让攻击者利用这个漏洞搞垮你的服务器,利用洪水攻击让你的程序夯在这个地方导致你的正常流程抢不到资源。
4、缓存更新的问题
    正确更新缓存的Design Pattern有四种:Cache aside, Read through, Write through, Write behind caching
    (1)cache aside Pattern 模式
        失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
        命中:应用程序从cache中取数据,取到后返回。
        更新:先把数据存到数据库中,成功后,再让缓存失效。
        注意,我们的更新是先更新数据库,成功后,让缓存失效。那么,这种方式是否可以没有文章前面提到过的那个问题呢?我们可以脑补一下。
        Cache Aside套路中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。
    (2)Read/Write Through Pattern
        Read/Write Through套路是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。
    (3)Read Through
        Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。
    (4)Write Through
        Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)
    (5)Write Behind Caching Pattern
        在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,write backg还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。
        但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。
        另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。

你可能感兴趣的:(Redis总结)