您的位置:首页 > 数据库 > Redis

redis -- 基本数据类型对象

2017-04-10 19:56 253 查看

redis:remote dictionary server

字面意思:远程“字典“服务器(哈哈哈哈)

由Salvatore Sanfilippo写的key-value存储系统,属于nosql的一种,Redis是一个开源的使用ANSI c语言编写,遵守BSD协议,支持网络, 基于内存但是可以实现持久的一款数据库。

redis相对于数据库家族的优势:

redis有着更为复杂的数据类型,支持set、list、hash、string等,它的一个键最大可为512M,value最大可达1G。

redis有着更高的运行速度和持久化的选择, redis是一款运行在内存的单进程单线程的异步数据库,单进程单线程并代表就low ,能读的速度是110000次/s,写的速度是81000次/s 。对于持持久化来说,它提供快照持久化和AOF持久化的方式选择设置,以及master-slave方式的数据备份,极大的方便了程序媛用户。

redis极高的支持原子化操作,redis的操作基本上都是原子化的,此外,多个操作合并后的原子性执行它也是支持的。

redis的安装:

网上教程多,它支持原码安装,看一下它的src目录下的关键文件



redis中的对象类型:

命名规则:除了
\n
和空格不能作为名字的组成外,其他都可以出现,且长度不作要求。

redis基本数据对象类型模型图:



string对象类型:

string类型的基本操作:



string对象类型是redis的基本数据类型, 但是它跟c语言里面的字符串常量或则字符数组不是同一个概念;在redis里面实现string对象有三种基本的方式:

如果string对象保存的是整数值, 并且这个值可以用long来表示, 那么redisobject里面的ptr指针从void*转变为long型, 字符串编码设置为int。这个编码方式并不是一成不变的,

当操作改变了存储对象,编码也许会变为raw。

如果string对象保存的是字符串值, 且这个字符串长度大于32字节, string对象将采用一个SDS来保存这个字符串,编码方式为raw, 调用两次内存分配,

一次是redisobject对象结构一次是sdshdr对象结构。

如果string对象保存的是字符串值, 且这个字符串长度小于32字节, string对象将采用一个embstr编码方式来保存,分配的内容跟raw编码一样,只不过它调用一次内存分配一块连续的空间,

这块空间包含两个结构体。严格意义上, embstr编码方式的字符串只是可读的,若操作改变, 则可能变为raw编码方式。

int

embstr

sdshdr – SDS

看一下SDS(简单动态字符串)的结构组成:



SDS相对应c语言字符串的优势:

1. 将获取字符串长度的工作从0(n)降到0(1)的复杂度。

2. SDS API能够降SDS的空间自动扩展,解决溢出的问题。如c的strcat不检查溢出,以及SDS的sdscat的检查并扩展容量。这也是一种节约空间的策略;

3. 空间优化策略: 起作用的前提是SDS对象的数组长度内容频繁的改变 ;利用的是SDS对象的free属性

·······a、空间预分配:优化在字符数组增长;扩展数组时,额外分配空间(小于1M翻倍分配,大于1M多加1M);利用SDS的free属性来记录这个位置。

·······b、惰性空间释放:优化字符数组缩短;缩短数组时,并不通过内存重新分配回缩,而是调整free的值,记录,以待将来对这段空间的使用。

4. 字节数组,类型多;在c的数组里面他以
\0
的方式标志结束一个字符串,内容中间不可包含空格;但是SDS中它是可以保存的,它采用二进制处理buf里面的内容,不会对其有任何的限制,存取不会造成差别,所以各种的文件类型都可以被它完好的引用。

list对象类型:

lis对象t类型的基本操作:



编码方式:

1. ziplist 列表保存的所有字符串元素长度都小于64个字节;保存的元素个数小于512个。

2. 其余情况使用linklist。

linklist

linklist是一个对双向不循环链表进行过封装的对象,两边双向一共四个方向操作,可以练两结合,因此list既可以作为栈出现也可以作为队列出现。

链表结点结构体:
typedef struct listNode{
//前驱节点
struct listNode* prev;
//后驱节点
struct listNode* next;
//节点值
void *value;
}listNode;


链表结构:
typedef struct list{
//表头结点
listNode* head;
//表尾结点
listNode* tail;
//链表长度
unsigned long len;
//结点值复制函数
void *(*dup)(void * ptr);
//结点值释放函数
void (*free)(void * ptr);
//结点值对比函数
void (*match)(void *ptr, void* key);
}list;


实现模型:



ziplist

ziplist 压缩列表, 为了节约内存而出现, 特点:是由一系列特殊比阿妈的连续内存块组成的序列, 可以包含任意多个结点。



zlbytes 记录整个压缩列表占用的内存字节数

zltail 记录压缩列表 表尾结点 距离列表起始位置的字节数

zllen 记录压缩列表包含的字节数

entry 列表结点

zlend 标记压缩列表的结束

压缩列表的结点:



结点包含三个属性:

其中previous_entry_length属性记录的是上一个结点的长度,如果上一个结点长度小于254个字节, 该属性用一个字节来保存它的长度, 否则用5个字节来保存。(利用这个属性, 可以实现向前的结点获取)

enccoding 属性标记结点保存的数据类型以及长度

content 保存结点的值

连锁更新:

当有结点的添加或着扩大修改或着删除中间小结点的时候, 可能后续结点的prvious_entry_length属性的大小由1变为5, 因此,该结点的大小如果大于254个字节。后继结点的prvious_entry_length属性应该变为5个字节,因此又引发一次空间重新分配。以此类推。

set类型:

可以实现数学里面的交,并,差集的运算,集合中的元素不重复。

set类型的基本操作:



编码方式:

1. intset 集合对象保存的所有元素都是整数值,元素数量不超过512个。

2.其他情况使用hashtable

hashtable

intset

intset 整数集合 :各个数据项在数组中按值的大小从小到大有序的排列,并且数组中不包含任何重复项。

整数集合的结构体:
typedef struct inset{
uint32_t encoding; //编码方式
uint32_t length;   //集合包含的元素数量
int8_t contents[];  //保存元素的数组
}inset;


整数集合的升级: 新添加的元素类型比整数集合现有的元素的类型要长,就要进行升级的活动。

升级三部曲:

1. 扩展底层数组的大小

2. 将底层数组现有的元素都换成与新元素相同的类型,并将转换后的元素放置到正确的位置上,这个过程保持元素的有序性。

3. 将新元素添加到底层数组里面。

注意:不存在回缩的降级。

sorted set有序对象类型:

每个元素都是一个值-权的组合,通过权值可以有序的获取集合中的元素。

sorted set的基本操作:



有序对象zset的编码方式:

1. ziplist 保存元素小于128个, 所有雅俗怒的长度都小于64个字节。

2. 其余情况采用skiplist编码。

ziplist

ziplist 方式:每个集合元素使用两个紧挨着的仪器的压缩列表结点来保存,第一个结点保存元素的成员,第二个元素爆粗元素的分值。元素在压缩列表内按分值的从小到达进行排序。

skiplist编码方式采用zset结构体的方式实现,zset结构体包含一个跳越表skiplist和一个字典。

typedef struct set{
zskiplist *zsl;
dict *dict;
}set;


skiplist – zset

使用skiplist编码的zset对象模型:



跳越表:

跳越表:
typedef struct zskiplist{
struct skiplistNode *header, *tail;
//表头结点和表尾结点
unsigned long length;  //表中结点数量
int level;    // 表中层数最大的结点的层数
}zskiplist;

跳越表结点:
typedef struct zskiplistNode{
struct zskiplistLevel{  //层
struct zskiplistNode *forward; //前进指针
unsigned int span;  //跨度
}level[];
struct zskiplistNode *backward; //后退指针
double score;  //分值
rob *obj;  //对象
}zskiplistNode;


跳越表的结构模型:



hash对象类型:

键-值对集合,每个hash可以存储40多亿个键-值对。



hansh对象的编码方式:

ziplist 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节,哈希对象保存的键值对数量小于512个,满足以上条件使用ziplist。

不满足以上条件使用hashtable编码。

ziplist

采用ziplist的hash对象,首先将保存了键的压缩列表结点压入压缩列表表尾, 然后再将保存了值的压缩列表结点压入队尾, 这样键值对的位置是相邻的。

hashtable

hashtable 编码采用字典作为底层的实现。

字典:又称符号表、关联数组或映射。

字典:
typedef struct dict{
dictType *type; //类型特定函数
void  *privdata;  //私有数据
ditch ht[2];      //哈希表, 包含两个数组项, 通常只用第一个, 第二个在进行rehash的时候使用。
int trehashind;  //rehash索引
}dict;

哈希表:
typedef struct dictch{
dictEntry **table; //哈希表数组
unsigned long size; //哈希表大小
unsigned long sizemask; //哈希表大小掩码, 用于计算索引值, 数值为size-1
unsigned long used; //哈希表已有的结点数量
}dictcht;

哈希表结点:
typedef struct dictEntry{
void *key;
union{
void *val;
uint64_tu64;
int64_ts64;
}
struct dictEntry *next; //后面连接的是哈希表结点, 将多个哈希值相同的键值对连接在一起, 以此来解决键冲突的问题。
}dictEntry;


对象模型:



解决键冲突:当两个或以上的键被分配到了哈希数组的同一个索引上的时候, 就称发生了冲突。

链地址法:将这些发生冲突的结点, 使用next指针构成一个单向的链表。以此来解决键冲突的问题。(通常将新的结点放在第一个位置, 因此添加的复杂度为 0(1))

rehash重整散列:这个动作不是一下完成的,具有渐进式的特点。

重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的制定位置上。当所有的动作完成, ht[0]变成一个空表,将ht[1]设置为ht[0],并再创建一个空的哈希表, 为下一次的rehash做准备。

rehash的步骤;

1. 为ht[1]分配空间, 让字典同时持有ht[1] ht[0]两个哈希表。

2. 在字典维护一个索引计数器变量rehashidx,并将它设为0, 表示rehash正式开始。

3. 在reshah期间, 每次对字典的操作, 程序会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当工作完成rehashidx属性的值增加一。

4. 当rehash操作完全的完成, 程序将rehashidx属性的值设置为-1;

负载因子=哈希表已保存结点数量/哈希表大小

哈希表自动进行扩展的条件:

1. 服务器目前没有进行BGSAVE 或BGREWRITEAOF,并且哈希表的负载因子大于1。

2. 服务器目前进行BGSAVE 或BGREWRITEAOF,并且哈希表的负载因子大于5。

持久化:

为了数据安全, redis会把本身的数据以文件的形式保存到硬盘中,在服务器重启之后自动吧硬盘数据恢复到内存里面。将数据保存到硬盘的过程就叫做’持久化’。

快照持久化RDB:

一次性把redis中的全部数据文件(键值对)保存一份存储在硬盘中。(适合数据量不大的情况,如10G以下)

设置快照频率的配置:



手动发起快照持久化的方式有两种
save
bgsave


save
是直接的阻塞服务器,直到工作完成,
bgsave
会启动子进程。



RDB的载入是在服务器启动的时候如果检测到它的存在就会自动载入。(如果开启了AOF持久化,则载入AOF文件不载入RDB文件)

AOF(APPEND ONLY FILE)持久化:

把用户执行的指令(添加、修改、删除)都保存到备份到文件中,还原数据的时候及时执行具体写指令。

注意:当在配置文件中开启AOF持久化的时候会清空redis数据库的全部内容,所以慎重!!

相关配置:



优化操作:



持久化的相关操作:



主从服务器:

目的:降低一个redis服务器的负载,将多个redis服务器设置为一个主服务器的slave服务器,master主服务器根slave服务器之间会进行自动的数据同步。

将一个服务器设置为其他服务器的slave服务器的方法:



all
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息