六:分布式架构存储设计

一. 单机存储系统介绍

1.什么是存储引擎 ???

数据库存储引擎是数据库底层软件组件,数据库管理系统使用数据引擎进行创建、查询、更新和删除数据操作。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能
性能因素
(1)写入方式
顺序写、随机写
(2)读取方式
随机读、顺序扫描

2. 单机存储引擎

2.1 hash存储引擎

代表数据库:redis、memcache等
优点
1)时间复杂度0(1)
就是最低的时空复杂度了,也就是耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。哈希算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标(哈希冲突不考虑)
这里列举缺点
1)Hash 索引仅仅能满足"=",“IN"和”<=>"查询,不能使用范围查询。
2)Hash 索引无法被用来避免数据的排序操作。
3)Hash 索引不能利用部分索引键查询。
4)Hash 索引在任何时候都不能避免表扫描。

综合评价:
通常也常见于其他存储引擎的查找速度优化上。 Hash 索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引。虽然 Hash 索引效率高,但是 Hash 索引本身由于其特殊性也带来了很多限制和弊端。
Hash碰撞,就是链式扫描:
由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果。
(5)Hash 索引遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高

2.2 B树存储引擎

代表数据库:MongoDB、mysql(基本上关系型数据库)等
b+树存储引擎特性
1)根节点常驻内存,最多h-1次磁盘io
2)非页字节点容纳尽量多的元素
3)擅长范围查询
4)随机写问题
5)节点分裂

2.3 lsm存储引擎

代表数据库:nessDB、leveldb、hbase等
lsm存储引擎特性
1)随机变顺序写
2)牺牲读性能
3)增加后台合并开销
综合评价:
核心思想的核心就是放弃部分读能力,换取写入的最大化能力。LSM Tree ,这个概念就是结构化合并树的意思,它的核心思路其实非常简单,就是假定内存足够大,因此不需要每次有数据更新就必须将数据写入到磁盘中,而可以先将最新的数据驻留在磁盘中,等到积累到最后多之后,再使用归并排序的方式将内存内的数据合并追加到磁盘队尾(因为所有待排序的树都是有序的,可以通过合并排序的方式快速合并到一起)。
lsm存储引擎优化:
1)有布隆过滤器快速判断
2)文件内数据有序

3 存储模型

数据模型是存储系统外壳,描述存储系统以什么样的形式存储或管理数据
1)关系数据
就是传统的数据库,如mySql,Oracle, postgresql,最明显的特点就是把一行中的数据值串在一起存储起来,然后再存储下一行的数据。
2)键值数据
它是NoSQL存储的一种方式。它的数据按照键值对的形式进行组织,索引和存储redis、MongoDB
3)时序
可以用来监控
4)图
图形数据库可用于对事物建模,如社交图谱、真实世界的各种对象,Neo4j是一个典型的图形数据库。

3.1 键值数据模型之redis原理分析

redis是用c语言开发的一个开源的高性能键值对(key-value)内存数据库

3.1.1 redis数据储存结构原理

Redis的数据库使用字典作为底层实现,数据库的增、删、查、改都是构建在字典的操作之上的。
redis服务器将所有数据库都保存在服务器状态结构redisServer(redis.h/redisServer)的db数组(应该是一个链表)里:

struct redisServer {
//…
// 数据库数组,保存着服务器中所有的数据库
redisDb *db;
//…
}

在初始化服务器时,程序会根据服务器状态的dbnum属性来决定应该创建多少个数据库:

struct redisServer {
// …
//服务器中数据库的数量
int dbnum;
//… }

切换数据库原理
每个Redis客户端都有自己的目标数据库,每当客户端执行数据库的读写命令时,目标数据库就会成为这些命令的操作对象。

127.0.0.1:6379> set msg ‘Hello world’ OK
127.0.0.1:6379> get msg “Hello world”
127.0.0.1:6379> select 2 OK
127.0.0.1:6379[2]> get msg (nil)
127.0.0.1:6379[2]>

在服务器内部,客户端状态redisClient结构(redis.h/redisClient)的db属性记录了客户端当前的目标数据库,这个属性是一个指向redisDb结构(redis.h/redisDb)的指针:

typedef struct redisClient {
//…
// 客户端当前正在使用的数据库
redisDb *db;
//… } redisClient;

redisClient.db指针指向redisServer.db数组中的一个元素,而被指向的元素就是当前客户端的目标数据库。
我们就可以通过修改redisClient指针,让他指向服务器中的不同数据库,从而实现切换数据库的功能–这就是select命令的实现原理。
实现代码:

int selectDb(redisClient *c, int id) {
// 确保 id 在正确范围内
if (id < 0 || id >= server.dbnum)
return REDIS_ERR;
// 切换数据库(更新指针)
c->db = &server.db[id];
return REDIS_OK; }

数据库的键空间
数据库的结构(我们只分析键空间和键过期时间)

typedef struct redisDb {
// 数据库键空间,保存着数据库中的所有键值对
dict dict; / The keyspace for this DB */
// 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
dict expires; / Timeout of keys with a timeout set /
// 数据库号码
int id; /
Database ID /
// 数据库的键的平均 TTL ,统计信息
long long avg_ttl; /
Average TTL, just for stats */
//… } redisDb

key对存储效率的影响
key越小越好,为什么???
因为key越大就会浪费越多的管理空间

在这里插入图片描述

上图是一个RedisDb的示例,该数据库存放有五个键值对,分别是sRedis,INums,hBooks,SortNum和sNums,它们各自都有自己的值对象,另外,其中有三个键设置了过期时间,当前数据库是服务器的第0号数据库。现在,我们就从源码角度分析这个数据库结构:
我们知道,Redis是一个键值对数据库服务器,服务器中的每一个数据库都是一个redis.h/redisDb结构,其中,结构中的dict字典保存了数据库中所有的键值对,我们就将这个字典成为键空间。
Redis数据库的数据都是以键值对的形式存在,其充分利用了字典高效索引的特点。
a、键空间的键就是数据库中的键,一般都是字符串对象;
b、键空间的值就是数据库中的值,可以是5种类型对象(字符串、列表、哈希、集合和有序集合)之一。
数据库的键空间结构分析完了,我们先看看数据库的初始化。

3.1.2 redis内存清理

其实就是删除key,删除过期的key
定期删除
遍历过期字典表,但是redis是单线程的如果他要划分大部分的资源在循环过期的链表(dictientry)或得不偿失,下面的图是redis的思路
在这里插入图片描述
周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
特点1:CPU性能占用设置有峰值,检测频度可自定义设置
特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
总结:周期性抽查存储空间 (随机抽查,重点抽查)
惰性删除
数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,
返回数据 ;发现已过期,删除,返回不存在。
优点:节约CPU性能,发现必须删除的时候才删除
缺点:内存压力很大,出现长期占用内存的数据
总结:用存储空间换取处理器性能(拿空间换时间)
lru
场景:我分配的内存不够用了,我需要淘汰一些数据才能正常工作
怎么做这个lru链表???
在一串dictentry随机找4个然后在4个里面找最久没有使用的,思想和定期删除一样
在这里插入图片描述
一般用的第一种,就是从过期的数据中删除

lfu
在这里插入图片描述

3.1.3 数据固化方式

(一)rdb快照方式
1)手动方式
sacve:阻塞当前redis服务器,直到rdb过程完成为止
2)自动方式
bgsave:fork子线程,持久化由子线程负责,完成自动结束
配置项:save m n秒存在n次写入,触发bgsave

优点:对redis性能影响低,二进制压缩文件体积小,数据恢复
**缺点:**有数据丢失可能,备份文件版本兼容问题
rdb快照方式的过程
1)写临时文件
2)覆盖老文件
在这里插入图片描述
(二)aof持久化
append only file 日志形式记录写入指令,实时持久化
在这里插入图片描述
aof持久化的过程
1)命令写入
2)文件同步
在这里插入图片描述
3)文件重写

  • 整理压缩aof文件,
    • 整理方式(过期数据,重复更新,删除数据,命令聚合)

4)文件重写怎么做
手动触发
被动触发
5)aof文件重写的一个过程
fork子进程不影响父进程处理请求
子进程双写,确保数据不丢失

思考:
这个aof持久化的过程会不断的刷盘追加因为他是实时的会不会导致append only of文件越来越大,其次有很多无效的数据比如有一个key我先add进去,然后一直update但是最后一个update才是最终想要的,那么我做aof恢复的时候就会把前面无效的update都恢复,所以才有了文件重写
AOF文件的优化策略:
1)Bgrewriteaof命令 - 手动触发后台对aof文件的重写,重写后会得到一个体积优化版
2)redis会自动的触发AOF文件重写

redis重启恢复过程
在这里插入图片描述

3.1.4 redis可靠性保障方式

3.2.3 .1 主从模式
 当从节点启动后,会向主数据库发送SYNC命令。同时主数据库收到SYNC命令后会开始在后台保存快照(即RDB持久化,在主从复制时,会无条件触发RDB),并将保存快照期间接收到的命令缓存起来,当快照完成后,redis会将快照文件和所有缓存命令发送给数据库。从数据库接收到快照文件和缓存命令后,会载入快照文件和执行命令,也就是说redis是通过RDB持久化文件和redis缓存命令来时间主从复制。一般在建立主从关系时,一次同步会进行复制初始化
在这里插入图片描述

1)全量同步
2)增量同步
3)运行id
(master启动时生成,同步给salve)
4)复制偏移量
(确定同步位置)
主从模式数据同步两个过程,一个增量同步包括了包含镜像和缓存的命令,然后增量同步,slave,master怎么知道你呢?需要同运行id根据偏移量复制数据就是我slave断了在起来把id给你 根据偏移量同步数据,在创建快照过程的时候需要缓存命令,加载快照过程的时候也需要缓存命令,执行的时候可能也会缓存命令。在流量特别高的时候没头永远追不上,比如路由的时候缓存在线数据备份slave挂了,就会出现这种情况
3.2.3.2 哨兵模式
在这里插入图片描述
1)监控
(不断的检查master和slave是否运作正常),
2)提醒(发现故障通知管理员和客户端),
3)故障转移(自动主从切换,更新配置信息),
4)集群配置信息提供(客户端应用初始化连接sentinel获取节点信息)
流程
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用:
1)通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器
2)当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

哨兵模式架构
在这里插入图片描述
哨兵模式的优缺点
优点:

  • 哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。
  • 主从可以切换,故障可以转移,系统可用性更好。
  • 哨兵模式是主从模式的升级,系统更健壮,可用性更高。
    缺点:
  • Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
  • 配置复杂
    3.2.3.3 集群模式
    在redis的每一个节点上,都有这么两个东西,一个是插槽(slot)可以理解为是一个可以存储两个数值的一个变量这个变量的取值范围
    还有一个就是cluster我个人把这个cluster理解为是一个集群管理的插件
    在这里插入图片描述
    优点:无需sentinel 哨兵,支持水平扩容,自动转移
    缺点:不支持批量操作,比如我同时查多个key,他不知道落在哪组主从里,就没办法批量,还有我客户端需要去找那个槽需要根据key计算,也不知道哪个集群对应那个槽,但是我知道哪个key对应是那个槽位,但是槽那么多。所以通过客户端路由,所以他会计算槽位和对应节点最少会有两次交互,存在性能问题
    在这里插入图片描述
    (3.1)集群模式-codie
    对性能需求特别高的就用codie对固化需求的就用固化方式zzkv
    Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别 (有一些命令不支持), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务
  • 数据分片策略对客户端透明
  • Zookeper作为name server
  • 提供支持redis协议的proxy
  • proxy负责请求路由
    在这里插入图片描述

3.2 mysql innodb存储原理与技术分析

3.2.1 mysql记录存储

在这里插入图片描述
1)页头
page header 空间不大,共占56个字节,记录一些信息包括页面的指针,页面的空间使用情况
2)虚记录
表示记录的范围,最小和最大之间他有可能存在页里面
3)记录堆
里面有语句分配的,写进数据了,还有一种已经写进去被删除的,然后就是未分配
4)自由空间链表
被删除会分配一个链表去管理
5)未分配空间
6)slot区
7)页尾
占8个字节,主要存储页面校验信息
页内记录维护
在这里插入图片描述
mysql的innodb是用B+树索引,那么他的顺序是怎么保证的插入策略是怎么插入优先怎么插入的,业内的查询。
1)顺序怎么保证
物理连续和逻辑连续(用链表连接起来):如下图
如果我们设计数据库是用物理连续还是逻辑连续我们肯定需要减少数据移动,所以页内肯定逻辑连续,看这个自由空间链表,删除的留下来并且是个链表的目的不就是继续往里插数据嘛,如果是物理连续这个空间就没办法利用了,肯定要移动的
在这里插入图片描述

2)插入策略
(自由空间链表和未使用空间)
我为什么要把这些已经删除的记录啊也删除记录放在一个空闲空间列表里。就是要把这些空闲的空间利用起来。我要优先用它。不行,我再去未使用的空间里分配。这样才能高效的利用这一块。空间为什么要高效利用这点空间呢?我们的内存是以page为单位加载在内存,如果页面没有插数据,以前插过但是全删了,只有两条有效数据,加载进内存的时候这快内存大部分浪费掉了。我们插入策略不管做到什么效果都尽量把自由空间链表里的无效数据的空间利用上。

3)页内查询
(遍历,二分查找)
首先遍历用二分查找比较快。
变长数据存储-innodb
pagesize 16kb
每页至少两条数据
最多存储10个大字段
varcher

3.2.2 索引原理分析

聚簇索引
在这里插入图片描述

聚簇:表示数据行和相邻的键值紧凑地存储在一起。一个表只能有一个聚簇索引
(1)数据存储在主键索引上
(2)数据按主键顺序存储
(3)自增主键vs随机主键
自增主键没有分裂的情况,是顺序写;
二级索引
在这里插入图片描述
data不是真实记录了,他是pk主键值,查找的时候找的是主键,最终要拿数据的时候要回聚簇索引才能拿到
(1)除主键索引以外的索引
(2)叶子中存储主键值
(3)一次查询需要走两遍索引
(4)主键大小会影响所有索引的大小
联合索引
在这里插入图片描述
他是有两个key最左匹配原则,然后最终的叶字节点对应的二级索引。
索引实现原理
1)存储空间
索引文件大小是由key决定的
字段大小决定页内节点索引个数,页内节点索引个数决定数的数量
2)主键选择
自增主键:顺序写入,写入磁盘利用效率高,每次查询走两级索引,
随机主键:写入磁盘利用效率低,每次查询走两级索引
业务主键:写入,查询磁盘利用率都高,可以使用一级索引
联合索引:影响索引大小,不易维护,不建议使用
3)联合索引使用
按索引区分度排序;覆盖索引
4)字符串索引
设置合理长度;不支持%开头的模糊查询

3.2.3 innodb内存管理

在这里插入图片描述
(1)预分配内存空间
为什么需要预分配空间,因为不能用一页分配一页这样频繁的创建销毁代价比较大,所以需要预分配空间,这样就减少了创建和,回收
(2)数据以页为单位加载
(3)数据内存交换
我一个空页或者不用的页,我把数据加载这里里面我修改update的时候数据变更,是需要刷回磁盘的;有内外存的交换内存到磁盘或者磁盘到内存

innodb内存管理-技术点
1)内存池
思考:分配内存池怎么管理和使用???
需要内存页面管理,那我磁盘一块数据加载到磁盘对应的内存是哪一块,这两个需要映射关系页面映射。然后对应的哪一块磁盘有没有更新都需要页面数据管理
我磁盘有这么大,我数据库一直用着,内存池有可能会满了我加载新数据的时候就需要数据淘汰,或者不太经常用的时候
2)页面管理:分为3种状态我把他分成了空闲页没有数据,有数据但是还没有写可以随时刷盘淘汰掉,还有一种就是脏页已经写进了内存但是我更新了我的磁盘对应的数据不一样了,我淘汰掉就只能先刷盘把更新写回文件然后淘汰掉在写回新的数据
3)页面淘汰:页面用完了所有内存都分配完了,我需要加载新的数据的时候没地儿放,需要淘汰这种数据淘汰,这和redis可以用lru去做,如果什么都不考虑,我们把所有的页面管理起来,可以用一个数据结构管理界面啊,每一个节点代表一个页面形成链表通过头尾指针把最久没有使用的淘汰掉
思考:全表扫描对内存的影响 ? ? ?
会导致缓冲区会被表来的数据全部填满,页面淘汰是淘汰冷数据,这个时候内存里面的就全是热数据性能肯定会下来,那怎么避免热数据被淘汰数据库性能好坏取决于磁盘数据io,那就得看他的内存管理
在这里插入图片描述
4)mysql内存管理
在这里插入图片描述
5)mysql内存管理-lru
在这里插入图片描述
在这里插入图片描述
没有空闲页怎么办???
从fielist中取 -》 lru中淘汰 -》 lruflush中取一个

3.2.4 mysql 事务基本概念

1)事务特性
2)并发问题
3)隔离级别
mysql的事务实现原理
(一)mvcc
1)当前读
2)快照读
有了mvcc可以帮我们在多版本,同一条记录可以读到不同的值他是怎么做的:通过可见性判断,read view
可见性判断
在这里插入图片描述

read view(快照读,活跃事务列表;列表中最小事务id;列表中最大事务id)
多版本是怎么存储的是通过undolog做的
(二)undolog
1)回滚日志
2)保证事务原子性
3)实现数据多原本
实现事务持久性是通过redolog
(三)redolog
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
你既然保证数据不丢,又真实写到内存配置上,那我们还要用redolog,我commit不好嘛,我实时刷盘,但是刷盘是刷整个页
redolog的意义:
1)体积小,记录页的修改,比写入也代价低
2)末尾追加,随机写改变顺序写,发生改变的页不固定

3.3 mysql使用及调优实践分析

联合索引:优于多列独立索引
索引顺序:选择性高的在前面
覆盖索引:二级索引存储主键值更有利
索引排序:索引同时满足查询和排序

3.3.11 分库分表设计

1)是否分表(建议单表并不超过1kw)
2)分表方式
取模:存储均匀&访问均匀
按时间:冷热库
3)分库
按业务垂直分布
水平垂直多个库

二 分布式存储技术原理分析

(一)cap定理:在一个分布式计算系统中,一致性,可用性和分区容错性这三种保证无法同时得到满足
1)一致性
2)可用性
3)分区容错性
cap取舍:
cp:发生分区,需要牺牲用户体验,等待所有数据全部一致了之后再让用户访问系统
ap:发生分区,为了高可用,每个节点只能用本地数据提供服务,会导致全局数据的不一致性
理想情况下单机数据库ac;分布式数据库系统cp

2.1 单机数据库分布式解决方案

1)垂直拆分
2)水平拆分
3)读写分离
实际上可以提高对吞吐量的提升但是对业务侵入大更多的是会有分布式事务,维护成本高问题出在扩展性不足

2.2 分布式数据库特性

1)存储量不受单机容量限制
2)计算能力不受单机资源限制
3)扩展性强
5)数据可靠性高

2.2.1 分布式数据库设计思路

1)多副本存储
保证数据一致性
一般kv存储模型
2)主从模式
提供数据分片路由支持
(2.1)多副本存储方式:底层kv存储、数据一致性协议
技术难点-热点数据
1)热点数据
数据分块
热数据迁移
2)原子性
保障多个key写入的原子性如何保证的
(2.1)技术难点-原子性
乐观锁方式
两阶段提交
(1)prewrite,获取锁,写入数据
(2)commit ,提交变更
常见分布式储存产品
(1)mongpdb
(2)pika(360)
(3)pagasus(小米)
(4)tian(美图)
(5)codis(redis分布式解决方案)
(6)tidb

三 分布式存储技术产品选型及适用场景分析

mongo

1)文档型数据库
2)支持主从
3)支持副本集模式
4)支持数据分片
5)快速水平扩展
6)高效数据压缩

tidb

(1)sql支持
(2)水平线弹性扩展
(3)水平式事务
(4)数据一致性保证
(5)故障自恢复的高可用

tidb架构

(1)基于rocksdb
(2)raft一致性协议
(3)etcd存储元数据
(4)支持olca
(5)支持olap

综合对比分析

1)协议
2)持久化
3)存储模型
4)分布式方案

mysql迁移到tidb案列

mysql使用问题
(1)数据量大,快速水平扩展存储
(2)大数据量下,快速ddl
(3)分库分表造成业务逻辑非常复杂
(4)常规mysql主从故障转移会导致业务访问短暂不可用
数据及流量迁移
(1)数据迁移
主从同步
双写
(2)流量协议
切读
停双写

锁冲突问题
业务场景–手机通知栏推送
(1)用户—设备id映射
(2)数据变更场景
1账号登录多手机
2手机登录多账号

  • 乐观锁问题
    (1)商品状态流转
    发布者下家商品0-1
    买家拍下商品0-2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值