架构师是互联网行业高薪又紧俏的资源。成为架构师最基本的是设计能力。设计与设计的区别主要体现在两方面:
1,深度:要解决哪些问题?这个问题背后的根本问题是什么?还有什么问题没有发现?对应的能力是发现和解决问题的能力。
2,体系:要解决的问题的属于哪一类的问题?这类问题能否进一步抽象,让系统解决更大的问题?对应的抽象归纳和体系化思维的能力。
而做架构的基本功就是研究成熟成功的系统,并总结归纳为一种设计方法添加到自己的设计库中。今天我们来看看文件存储机制的通用实现及原理-谈Kafka、Redis、基于Lucene的搜索引擎等中间件和数据库的文件存储机制。如果你有九年义务教育以上学历,并且觉得看不懂这篇文章,请给我留言~~
顺便插一句,不知道大家有没有奇怪静儿最近都没有写高可用方面的文章。事情是这样的,对于高可用的很多设计、架构,静儿都在进行专利申请中,为了避免对公司造成影响和损失,暂时处于多想不说的阶段。
一个商业化中间件的性能好坏,其文件存储机制设计是衡量一个消息队列服务技术水平和最关键指标之一。下面先介绍一下各个中间件的存储机制。看不懂可直接跳到最后。
各个中间件的存储机制
1Kafka
Kafka是最初由Linkedin公司开发,是一个分布式、分区的、多副本的、多订阅者,基于zookeeper协议的分布式日志系统(也可以当做MQ系统),常见可以用于web/nginx日志、访问日志,消息服务等等,Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目。
设计特点
Kafka把topic中一个partition(大块)大文件分成多个小文件段,通过多个小文件段,就容易定期清除或删除已经消费完的文件,减少磁盘占用。
通过索引信息可以快速定位message和确定response的最大大小。
通过index(索引)元数据全部映射到memory(内存),可以避免segment file(文件片段)的IO磁盘操作。
通过索引文件稀疏存储,可以大幅降低index文件元数据占用空间大小。
一张图解释一下刚才说的特点:
从上面的图中可以看到,生产者和消费者并没有直接的交互。这就达到了生产者消费者模式的第一个好处:解耦。作为消费者,可以自定义消费线程数和消费者数量。这就达到了生产者消费者模式的第二个好处:限流。这也是大家使用MQ一般想要达到的目的。
2Redis
谈redis首先要纠正一个误区:缓存比mysql快。为了说明这个问题,下面是静儿自己动手得到的数据。
结论:使用了缓存后响应时间不稳定
结论:mysql的响应时间非常稳定
从cat监控中可以看到缓存的get时间确实较长。
顺便跑题一下,静儿测试中还发现在jvm正常执行业务逻辑,不太复杂无外部调用,一般是1到2毫秒。但是如果抛出了异常,这段代码执行时间要变成100ms+。所以一般代码中禁止用抛出非必要异常来代替正常逻辑。
之所以给大家打这个预防针,是因为下面要给出Redis的描述了。
Redis本质上是一个key-value类型的内存数据库,整个数据库加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过10万次读写操作,是已经性能最快的key-value DB。
Redis存储机制分成两种Snapshot和AOF。无论是哪种机制,Redis都是将数据存储在内存中。
AOF工作原理:是将数据先存在内存,但是在存储的时候会使用fsync(无阻塞进程的)来完成对本次写操作的日志记录。AOF最关键的配置就是关于调用fsync追加日志文件的频率,有两种预设频率。always:每次记录进来都添加。everysecond每秒添加一次。
存储模式性能和安全比较:
1.性能
snapshot性能是要明显高于AOP方式的,原因有两点:
1>采用二进制方式存储,数据文件小,加载快速。
2>存储的时候是按照配置中的save策略来存储,每次都是聚合很多数据批量存储,写入的效率高。AOF一般都是工作在实时或准实时模式下。相对存储频率高,效率低。
2.数据安全
AOF数据安全性高于Snapshot,原因:
snapshot存储是基于累积批量的思想,累积的数据越多写入效率越高,但是如果长时间数据不写入RDB(redis的数据库),redis遇到了崩溃,没写入的数据就无法恢复。
Redis中有rewrite功能,AOF的存储是按照记录日志的方式去工作的,成千上万的数据插入必然导致日志文件的扩大,Redis这个时候会根据配置合理触发rewrite操作。这个操作是将最终保留值记录到日志文件中,从而缩小日志文件的大小。这个类似于lucene的optimize操作。
3Mysql/MariaDB
Mysql与目前流行的nosql数据库最大的不同是规定了严格的数据类型。数据类型因为在创建表时在内存中严格划定了地址空间,所以能限定字段的数据存储长度。
数据类型限定范围的方式有两种:1是严格限定空间,划分了多少空间就只能存储多少数据,超出的数据将被切断;2是使用额外的字节的bit位来标记某个数据,存储了就进行标记,不存储就不标记。下面介绍3种类型的存储方式。
1.整形的存储
它严格限定空间,每个已划分的字节上的bit位上的0和1直接可以计算出数值,所以它的范围是根据bit位的数量值来计算的。一个字节有8个bit位,一个bit位可以构成2的8次方=256个数值。同理2字节共2的16次方=65526个数值。也就是说,在0-255之间的数字都只占用1个字节,256-65535之间的数字占用2个字节。
2.char的存储
char类型是常被成为定长字符串类型,它严格限定空间长度,但它限定的是字符数不是字节数。它有“短了就使用空格补足”的能力。
3.varchar常被称为“变长字符串类型”。它存储数据时使用额外的字节的bit位来标记某个字节是否存储了数据。每存储1个字节占用一个bit位进行标记。一个额外的字节可以标记256个字节,2个额外的字节可以标记65536个字节。MySQL/mariadb限制了最大能存储65536个字节。这表示,如果是单字节的字符,它最多能存储65536个字符,如果是多字节字符,如UTF8的每个字节占用3个字节,它最多能存储65536/3=21845个字符。
4基于Lucene的搜索引擎
提到基于Lucene的搜索引擎,大家可能更熟悉ElasticSearch(ES)。静儿有段时间专门负责公司的搜索,没有现在这么忙,本应该是非常难得和宝贵的机会。有大把的时间研究源码,可惜当时整个心都不在工作上。当时用的还是solr4.X.X(solr是基于Lucene的搜索引擎系统,lucene和solr版本同步更新,目前最新是7.6.0),当时solr每月发布一个新版本。版本有很多bug,我当时是在源码上做了很多修复补丁的。但是当时各种意识都很薄弱。如果当初给apache提patch,有些应该是能通过的。这就能形成了一个良好的反馈循环,可能现在的状况截然不同。
想起dubbo有段时间已经不更新版本,后来捐给了apache,给这个项目又重新注入了生机。其中的一个教训就是大家一定要有很强的开源意识。这种意识还包括在自己做功能方案和开发实现的时候要有业界调研和对标的意识。
静儿的梦想是实现自己的搜索引擎中间件,所以目前从事的是和这个原理有很深渊源的工作:容器调度。什么?看不出来联系?那这个以后再慢慢聊。
Lucene的数据都存在一个目录下,一个目录构成了一个索引。Lucene经多年演进优化,现在的一个索引文件可以分为4个部分:词典、倒排表、正向文件、列式存储。一张表解释lucene的存储原理:
通用原理
1.高频读取操作放于内存。
2.文件分段减少磁盘IO操作。
3.磁盘存储防止数据丢失。
4.更好的结构化可以提升存储和读取效率。
以上です。不解释。
总结
之前和同事聊天,同事说他们架构师基于原来的版本设计了一个非常完美的2.0方案。但是这个方案并不解决现有的任何问题。这也是静儿想做架构并且可以很轻松的找到一个架构师职位,但一直都是在项目组内自己动手写代码的原因:一个旁观者想了解内部的痛点很困难。
前段时间大家纷纷剖析拼多多优惠券事件背后的技术问题。不可否认,技术是有点菜。但是拼多多目前整体看是成功的。成功在哪里呢?它成功的解决了商家(其他平台门槛高,入住不了)和消费者(以更低的价格买到商品)的痛点。所以这点状况并未伤及元气。
在做设计的时候,我会首先问自己一个问题:现在哪里最痛?架构讲究深度和体系。深度解决的是痛点根本性的问题。体系解决的是未来的痛点问题。不以解决问题为目的设计的系统都是“成功之母”。