文章目录
前言
数据库,了解数据库的基本知识都为之后职业发展提供了有力的支持,所以一起学习起来吧。这一系列主要分享我自己学习的总结,因为目前日常工作涉及数据库较少,主要还是学习他人讲解视频,看代码的方式自学为主。过程中有问题,欢迎大家指点讨论。不断更新中…
一、Redis是什么?
Redis(Remote Dictionary Server),开源高性能键值对数据库。非关系型数据库(NoSQL)的一种,作为关系型数据库的一种补充支持,处理的数据面向内存,保证了大数据高并发下的性能。
特点:数据间无必然联系、单线程机制、高性能、5种数据类型支持(字符串string、列表list、散列hash、集合set、有序集合类型sorted_set)、持久化支持。
应用:热点搜索、抢购排队、公交到站等及时信息、分布式数据共享、分布式锁
基础操作
set/get
clear//清楚屏幕显示
help//帮助指令,后接空格+tab键,给出可能想查看的推荐;
help @string//看string群组相关的所有方法
help set//跟command,查看该命令使用方法
二、数据类型
Redis中所有的key都只能是string类型,但value类型可以不同,常见的Redis数据类型:string、hash、list、set、sorted_set。
//key命名规范
表名 : 主键名: 主键值 : 字段名
eg:order : id : 89757 : name
set user:id:89757:bolgs 666
set user:id:89757:fans 888
or
set user:id:89757 {blogs:789,fans:888}
1.string类型
(1)string底层实现
Redis自己构建了一种简单动态字符串SDS。如使用set [key][value]的时候,key和value都是SDS。接下来我们尝试简单阅读源码(版本redis-7.0.11)
本人认为SDS和普通的C字串的最大区别在于,为了减少字符串增长、收缩或者拼接重新分配内存的次数,以及C字符串可能存在的缓存区溢出的可能性,SDS作为Redis自己构建的字符串,内存大小支持动态的变化,这要求SDS首先得时刻知道自己的字符串大小。
......
//SDS有许多不同的实现类型(在初始化的时候也可以看到)但基本内存布局是一致的,以下是其中一种sdshdr64
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; //包含着当前SDS存储字符串的大小
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
//SDS的初始化函数(只保留关注的代码)
sds _sdsnewlen(const void *init, size_t initlen, int trymalloc) {
sh = trymalloc?
s_trymalloc_usable(hdrlen+initlen+1, &usable) :
s_malloc_usable(hdrlen+initlen+1, &usable);//hrdlen是当前选择初始化的SDS类型在内存中的大小
sh->len = initlen;//通过结构体记录当前字符串大小
s = (char*)sh+hdrlen;//字符串被记录的实际位置是当前申请内存偏移hdrlen个字节开始的
s[initlen] = '\0';//和C字符串一致,以‘\0’作为结尾
return s;
}
......
粗略地说,首先根据申请的字符串大小决定要使用的结构体(sdshdr64 、sdshdr32等),然后分配结构体大小+字符串长度大小+1的内存块。放入结构体字符串末尾补零后完成SDS初始化。(alloc表示字符串总共申请的大小,后期可用大小可以通过它与length的差值算得)
在此基础上看看Redis是如何使用SDS拼接字符串的
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sds sdscatlen(sds s, const void *t, size_t len) {
size_t curlen = sdslen(s);//获取SDS存储字符串的长度
s = sdsMakeRoomFor(s,len);//为即将拼接字符串先确保足够的内存大小
if (s == NULL) return NULL;
memcpy(s+curlen, t, len);//从当前字符串之后开始拼接字符
sdssetlen(s, curlen+len);//设置SDS记录字符串的长度
s[curlen+len] = '\0';
return s;
}
//sdsMakeRoomFor函数内部调用_sdsMakeRoomFor(只保留关注代码)
sds _sdsMakeRoomFor(sds s, size_t addlen, int greedy) {
size_t avail = sdsavail(s);//返回可用大小
if (avail >= addlen) return s;//当前大小已经够用了,直接返回
len = sdslen(s);
sh = (char*)s-sdsHdrSize(oldtype);
reqlen = newlen = (len+addlen);//当前大小和待添加大小相加得到请求大小
//greedy为1表示要留后手,内存多申请些。为0则表示申请刚刚好即可
if (greedy == 1) {
if (newlen < SDS_MAX_PREALLOC)
newlen *= 2;//请求的大小小于1Mb,新大小直接*2
else
newlen += SDS_MAX_PREALLOC;//大于等于1Mb,直接加1Mb
}
//判断类型是否相同,也就是SDS初始地址是否需要更换
if (oldtype==type) {
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
s = (char*)newsh+hdrlen;
} else {
newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
sdssetlen(s, len);
}
sdssetalloc(s, usable);
return s;
}
通过阅读以上代码可以看出,SDS在做拼接字符串的时候,考虑到字符串可能频繁得更改是做了预分配的优化的,这样带来的好处是当再次拼接字符串有较大概率现有内存满足需求,从而避免了频繁申请内存带来的性能开销。
同样的惰性释放空间也可以从一定角度解决上面提到的问题。
注意:string的最大存储大小512MB
(2)string常见指令/扩展指令
//常见指令,操作成功返回integer 1,失败返回integer 0
set [key] [value]
get [key]
del [key]
mset [key][value] [key][value] //一次性插入多个数据,和set指令比主要减少了多次请求往返耗时
mget [key][key][key]
strlen [key] //得到value字符长度
append [key]
//扩展指令,这些对string类型进行整数或者单精度的加减都是原子操作,内部转换为数值后进行计算
incr [key] //对数据进行整数+1,不支持ASCI转译
incrby [key] [increment] //increment只能是整数,负数也可以。按照指定的大小增加
incrbyfloat [key] [increment] //increment额外支持小数
decr [key]
decrby [key] [increment]
//设置数据生命周期,在生命周期期间重新set,会让时钟失效;但是只是append,时钟结束数据还是会丢失
setex [key][seconds][value]
psetex [key] [milliseconds] [value]
2.hash类型
将一个key对应一堆数据,其中一堆数据也是field-value的映射。
如果field数量较少,存储结构优化为类数组结构,如果数量较多,使用HashMap结构。
hash类型下只能存储string类型,不允许嵌套hash的操作。
与string的区别,一般string主要应用于JSON整体,读为主,hash主要应用于更新,但都不绝对。
(1)hash底层实现
(2)hash常见指令/扩展指令
//常见操作
hset key field value //设置或修改单个field数据
hget key field
hsetall key field//field和value全部返回,filed占据单数位置,**注意如果内部field过多,全部遍历极影响效率,会成为效率瓶颈**
127.0.0.1:6379> hgetall user01
1) "name"
2) "zhangjunbao"
3) "age"
4) "28"
5) "weight"
6) "65"
7) "height"
8) "178"
hdel key field [field ...]//可删除单个或多个field
hmset key field value [field value ...]
hmget key field [field ...] //此时返回的不带field
127.0.0.1:6379> hmget user01 name age
1) "zhangjunbao"
2) "28"
hlen key //返回field的个数
hexists key field //查看指定filed是否存在,存在返回1,否则0
//扩展操作
hkeys key //返回key所有的field,不可能重复
hvals key //返回key所有的value,可能重复
hincrby key filed increment //没有hdecrby,增加负数实现减
hincrbyfloat key filed increment
hsetnx key field value//filed存在不重复写入了
2.list类型
存储多个string类型数据,并对数据的顺序进行区分,底层使用双向链表存储结构实现。
具有索引的概念,可以使用index查询数,操作可以实现队列或者栈的功能;索引结束为-1。
可以日志实现消息队列等。
(1)list底层实现
(2)list常见指令/扩展指令
//常见操作
lpush key value [value ...] //从左边头插法添加val1、val2、val3...
rpush key value [value ...] //想要实现abc顺序填入就使用rpush即可
lrange key start stop //start开始索引,从0开始,忘记结束的索引可以写-1,代表倒数第一个
lindex key index //从左查看索引为index的value,没有rindex,从最后查看索引填-1即可
llen key //查看共有多少元素
lpop key //从左边弹出第一个元素
rpop key
//扩展操作
lrem key count value //从左往右移除指定个数的 value值
4.set类型
和hash很类似
(1)set底层实现
(2)set常见指令/扩展指令
//常见操作
sadd key member [member ...]
smembers key
srem key member [member ...]
scard key //获取集合数据总量
sismember key memeber //判断集合中是否包含指定数据
//扩展操作
srandmember key [count] //随机给出set中指定个数的成员
spop key [count] //随机弹出set中指定个数的成员