为什么会选择Redis?
Redis是C语言开发的内存数据库,作为时下最常用的非关系型数据库之一,为什么会有如此多的公司选择Readis?主要包括一下几点:
-
丰富的数据类型,包括String,List,Hash,Set,zSet,根据不同的场景选择更适用的数据类型;
-
性能优势:数据存在内存,读写速度快,支持可达10W QPS;
-
高可用:支持主从复制,哨兵监听,集群部署;
-
安全性:单线程,线程安全,采用IO多路复制机制;
-
数据可靠性:数据可持久化到磁盘,重启后可重新加载到内存。
数据类型和数据结构
Redis构建了自己的对象系统,这个对象系统中包括常用的五种数据结构:String、Hash、List、Set、zSet。Redis的每个对象都是由一个redisObject结构表示:
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层实现数据结构的指针
void *ptr;
//...
}rojg;
其他对象类型就包括:String、Hash、List、Set、zSet编码和指向指定了以上5种常用的数据类型底层的实现的数据结构:
五种的常用的数据类型:
Redis的数据存储格式都是key- value的形式存在的。
String
简介:
String是最基本的数据类型,而且是二进制安全的,String可以包含所有格式的数据,包括字符串,数字,JPG图片等,最大存储可以是1G。这里和Memcached基本是一模一样的类型。
常用的命令包括:
get key value,获取key对应的字符串,如果不存在,返回nil
set key value,设置key对应类型的String类型数据,成功返回1,失败返回0;
decr key,对key的值做--操作,decr一个不存在的key,则设置key值为-1;
incr key,对key的值做++操作,返回一个新的值,需要注意的是incr一个不是int类型的value会报错,incr一个不存在的值会返回1;
mget key1,key2.....keyN,一次获取多个key的值,如果对应的key不存在,则返回nil;
mset key1 value1;key2 value2......keyN valueN,一次设置多个key的值,如果全部成功返回1,如果返回0,表示一个都没有设置成功。
适用场景:
存储简单类型的数据,比如计数等功能。
实现原理:
Redis字符串由动态字符串SDS其实是函数实现,函数中记录了字符串的长度,未使用字节长度,和保存字符串的数据
struct sdshdr{
//记录bus数组中已使用的字节长度
//等于SDS所保存字符串长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
优势:
- SDS本身保存字符串长度,提高效率:C本身获取字符串需要变量获取字符串长度需要整理遍历字符串,时间复杂度为O(n),SDS将字符串长度作为本身的属性,时间复杂度为O(1);
- 杜绝缓冲区溢出:C本身不记录自身长度,除非分配了足够的内存,否则在修改字符串时会产生缓冲区溢出的问题。而SDS的分配策略是在修改字符串长度时会首先检查字符串长度是否满足,如果的不满足,会先扩展SDS的空间,从而杜绝异常;
- 减少字符串修改时带来的内存重分配次数:在C中字符串重复修改会造成频繁的执行内存重分配操作,可能会对性能造成影响,SDS为了解决这缺陷,实现了空间预分配的机制,简单来说就是在对SDS进行空间扩展时不仅会分配必要的空间,还会分配额外的使用空间,用free表示。空间界限为1MB,如果修改后的len小于1MB,那么会分配同样大小的未使用空间,此时len和free的值相同;如果大于1MB,则会分配1MB的未使用空间。
- 惰性空间释放:当字符串缩短时,并不会将缩短的空间立即释放,而时将空间长度暂存free,已备下次使用。同时在需要时也有API提供去释放SDS未使用的空间。
- 二进制安全存储:C字符串中只能保存文本数据,不支持图片,音频等数据,而SDS已二进制处理buf数据,来支持其他数据的存储,从而达到存储,保护数据的目的,同时也兼容了C字符串以及C的部分函数。
List:
简介:
List是由双端链表实现的字符串列表,每一个子元素都是String类型,按照插入顺序排序,每一个key都是链表的名字。
//节点结构
typedef struct listNode{
//前躯节点
struct listNode*prev;
//后继节点
struct listNode*next;
//节点的值
void*value;
}
常用命令包括:
lpush key value 在key对应的list头部添加字符串元素,1表示添加成功,0表示key存在且不是list类型;
rpush key value 在key对应的list尾部添加字符串元素;
lpop key value 从list的头部开始删除元素并返回删除元素,如果key对应的list不存在或者为空返回nil,如果key对应不是list返回报错;
rpop key value 从list的尾部开始删除元素并返回删除元素,如果key对应的list不存在或者为空返回nil,如果key对应不是list返回报错;
lrange key start end 返回区间内的指定元素,下标从0开始,负值从尾部开始计算,-1表示最后一个元素,不存在返回空列表;
适用场景:
粉丝列表等列表类数据,发布、订阅的中间件
优势特点:
- 获取某个节点的前驱节点和后继节点的复杂度都是O(1);
- 双向链表的头尾节点都是null,可以判断头尾节点
- 带有链表长度的技术器
- 可以用于保存不同的数据类型
Hash
简介:
Hash 是一个键值(key-value)的集合。readis的 hash 是一个 string 的 key 和 value 的映射表,Hash 特别适合存储对象。
//节点结构
typedef struct listNode{
//前躯节点
struct listNode*prev;
//后继节点
struct listNode*next;
//节点的值
void*value;
}
常用命令包括:
hget key field value 设置hash fied为指定的值,如果key不存在,则创建;
hset key field 获取指定的hash fied
hgetall 返回Hash所有的key 和value
适用场景:
Hash由于适合存储对象,在数据范围更广,可以用来存储用户信息,商品信息等
优势:
- 相比String,hash占用更少的内存,存取整个对象更方便
- 省内存的原因:新建hash对象时先开始用zipmap来存储,会节省Hash一些本身元素数据的存储开销,当filed和value超过一定限制后,readis会将zipmap转换成正常的Hash实现,这个限制可以在配置文件中指定:
hash-max-zipmap-entries 64 #配置字段最多64个
hash-max-zipmap-value 512 #配置value最大为512个字节
set
简介:
set是存储String类型的无序集合,通过hashtable实现,set存储的元素是没有顺序且不可重复的。
常用命令包括:
sdd key member 添加一个String元素到key对应的set集合中,成功返回1,如果元素在集合中则返回0,key对应的set不存在则返回错误;
spop key 删除并返回key对应set中随机的一个元素,如果set是空或者key不存在返回nil;
smembers key 返回key在set中所有元素,结果是无序的;
sunion key1 key2 .....keyN 返回所有指定key的交集
适用场景:
可以对标list的适用场景,特殊之处是元素是不可重复的,共同好友等
优势:
set存储的是不可重复的元素,最大元素支持(2的32次方-1)个元素,由于是hash table实现,所以添加和删除元素,查询元素的时间复杂度都是O(1),hash table 会随着元素的增删自动调整大小,需要注意的是在调整大小时需要同步会阻塞其他读写(3.0之前的版本)除了常规操作,还是支持取并集,交集,差集等。
zSet
简介:
内部数据结构sorted set ,和set的指令集基本一致,但是插入的元素是不可重复的且有序。
常用命令包括:
zadd key score member 添加元素到集合,元素在集合中存在则更新对应的score;
zrange key start end 类似lrange的操作从集合指定去取指定区间的元素,返回是有序的结果
zrem key member 删除指定元素,1表示成功,如果元素不存在返回0;
zcard key 返回集合中元素的个数。
适用场景:
获取排行榜,有权重的消息队列。
优势:
sorted set 的内部使用 HashMap 和跳跃表(skipList)来保证数据的存储和有序,HashMap 里放的是成员到 score 的映射,而跳跃表里存放的是所有的成员,排序依据是 HashMap 里存的 score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。