redis应用和实现

时间:2021-06-23 08:52:03

概述
NoSQL = not only SQL,全新的数据库理念,泛指非关系型数据库。
为什么使用NoSQL?
随着web2.0的网站的兴起,传统的关系数据库在应对web2.0网站,特别是大规模和高并发的SNS(社交网络服务)类型的web2.0纯动态网站已经力不从心,暴露出很多难以克服的问题,NoSQL数据库的产生就是为了解决大规模数据集合多重数据类带来的挑战,尤其是大数据难题。
NoSQL特点
1. 易扩展:数据之间没有关系特征,方便扩展。
2. 大数据量,高性能:NoSQL无关系性,数据库的结构简单。这样在大数据量下可以具有非常高的读写性能。
3. 灵活的数据模型:NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式
4. 高可用:NoSQL在不影响性能的情况,就可以方便的实现高可用的框架。比如Cassandra,HBase模型,通过复制模型也可实现高可用。
NoSQL分类
1. 键值(key-value):Redis
2. 列存储数据库:HBase
3. 文档型数据库:MongoDb
4. 图形数据库:InfoGrid
Redis定义
Redis是一个高性能的Key-value(非关系)数据库。
应用场景
缓存,任务队列,应用排行榜

Redis内部内存管理实现
redis应用和实现
redis应用和实现


Redis定义dict,dictht,dicttype和dictEntry四个结构体实现内存管理的主要功能。在Redis中,所有key-value对都存储在一个hash table中并且是一个二维结构。其中包括一个一维固定长度的数组,每个槽位上保存一个dictEntry对象。key计算hash值后按照这个数组求模,结果相同的key-value通过链表保存在同一个槽位上,这样便相成一个二维结构。需要说明的是,hash table中这个固定长度的数组能够根据key-value数量动态调整大小。
重点看一下dictEntry结构{key,v(value),next};中的v的val成员,val是一个类型为robj(RedisObject)的数据结构,其中的type标示了当前的value的数据类型(string,list,set,hash,zset),
encoding标示了当前value存储方式(ziplist,string,hash table double,list等)

Redis数据类型

redis应用和实现
string类型
string数据结构是简单的key-value类型,value其实不仅是string,也可以是数字。
redis应用和实现
常用命令:set,get,decr,incr,mget等。
应用场景:string是常用的一种数据类型,普通的key/value存储都可以归为此类,即可以完全实现目前Memcache的功能,并且效率更高。还可以享受Redis的定时持久化,操作日志及Replication等功能。
实现方式:String在Redis内部存储默认就是一个字符串,被redisObject所用,当遇到incr,decr等操作时会转成数值类型进行计算,此时RedisObject的encoding字段为int。

l ist类型
redis应用和实现
常用命令:lpush,rpush,lpop,rpop,lrange等
应用场景: Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。
Lists就是链表,相信略有数据结构知识的人都应该能理解其结构。使用Lists结构,我们可以轻松地实现最新消息排行等功能。Lists的另一个应用就是消息队列,可以利用Lists的PUSH操作,将任务存在Lists中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作Lists中某一段的api,你可以直接查询,删除Lists中某一段的元素。
实现方式:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也是使用的这个数据结构。


set类型(不允许有重复值)
redis应用和实现

常用命令:sadd,spop,smembers,sunion等
应用场景: Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
Sets 集合的概念就是一堆不重复值的组合。利用Redis提供的Sets数据结构,可以存储一些集合性的数据,比如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
实现方式:set的内部实现是一个value永远为null的hashMap,实际就是通过计算hash的方式来快速重排的,这也就是set能提供判断一个成员是否在集合内的原因。

hash类型
redis应用和实现
常用命令:hset,hget,hgetall等
应用场景:在Memcached中,我们经常将一些结构化的信息打包成HashMap,在客户端序列化后存储为一个字符串的值,比如用户的昵称、年龄、性别、积分等,这时候在需要修改其中某一项时,通常需要将所有值取出反序列化后,修改某一项的值,再序列化存储回去。这样不仅增大了开销,也不适用于一些可能并发操作的场合(比如两个并发的操作都需要修改积分)。而Redis的Hash结构可以使你像在数据库中Update一个属性一样只修改某一项属性值。
        我们简单举个实例来描述下Hash的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息:
用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:
redis应用和实现

第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。
redis应用和实现
第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。
那么Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口,如下图:
redis应用和实现
也就是说,Key仍然是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。很好的解决了问题。
这里同时需要注意,Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。
实现方式:上面已经说到Redis Hash对应value内部就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Rdis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的hash Map结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
sort set类型
redis应用和实现
注意:1. rank属性值根据score进行排行。 2. value的值需要全局唯一
常用命令:zadd,zrang,zrem,zcard等。
使用场景: Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。另外还可以用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
实现方式:sort set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的所有的成员,排序依据是HashMap里村的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
散列函数
Redis提供了三种不同的散列函数,分别是:
(1)、使用Thomas Wang’s 32 bit Mix哈希算法,对一个整型进行哈希,该方法在dictIntHashFunction函数中实现。
(2)、使用MurmurHash2哈希算法对字符串进行哈希,该方法在dictGenHashFunction函数中实现。
(3)、在dictGenCaseHashFunction函数中提供了一种比较简单的哈希算法,对字符串进行哈希。