Redis实战(通俗易懂,超详细攻略) V2.0版本

Redis内存数据库

更新说明:此文第一版是2019年4月份写得,当时也对Redis没有太多的认识,写了较为基础的一些东西,如今已到年末,对Redis的认识也多了很多,特此更新2.0版本。
新增的内容有:
1、排版优化
2、新增基础数据类型的底层实现
3、新增高级数据类型
4、新增了Redis扩容
5、新增Redis混合备份的方式
6、优化Redis集群主从通信的细节
7、对一些bug进行了优化
(目前更新进度80%,2019/12/11)

第二篇原创技术博客就贡献给Redis,做缓存这块,Redis内存数据库极其擅长,记得在Redis之前还有个Memcached,但支持的数据类型较少只有一种,而Redis支持多达五种数据类型。可谓长江后浪推前浪,前浪死在沙滩上了。

本文旨在让新手小白也能快速上手,所以从最基本的讲起,如果您是中手或者高手,可以跳到响应章节查漏补缺。

文章目录

1.什么是Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如
①字符串(strings),
②散列(hashes),
③列表(lists),
④ 集合(sets),
⑤有序集合(sorted sets),

与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

2.为什么要用Redis

请看上一段,Redis是内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件

  • 数据库
    Redis本质是内存数据库,所以自然可以当做数据库来使用,但要注意的是内存空间是极其有限的,可不像硬盘那样浩瀚无垠,所以大多数情况下我们还是用关系型数据库+Redis缓存的方式运用Redis
  • 缓存
    比如Mysql,可承担的并发访问量有多大呢?答案是几百左右就会扛不住了,所以我们为了支持更高的并发,会使用缓存,为数据库筑起一道护盾,让大多数请求都发生在缓存这一层。Redis是把数据存储在内存上的,访问数据速度相当快,很适合做缓存。
  • 消息中间件
    Redis支持发布/订阅消息,当然真正的MQ我们一般在Rabbit,Rocket,卡夫卡之间选一个,这并不是Redis的强项

3.Redis的使用方式

这一块分为两个部分,第一部分是把Redis部署在linux上,我们在linux使用Redis。
第二部分是通过SpringBoot来操作使用Redis。

3.1 Linux上使用

3.1.1 redis启动

1、备份redis.conf(此文件为Redis配置文件,非常重要):拷贝一份redis.conf到其他目录

2、修改redis.conf文件将里面的daemonize no 改成 yes,让服务在后台启动

3、启动命令:执行 redis-server /myredis/redis.conf(后面那个是配置文件的位置)

4、用客户端访问: Redis-cli

多个端口可以 Redis-cli –p 6379

5、测试验证: ping 若成功启动会返回 pong!

3.1.2 redis关闭

单实例关闭:Redis-cli shutdown

也可以进入终端后再关闭 shutdown

多实例关闭,指定端口关闭:Redis-cli -p 6379 shutdown

3.1.3 Redis–key/value

Redis作为Nosql数据库,数据都以键值对的形式存储,Value内置了5大数据类型,
在看Value操作之前,先看一看Key的操作

keys * 查询当前库的所有键

exists 判断某个键是否存在

type 查看键的类型

del 删除某个键

expire 为键值设置过期时间,单位秒。

ttl 查看还有多少秒过期,-1表示永不过期 (-2表示已过期)

dbsize 查看当前数据库的key的数量

Flushdb 清空当前库(慎用!)

Flushall 通杀全部库(删库跑路!!!忘了这个命令吧)

4 Redis五大数据类型

4.1 Redis五大数据类型–String

Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。

4.1.2 关于String底层实现

Redis 的字符串叫着「SDS」,也就是Simple Dynamic String。它的结构是一个带长度信息的字节数组。所以以后有人在面试的时候问你:请聊一聊SDS。就不要傻乎乎的问人家“Redis有名为SDS的数据结构么?”
在这里插入图片描述

上图为容量与长度的关系,是不是简单明了。

SDS的结构:
struct SDS {
T capacity; // 数组容量
T len; // 数组长度
byte flags; // 特殊标识位,不理睬它
byte[] content; // 数组内容
}

SDS 结构使用了范型 T,为什么不直接用 int 呢,这是因为当字符串比较短时,len 和 capacity 可以使用 byte 和 short 来表示,Redis 为了对内存做极致的优化,不同长度的字符串使用不同的结构体来表示。所以别人才那么快嘛,太精细了!
注意:创建字符串时 len 和 capacity 一样长,不会多分配冗余空间,这是因为绝大多数场景下我们不会使用 append 操作来修改字符串。

4.1.3 String常用命令

get 查询对应键值

set 添加键值对

append 将给定的 追加到原值的末尾

strlen 获得值的长度

setnx 只有在 key 不存在时设置 key 的值

incr

将 key 中储存的数字值增1

只能对数字值操作,如果为空,新增值为1

decr

将 key 中储存的数字值减1

只能对数字值操作,如果为空,新增值为-1

incrby / decrby <步长>

将 key 中储存的数字值增减。自定义步长。

mset …

同时设置一个或多个 key-value对

mget …

同时获取一个或多个 value

msetnx …

同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

getrange <起始位置> <结束位置>

获得值的范围,类似java中的substring

setrange <起始位置>

用 覆写 所储存的字符串值, 从<起始位置>开始。

setex <过期时间>

设置键值的同时,设置过期时间,单位秒。

getset

以新换旧,设置了新值同时获得旧值。

4.2 Redis五大数据类型–list

单键多值

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。

它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。操作的时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)

4.2.1 底层实现原理

上面也讲了底层是个双向链表,但还是太模糊了,具体一点,其实底层用的是ZipList(压缩链表)和QuickList(快速链表)。

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。(感觉好像数组啊,连续的内存空间)

当数据量比较多的时候才会改成 quickList。那么什么是quickList呢?Redis 将链表和 ziplist 结合起来组成了 quickList。如下图所示:在这里插入图片描述

4.2.1.1 压缩链表
  • 压缩链表结构:
    struct ziplist {
    int32 zlbytes; // 整个压缩列表占用字节数
    int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
    int16 zllength; // 元素个数
    T[] entries; // 元素内容列表,挨个挨个紧凑存储
    int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
    }
    在这里插入图片描述

    压缩列表为了支持双向遍历,所以才会有 ztail_offset 这个字段,用来快速定位到最后一个元素,然后倒着遍历。

  • Entry的数据结构:
    entry 块随着容纳的元素类型不同,也会有不一样的结构。
    struct entry {
    int prevlen; // 前一个 entry 的字节长度
    int encoding; // 元素类型编码
    optional byte[] content; // 元素内容
    }

    encoding字段是Redis自己定义的N种编码类型,罗列出来也无意义。只需要知道可以根据编码字段来确定元素内容的类型即可。

  • 增加元素:
    因为 ziplist 都是紧凑存储,没有冗余空间 (对比一下 Redis 的字符串结构)。意味着插入一个新的元素就需要调用 realloc 扩展内存。取决于内存分配器算法和当前的 ziplist 内存大小,realloc 可能会重新分配新的内存空间,并将之前的内容一次性拷贝到新的地址,也可能在原有的地址上进行扩展,这时就不需要进行旧内容的内存拷贝。

如果 ziplist 占据内存太大,重新分配内存和拷贝内存就会有很大的消耗。所以 ziplist 不适合存储大型字符串,存储的元素也不宜过多。

4.2.1.2 快速链表

先看下最基础的双向链表结构:
// 链表的节点
struct listNode {
listNode* prev;
listNode* next;
T value;
}
// 链表
struct list {
listNode *head;
listNode *tail;
long length;
}

链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率

为了解决上述问题,就引出了quicklist:
quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来。
在这里插入图片描述zipList因为是连续的内存空间,所以不需要prev 和 next 指针,空间占用大幅减少。同样,比起原来每个节点的一盘散沙,现在zipList是一坨一坨的沙砖,碎片化的问题也解决了。

  • 快速链表数据结构:
    struct quicklistNode {
    quicklistNode* prev;
    quicklistNode* next;
    ziplist* zl; // 指向压缩列表
    int32 size; // ziplist 的字节总数
    int16 count; // ziplist 中的元素数量
    int2 encoding; // 存储形式 2bit,原生字节数组还是 LZF 压缩存储

    }
    struct quicklist {
    quicklistNode* head;
    quicklistNode* tail;
    long count; // 元素总数
    int nodes; // ziplist 节点的个数
    int compressDepth; // LZF 算
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值