1、 Redis概述

什么是Nosql:Nosql叫做非关系型数据库,为了解决高并发、高可用、高可扩展,大数据存储等一系列问题而产生的数据库解决方案。

Redis是使用ANSI C语言开发的一个高性能Key-Value数据库,是当今速度最快的内存型非关系型(NoSQL)数据库,可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。

Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。

2、Redis数据类型

2.1、 String 字符类型

String数据结构是简单的Key-value类型,value可以是String也可以是数字。

常用命令
get:获取key对应的vlaue
set:为一个key设置value,可配合ex/px参数设置key的有效期
getset:为一个Key设置value,并返回该key的原value
mget:获取多个key对应的value
mset:为多个key设置value
incr/incrby:将key对应的value自增1(或者指定的整型数值),并返回自增后的值。
decr/decrby:同上,自减。

应用场景:String是最常用的一种数据类型,普通的key/value存储都可以化为此类,即可以完全实现目前Memcached的功能,并且效率更高。还具有Redis的持久化,操作日志以及Replication等功能。除了和Memcached一样有get set incr decr等操作外,Redis还具有如下操作:

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

使用场景:常规key-value缓存应用。常规计数:微博数、粉丝数。

实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr ,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int

2.2、Hash 散列类型

key-value ,单点登录时采用这种数据结构存储用户信息,key为cookieId,value存放结构化对象。

常用命令
hset:将Key对应的hash中的field设置为value。如果该hash不存在,会自动创建一个。
hget:返回指定hash中field字段的值。
hmset/hmget:同hset和hget,可以批量操作同一个key下的多个field。
hgetall:谨慎使用,为完整遍历,耗时!—以数组形式返回哈希表中,所有的字段和值。紧跟每个字段名(field name)之后是字段的值 (value),所以返 回值的长度是哈希表大小的两倍。

应用场景:比如我们要存储一个用户信息对象数据,包含以下信息:

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

云数据库Redis主从版 redis数据库是干什么的_数据


方法一缺点是增加了序列化和反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。

云数据库Redis主从版 redis数据库是干什么的_Redis_02


方法二是将用户ID+对应属性整体作为唯一表示来取得对应属性的值,虽然省去序列化等操作,但是ID为重复存储,浪费内存。因此,引出Redis的Hash。

云数据库Redis主从版 redis数据库是干什么的_云数据库Redis主从版_03


Redis的Hash实际是内部存储的value是一个hashmap,并提供了直接存取这个map成员的接口。

key为用户ID,value是一个map,这个Map的key是成员的属性名,value是其属性值,这样一来,对数据的修改可直接通过内部map的key(redis称内部map的key为field),即通过key(用户ID)+field(属性标签)就可以操作对应属性数据了,既不需要重复存储数据,也不会带来徐泪花和并发修改问题。

使用场景:存储部分变更数据,如用户信息。

实现方式:Redis的Hash数据类型对应的Value内部实际就是一个HashMap,有两种不同实现:
当hash成员比较少时,Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,此时对应的value redisObject的encoding为zipmap。
当成员数量增大时,会自动转成真正的HashMap,此时的encoding为ht。

2.3、List 列表类型

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
常用命令
LPOP key :移出并获取列表的第一个元素
RPOP key :移除列表的最后一个元素,返回值为移除的元素。
LPUSH key value1 [value2] :将一个或多个值插入到列表头部
RPUSH key value1 [value2] :在列表中添加一个或多个值
LRANGE key start stop: 获取列表指定范围内的元素

应用场景:Redis list的应用场景非常多,也是redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用redis的list结构来实现。

List就是链表,使用List结构,我们可以实现最新消息排行等功能。List的另一个应用就是消息队列。

可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出来进行执行。Redis还提供了操作List中某一段的api,可以直接查询,删除List中某一段的元素。

实现方式:Redis List的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过会带来额外内存开销,Redis内部的很多实现,包括发送缓冲队等也是采用这个数据结构。

Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样list既可以作为栈又可以作为队列。

使用场景:消息队列
使用list可以构建消息队列系统。
比如将Redis 用作日志收集器

实际上还是一个队列,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。取出最新的N个数据的操作。

记录前N个最新登陆的用户ID列表,超出范围的可以从数据库取出。

//将当前登录人添加到链表
ret=r.lpush("login:last_login_times",uid)

//保持链表只有N位
ret=redis.ltrim("login:last_login_times",0,N-1)

//获得前N个最新登陆的用户ID列表
last_login_list=r.lrange("login:last_login_times",0,N-1)

举例新浪微博:
在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID会一直询问Redis,只要在start/count参数超出这个范围时候,才会去访问数据库。、

我们的系统不会像传统方式那样刷新缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其它数据库)只是在用户需要获取‘很远’的数据才会出发,而主页或者第一个评论页是不会涉及到硬盘上的数据库的。

2.4、 Set 集合类型

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

为什么不用JVM自带的Set进行去重: 一般我们的系统都是集群部署,那么使用JVM自带的set比较麻烦,需要再起一个公共服务。

常见命令
sadd
spop
smembers
sunion
应用场景:set对外提供的功能和list类似,但是set具有自动排重功能。并且set提供了判断某个成员是否在一个set集合内的重要接口,这要是list所不能提供的。
案例:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存到一个新的集合中。

Set是集合,是String类型的无序集合,set是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,set中的元素是没有顺序的。

实现方式:set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

使用场景
交集,并集,差集:(Set)

//book表存储book名称
set book:1:name    ”The Ruby Programming Language”
set book:2:name     ”Ruby on rail”
set book:3:name     ”Programming Erlang”

//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")

获取某段时间所有数据去重值

这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。

2.5、 Sorted set 有序集合类型

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

常用命令
Zadd :将一个或多个成员元素及其分数值加入到有序集合中。
Zrange :返回有序集合中指定区间内的成员。其中成员的位置按分数值递增排序。具有相同分数值的成员按字典序排列。如果你需要成员按值递减来排列,可使用 ZREVRANGE 命令。
Zrem :移除有序集合中的一个或多个成员,不存在的成员将被忽略。当 key 存在但不是有序集合类型时返回错误。
Zcard:计算集合中元素的数量。

使用场景:Redis的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级score的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构。

比如一个存储全班同学成绩的sorted set,其集合value表示学号,而score可以设置为考试得分,这样在数据插入集合的时候,就已经是排好序的。

再比如,可以用sorted set来做带权重的队列,比如普通消息的优先级为1,重要消息的优先级为2,然后工作线程可以选择按照优先级score来获取相应的任务执行。

比如twitter的public timeline可以将发表时间作为优先级进行存储,这样获取时就是自动按时间排好序的。

实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里面存的是成员到优先级score的映射,跳跃表存的是所有的成员,排序是依据HashMap里的score,使用跳跃表的结构可以获得比较高的查找效率。并且实现简单。

3、Redis应用场景

Redis是一个高性能的缓存,一般应用在以下几个场景:

  • 缓存(数据查询、短连接、新闻内容、商品内容等)
  • 分布式集群架构中的session分离
  • 任务队列(秒杀、抢购、12306)
  • 排行榜
  • 计数器
  • 最新最热文章、最近最热评论
  • 发布订阅
  • 数据过期处理
  • 网站访问统计

以上,Redis适用于数据实时性要求高、数据存储有过期和淘汰特征的、不需要持久化或者只需要保持一致性、逻辑简单的场景。

4、Redis相对MySQL关系型数据库优缺点

Redis主要用来做缓存,它有持久化,但也只是为了缓存的可靠性。
Redis是一种内存数据库,最大优点在于数据全放在内存,速度快,效率高。尤其是在某些特定场合,比如热点数据量非常大,而数据在内存和磁盘之间切换代价比较高的场景下,适合应用Redis。但缺点是数据不能超过内存大小。

传统关系型数据库在于它对数据的一致性保障,它的数据模型范式是遵循严格事物规则的结构化数据,由于其数据的高度抽象化,它调度到内存的数据一般场合下不会占用很大的内存空间。

Redis和MySQL各自有不同的业务场景,谁都无法取代谁。