1.限制和要求
2.事务支持
3.分布式?
4.数据格式设计,索引设计
5.物理文件设计
6.压缩(适合内存和物理)
7.单机运行设计
8.分角色职责
1.限制和要求
涉及到选介质,选索引,是否分布式。
- 延时?
介质(内存,SSD,磁盘。裸设备,文件? 举个例子:有buf的系统,避免Inode和数据和内核缓存可以,元数据放入rocksdb,NVRAM作为rocksdb的WAL,rocksdb用SSD,数据放入磁盘裸设备中)。 全内存操作要对数据格式要求极高。 - 读多?
数据量中的3 最多一次小index+数据,适合大量读。磁盘每页随机写。单次为何还比SSD慢呢?一个是SSD都是顺序,为何差10倍写,mysql对事务的支持中间操作很多。 - 写多?
数据量中的2 一般与SSD一起用,大量操作SSD很快,擦除影响寿命=》追加,适合写多。读性能和mysql差不多 - 数据量?
1.数据量少,内存足够,类似redis,index不需要排序
2.大,顺序+hash。每个hash要全部载入内存。16M/G。适合大数据或者单词查询。
3.与2一个量级,B+。以页为答案为索引,前几层基本都在索引中,
4.很大。大到mysql操作同步,事务支撑不了。放弃事务。leveldb+分区。将文件+内存分区等
2.事务
不同日志和锁实现方式。
并发(锁)或者 串行
?
2.1原子
undolog。基本也只有mysql支持
。问题就只有为啥mysql的undolog不用于持久化。因为计算太多。
不需亚多条显示提交回滚。比如redis,leveldb的KV形式。则按照上文的WAL+操作。日志有就重做。注意加seqid去重
否则WAL(begin)+操作+WAL(end)
涉及到复杂的性能提升的批量刷盘像mysql一样加redolog或者顺序操作不需要
2.2隔离与一致
redis读写都是串行解决众多问题。
leveldb写入是串行。recoksdb写入单个family串行。所以只有一个可重复读需要解决
其他基本上都是MySQL的问题
- 1.脏写:
行写锁。leveldb的锁是整个mem的。 - 2.脏读:
锁代价大=》cOPY ON WRITE/MVCC;持有写入锁可以设置新值,读旧数据。读已提交的隔离级别,只需要保留一个版本 - 3.可重复读:
事务id/seq比较。多版本
保存方法:
leveldb全部保存。内存key就带了seqid,每个都保留,live的读manifest里有最小最大seqid,内存compact是seqid决定删除否。文件不删除,多份。
mongodb B树的多节点,当前checkpoint的东西在页节点的updatelist和insertlist。多个checkpoint的引用多个树。
mysql:单独updatelist,回查。节点只存最新的。 - 4.丢失更新等:
select v;update v=v+1; leveldb单独加1操作,mysql间隙锁,for update等扩大锁定范围
唯一 select empty;insert 冲突; leveldb不支持这么复杂的约束 - 5.串行
SSI 序列化快照隔离:悲观锁=》乐观锁(先执行到提交时再检查是否与隔离违背,违背则中止重试)。快照隔离+检测写入序列化冲突。(读之前存在未提交的写入)MVCC的读在提交时检查是否有被忽略的写入已经被提交。写入数据时通知所有最近读取的其他事务(读之后发生写入)
2.3持久
区别在前log还是在后
,是逻辑还是物理
。
- redis的持久:
执行完一个写命令后,会以协议格式将命令追加到aof_buf缓冲区末尾。AOF,子进程rewrite,重写过程中新的继续写入缓冲区。也算类似checkpoint吧 - leveldb
内存前WAL。还有每个事务引用不同的version在内存中需要恢复,这部分每个version的持久化在manifest中,compact生成新version后写入 - mysql
redolog的覆盖。内存的刷新,更新lsn,写入checkpoint点,此后的日志可以覆盖了。如果事务执行太快,redolog来不及刷盘,2G内存会被覆盖,会对log进行checkpoint
落盘。这点之前的可以安全覆盖
3分布式?
3.1备份
高可用的备份:高性能的负载,地理就近进行数据复制,一般三个副本可以保证11个9
- 复制方法
快照+新命令传播(mysql redo和binlog两种,语句注意rand等,逻辑行复制)
不重要么log幂等,要么全局id
。
事务是否结束binlog只有结束才写,oplog作为WAL但结束转移id队列。
都是从主动
拿offset.顺序
保证offset
流水线;分离数据流和控制流,S1,S2,S3,1,2同机房,控制流1=》2=》3而不是1=》3=》2 - 主从切换
从库上报心跳,选主,对比term和seq,(ISR中选择)。
从库延时超过几个seq后down剔除同步集合。
谁来做切换?一个哨兵集群或者proxy/zk来做(固定心跳集),自动gossip+raft。redis 1s频次,mongodb 2s。10s未收到有问题,bully。rocksdb 30s,2分钟
心跳/租约:心跳避免网络分区,租约不需要(两个用) - 复制方案
同步,异步,半同步(ISR,pipeline
)。有降级。分角色(参与投票和只备份,只读可落后) - 部署
同机房三副本,同城三机房三副本。3地3机房5副本,2副本用主从复制 - 访问方式
几种保证:读写一致性(自己更新自己立刻可看),单调读(同一用户多次读取一致),一致性前缀读(保证读取顺序按写入顺序),最终一致性。
1)读主库,刚更新数据读主库,记录时间戳;自己请求读主库,注意不同网络和设备,分布打到多个数据中心的需要路由到主库的数据中心
2)同一用户读固定从库,防止更旧
3)依赖事务有因果关系的写入 同一个分片。不同分片不保证顺序一致
单主 写主。除非上述,否则读从。冷备,温被,热备
多主 同上
无主 全部多读多写,返回数量足够成功 -
一致性解决方案
一致性从客户角度:读写一致,会话,读单调,写单调。起码要保证支持。存储系统可以考虑强一致性或最终一致性。单主 多库事务,主从延时(redo取代binlog,反读主+cache)
多主多主写入要同步,夸机房多主写入前通信延时不行,业务上很难保一个分类始终在一个机房,小流量等;写后`冲突处理`,(1.每次请求带最大id,lamport时间戳;2.保留多版本,按时间戳覆盖) 数据保证不丢,mq。redis的 或者就paxos。读严格也要大多数读(可以加一个本地更新协调者有省去多数读)
无主 类似raft,每个写入前协商。
全局有序,线性一致性
zk这种的数据,全放入内存的,选主更新term,数据同步(收集,确认同步范围),投票决议,提交,同步 - 多存储事务
无核心的:二阶段提交,三阶段提交,一致性协议
有核心的:binlog同步,cache解决
3.2可扩展的分片
- 分片方式
hash
键值+hash(重新分片可以直接计算,变化范围还可控)
一致性hash
range(hbase热点
) 扫描和范围查询 - 重新分片
1.hash提前分配slot(如果任意,要记录slot。比如1024要记录1024个,16384这种记录slot范围,key到槽固定,槽到机器不确定),如果非任意,2,4,8这种扩展可以不需要元数据。
2.动态分区,合并和拆分(需要记录范围)
3.新加入节点在旧节点中随机拿一部分。比如一致性hash基础上,每个机器节点256个虚拟节点,配置范围,新节点取82%~102%的部分。维持总节点数不变,每个机器持有节点变少。(需要记录范围)
4.CRUSH(不需要元数据
)固定虚拟节点gid第一层hash。gid与节点id生成随机,只跟二者相关的伪随机,所有节点id中选择随机数*权重最大的,新增只需要把更大的移走,减少只需要移动到次大的。 - 元数据存储:
单独zk。codis,zk负责分片排队
每个都存在节点上做同步。redis的集群每个转发,迁移过程中ASK标识,重新分片需要个单独东西排队。或者mongodb的抢锁迁移(分布式锁问题,比zk代价还是小的)。自己做name集群,每个同步。 - 增量元数据何时切
增量全转到新的,快照同步完就切。
4.数据格式设计、索引设计
myssql B+页索引。页中仍有索引(4-8一个)。
leveldb 一页一个index值。hash有序。无继续索引。bloom过滤器
。其他过滤器。
redis 直接hash。rehash
列存储:倒排索引。bitmap(布尔查询)
删除:mysql:标注和update类似处理,仍然要改到页上.leveldb也和update一样,新文件。原地整理
5.物理文件设计
- leveldb
manifest存储当前版本每层文件,文件最大和最小序号,
data ;bloom filter;filter;meta ; filter index;data index;footer。 - mysql
元数据每个索引根节点page no。表=》段(一个表最多85个)=》区(一个区一个区分配,64页,每256个区一个索引)=》page到page后根据树搜索。
数据,索引,插入缓存Bitmap页等 独立文件或者一个文件。共享:undo 信息,二次写缓冲 一个文件
表空间ibdata=>段=》区extent=>page(1M区,4kpage的话 256个。默认1M,64页,16k)=》行(每页最多7992行)(页有不同的类型,如果表空间管理页,INODE节点页,插入缓存页以及存放数据的索引页等)
1.表空间第一页是表空间管理,管理256个区(spaceid,下一个段id)。
2.第三页为Inode page,维护段(数据段/索引段/undo段)的使用信息,当前段的free extent list等。除了维持当前segment下的extent链表外,为了节省内存,减少碎页,每个Entry还占有32个独立的page用于分配,每次分配时总是先分配独立的Page。当填满32个数组项时,再在每次分配时都分配一个完整的Extent。一个Innode page里最多存放85个entry,除非为一张表申请42个索引,否则一个Innode page足够使用。如果未开启innodb_file_per_table选项的数据库来说,所有的信息都存储在ibdata文件,可能需要多个Inode page来维护段信息。Innodb将这些Inode page通过链表的方式组织起来。当创建一个新的索引时,实际上构建一个新的btree(btr_create),先为非叶子节点Segment分配一个inode entry,再创建root page,并将该segment的位置记录到root page中,然后再分配leaf segment的Inode entry,并记录到root page中。
3.第8页为数据字典:当我们需要打开一张表时,需要从ibdata的数据词典表中load元数据信息,其中SYS_INDEXES系统表中记录了表,索引,及索引根页对应的page no,进而找到btree根page,找到inode,就可以对整个用户数据btree进行操作。
4.index page。页头(页信息,slot个数,记录数,level等)。数据。索引(infi slot supre,每4~8个分一个。二分查找) - 磁盘或者文件系统的选择
ext3/4自己还有一个写日志+写数据 可保证数据恢复。或者写元数据,只能保证文件系统大小等正确。sql如果自己有保证原子写可以不用该特性。
另外有自己buff的可以去掉内核buf
对磁盘写文件系统要改三个快,加日志是四个快(超级快,Inode块,数据块,日志块)。考虑裸设备自己实现。
6.压缩(适用于索引,cache,文件)
- 有序的可以
前缀压缩
.snappy,zlib
(慢,压缩比很高) - 哈夫曼编码
- 德鲁伊中列三种类型:时间,列,统计
1.时间戳和统计:变长整数编码+LZ4
压缩的整数或浮点(比snappy两者都好点),DC动态词典编码
2.列:字典+列字典值+倒排索引
1)将值(总是被视为字符串)映射到整数ID的字典,
2)列的值列表,使用1中的字典编码,和
3)对于列中的每个不同值,一个位图指示哪些行包含该值。倒排索引:posting list采用压缩的bitmap位图索引, - redis
ziplist.连续空间取代链表,前面节点固定1/5字节。encodinng/length/content字节编码标识长度
zset整数固定长度
- pb的
1.连续位标识
以上数字的转变全是基于VLQ可变长二进制数字编码
的变体。最低位加0表示整数,1表示负数,然后7位一个分割。从最后开始,每个后面有第一位是1,否则是0. - EC编码:N个数据块和校验,可以任意丢k个相互恢复
7.单机运行设计
- 网络模型
进程/线程通信(pipe,socketpair,文件/匿名共享内存) - cli
协议 - cache 索引cache 数据cache
lru(分成多组,活跃/非活跃3/8) 。
redis的没有用lru的列表。在记录上记录时间,搜索记录删除。
rocksdb lru在改时要锁。CLOCK LRU介于FIFO和LRU之间,不需要加锁,首次装入主存时和随后再被访问到时,该帧的使用位设置为1。循环,每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0,遇到第一个0替换 - 并发
网络
WAL+mem写入批量并发,rocksdb的内存CAS
写log并发(hbase分区/mongo的预分配slot
)
落盘 (积累数据,单独flush线程
,redis的async也是单独线程)
compact并发(rocksdb的多线程compact
,mysql的日志重放每个db一个线程
) - 内存池
对其的内存
(连续小块)。大块内存不需要对其
,没啥意义了。 - IO
写入的buf,flush,sync
。(数据4k write(flush)一次,64k补一个fsync,日志32k读一次,buf,32kflush一次)页保证完整性(校验和+丢弃一整块,按最小原子写入,mysql的二次写)
IO与CPU并行 - 数据结构优化
hash表。锁位图表 读/写/空闲 1000万个。默认65536一个单元初始化
锁:也是原子
任务队列:为了防止全局任务队列抢锁问题。分配任务到线程队列,为了防止一个忙,steal
lru:更新链表会有全局锁冲突,因此记录次数排序等。cache预热
8.分角色职责
8.1 有master
master
- 存储节点信息和状态
拓扑结构从上至下存储池、分区、服务器、磁盘、文件目录等,内存,持久化,容量/负荷/状态 - 文件读写的调度策略
非hash,调度,当客户端发起写请求时,第一步会到Master节点获取文件ID,Master节点根据客户端读写文件的大小、备份数等参数以及当前系统节点的状态和权重,选择合适的节点和备份,返回给客户端一个文件ID,而这个文件ID包含了该文件多个副本位置信息 - 存储节点选主
node
- 单机存储(比如
小文件合并
,leveldb等) - 备份一致性
- 上报,接收调度
client
- 集群信息缓存(减少与Master节点的交互)
- 异常处理
8.2 去中心
client:(proxy)
- 备份一致性的保证,比如3个才成功
- 节点故障的重试
node
- 单机存储
- 备份一致性
集群信息「重要,单独」,元数据,数据
9.瓶颈/性能分析
IO的吞吐量。IOPS
CPU能承受的QPS。计算量
网卡
hash的内存
SQL一些特别添加
存储于计算分离。
分布式并行计算,类似flink。调度/隔离
两阶段提交+协调者paxos
sql计划缓存,避免硬解析和优化
DDL:只更改元数据,元数据快照,反解析或者compact时再应用
join:
merge nested-loop hash
hybrid-hash: 在grace分配分片时,将尽量完整的分片放在内存中,将probe分片内存中的直接运算,其他落盘。磁盘中的再循环
leveldb型compact特别添加
渐进。增量,副本轮询(注意冷cache)
1.compaction:使得读取延时稳定seek不那么多。代价是大量IO和带宽会导致性能下降 compaction的写放大 当内存不足时需要限制写速度
控制读放大:合并
控制写放大:避免compact相同数据
控股空间放大:删除数据和过期数据清除,不需要的多版本数据,compact过程中临时空间
控制compact使用资源
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。