前言redis是一个用ANSIC语言编写的开源,支持网络,可以基于内存,也可以是持久化日志类型,key-value数据库,提供API在多种语言。它是一个内存数据结构服务器,可以用作数据库、缓存和消息队列代理。通过内存中的所有数据保证高速访问,同时提供数据落地的功能,这是redis最重要的应用场景。Reids内置了不同层次的复制、Lua脚本、LRU回收、事务和磁盘持久化功能,同时通过redisSentinel提供高可用,通过RedisCluster提供自动分区。Redis支持字符串、哈希表、列表、集合、有序集合、位图和hyperloglogs等数据类型。redis最常用的数据类型:stirng、hash、list、set、sortedset、pub/sub、transactions。字符串类型字符串类型是一种简单的键值类型,值不仅是字符串,还可以是数字。常用命令:set、get、decr、incr、mget等。redis除了提供和memcached一样的get、set、incr、decr等操作外,还提供了以下操作:(1)获取长度细绳;(2)将内容追加到字符串中;(3)设置和获取字符(4)设置和获取字符串的某个位(bit);(5)批量设置一系列字符串的内容;哈希类型哈希特别适合存储对象。常用命令:hget、hset、hgetall等。应用场景:存储一些结构化数据,比如用户的昵称、年龄、性别、积分等,存储一个用户信息对象数据。下面举一个简单的例子来描述Hash的应用场景。例如,我们存储一个用户信息对象data,它包含以下信息:(1)用户id是搜索的key;(2)存储的值包括姓名、年龄、生日等信息实例分析:(1)key是userid,value是一个Map。(2)这个Map的key是成员的属性名,value是属性值;(3)这样就可以直接通过内部Map的key(内部Map的key在redis中称为field),即key(用户名id)+field(属性name)来操作相应的属性数据。注意:(1)Redis提供了一个接口(hgetall)可以直接获取所有的属性数据,但是如果内部Map的成员比较多,就会涉及到遍历整个Map的操作。(2)由于redis的单线程模型,这种遍历操作可能比较耗时,其他客户端的请求根本无法响应,需要注意。列表类型列表类型本质上是一个双向链表,其中每个元素都是一个字符串类型,这使得列表既可以用作堆栈,也可以用作队列。列表类型常用于消息队列服务中,完成多个程序之间的消息交互。常用命令:lpush、rpush、lpop、rpop、lrange等。lpush插入到链表的左边,也就是表头;rpush插入到链表的右侧,也就是尾部;lrange(key,start,end)返回指定范围内的元素,从头(左端)开始到尾(右端)范围。应用场景:实现最新消息排行榜等功能,以及消息队列。简单消息队列实例分析:(1)假设一个应用程序执行lpush向链表添加新元素,我们通常称这样的程序为“生产者”;(2)当另一个应用程序正在执行rpop操作时,从链表中提取元素,我们称这样的程序为“消费者”;(3)消费者在消费消息的过程中,需要不断调用rpop检查列表中是否有待处理的消息。每次调用都会发起一个链接,造成不必要的浪费。(4)另外,如果生产者的速度大于消费者的速度,消息队列的长度会一直增加,时间长了会占用大量的内存空间;(5)因此,可以使用brpop命令,只有当有元素时才返回。如果不是,它将阻塞直到超时返回null。集合类型集合类型是字符串类型的无序集合。集合集合的概念是一堆唯一值的组合。set元素最多可以包含(2的32次方-1)个元素。set的内部实现是一个HashMap,其值始终为null。set对外提供的功能和list类似,都是列表功能。特别之处在于设置时可以自动对重复项进行排序。常用命令:sadd、spop、smembers、sunion等。当你需要存储一个数据列表,又不想重复数据时,set是一个不错的选择。而set提供了判断一个成员是否在set集合中的重要接口,这是list无法提供的。使用集合数据结构,可以存储一些集体数据。例如,在微博应用中,一个用户的所有关注者可以存储在一个集合中,所有的粉丝可以存储在一个集合中。Redis还提供了集合的交集、并集、差集等操作,可以很方便的实现共同关注、共同偏好、二度好友等功能。Zset类型与set相同,sortedset也是stirng类型元素的集合。不同的是每个元素关联一个double类型的score,元素的顺序由score决定。有序集合是插入有序的,即自动排序。常用命令:zadd、zrange、zrem、zcard等。zRange(key,start,end)按照分数从低到高的顺序返回指定范围内的所有元素;zRevRange(key,start,end)按照score所有元素从高到低的顺序返回key对应的有序集合中指定区间的顺序。当你需要一个有序且不重复的集合列表时,你可以选择sortedset数据结构。应用实例:(1)比如存储全班的成绩,setvalue可以是学生的学号,score可以是成绩。(2)排行榜应用,根据得分列出topN用户等。pub/subscribe、unsubscribe、publish这三个命令实现了发布和订阅泛型。发送者(发送信息的客户端)并不直接将信息发送给具体的接收者(接收信息的客户端),而是将信息发送给通道(channel),然后通道将信息转发给所有那些对频道感兴趣。感兴趣的订阅者。发送者不需要知道订阅者的任何信息,订阅者也不需要知道哪个客户端向它发送信息,它只需要关注它感兴趣的通道。发布/订阅被设计得非常轻量级在redis中简洁。实现了消息发布和订阅的基本能力;但是,它还没有提供消息持久化等各种企业级功能。一个redis客户端发布消息,其他redis客户端订阅消息。发布的消息被发送并立即丢失。Redis不会持久化发布的消息;消息订阅者只能获取到订阅的消息,无法获取到频道中之前的消息。.消息发布者,即发布客户端,不需要独占链接。可以在发布消息的同时使用同一个redis-client链接进行其他操作(如incr等);消息订阅者,即订阅客户端,需要独享链接。即在订阅期间,redis-client不能穿插其他操作。此时客户端以阻塞的方式等待发布者的消息,所以订阅需要使用单独的链接,甚至需要在额外的线程中使用。tcp默认的连接时间是固定的。如果sub端没有收到这个世界的pub端消息,或者没有pub端产生的消息,sub端上的连接会被强制回收。这需要特殊的手段来解决。定时器用于模拟pub和sub之间的保活机制。定时器时间不能超过tcp的最大连接时间。一旦订阅端断开连接,部分消息会丢失,即链路故障期间的消息会丢失,所以这里需要考虑redis链表进行持久化;如果你对每条消息都非常在意,那么你应该在基于redis的基础上做一些额外的补充工作,如果你希望订阅持久化,那么可以参考下面的设计思路:(1)订阅端:先添加“订阅者id”到一个集合,这个集合保存“活跃订阅者”;Subscriberid标记每个唯一的订阅者,这个集合是“活跃的订阅者集合”。(2)订阅端启动订阅操作,基于redis创建一个以订阅者id为key的列表数据结构。此列表存储所有未使用的消息。这个列表叫做“订阅者消息队列”;(3)发布端:每条消息发布后,发布端需要遍历活跃订阅者集合,将发布的消息依次追加到每个“订阅者消息队列”的末尾;(4)至此,我们基本可以保证,每条发布的消息都会被持久化存储在各个“订阅者消息队列”中;(5)订阅端,每收到一条订阅消息,必须在消费周后删除自己的“订阅者消息队列”(6)订阅端启动时,如果发现自己的“订阅者消息队列”中有残留记录messagequeue”,它会先消费这些消息,然后订阅。上面的方法可以保证成功到达的消息一定被消费掉,不会丢失。事务Redis事务可以一次执行多个命令。一个事务从启动到执行要经历三个阶段:(1)启动事务(2)入队命令(3)执行事务事务是一个单独的隔离操作:事务中的所有命令都会被序列化并依次执行。交易执行过程中,不会被其他客户端发送的命令请求打断。单个redis命令的执行是原子的,但是redis并没有在事务中加入任何维护原子性的机制,所以redis事务的执行不是原子的。事务可以理解为封装好的批量执行脚本,但是批量指令不是原子操作,中间某条指令失败不会导致前面指令的回滚,也不会导致后面的指令不执行。multi、exec、discard和watch命令是redis事务的基础。multi:(1)multi命令用于启动一个事务,它总是返回ok。(2)multi命令执行后,客户端可以继续向服务器发送任意数量的命令;(3)这些命令不会立即执行,而是放在一个队列中;(4)当exec命令被调用时,所有排队的命令都会被执行。exec:(1)exec命令负责触发并执行事务中的所有命令;(2)如果客户端在使用multi开启一个事务后,由于连接断开导致无法执行exec命令,则执行该事务中的所有命令。不会被执行。(3)另一方面,如果客户端在开启事务后成功执行了exec命令,则该事务中的所有命令都会被执行。discard:(1)通过调用discard,客户端可以清空事务队列,放弃执行事务。watch:(1)watch命令可以为redis事务提供check-and-set(CAS)行为。(2)watch使exec命令有条件地执行:只有在所有被监控的key都没有被修改的前提下才能执行事务。如果不能满足这个前提,交易将不会被执行。(3)如果用watch监控有过期时间的key,即使key过期了,事务还是可以执行的。(4)watch可以被多次调用,watch执行后健康度的监控才会生效,直到调用exec。(5)调用exec时,无论事务是否执行成功,都会取消对所有健康度的监控。(6)当客户端断开连接时,客户端对健康的监听也会被取消。
