后端好书阅读与推荐系列文章:
后端好书阅读与推荐
后端好书阅读与推荐(续)
后端好书阅读与推荐(续二)
后端好书阅读与推荐(续三)
后端好书阅读与推荐(续四)
后端好书阅读与推荐(续五)
Redis设计与实现
Redis设计与实现 (豆瓣): https://book.douban.com/subje…
通过前面这本书我们已经知道redis怎么用比较好了,现在我们来看看 Redis 的实现原理。这本书是作者自己看着源码写出来的,不得不佩服作者的智慧与毅力。这本书基于redis3.0,此刻redis最新版是4.0.9,我们看书的时候可以自己去看看源码,看看redis有啥变化没,源码在此。
亮点:
- redis并没有用C语言的字符串作为其基准字符串表示,而是使用了
SDS(简单动态字符串)
这一抽象类型,被用于键、字符串的表示,还有AOF中的缓冲区、客户端输出缓冲区等buffer的表示。 SDS的优势在于:记录了长度信息不需要遍历(空间换时间)、有长度所以会先检查空间是否足够而不会溢出、记录了free(未使用空间)长度从而避免频繁的内存重新分配(实现了空间预分配和惰性释放)、二进制安全等等 - redis还定义了链表Linkedlist(用于实现
List
结构、发布订阅、慢查询、监视器等功能)、字典Dict(基于两个hash表,一个平时用、一个rehash用,主要用于实现数据库、Hash
结构,需要注意渐进式rehash而不是java中Hashmap的一次性rehash,渐进式主要是避免键数量太大导致服务器暂时停止服务)、跳跃表SkipList(用于实现SortedSet
结构、集群节点)、整数集合(用于实现Set
结构)、压缩列表(用于实现List
和Hash
结构,主要是面向小整数和短字符串,可以节省空间)、对象redisObject(redis的五种数据结构就是五种对象,key value都是对象,是对前面所有数据结构的一种封装,实现类型判断、编码、引用计数与对象共享和内存回收、过期删除等功能)等数据结构来实现特定功能 - redis服务器是一个
redisServer
数据结构,包含一个redisDb
的链表,每一个数据库都是一个数据结构redisDb
,中间有一个dict
的数据结构保存了这个数据库所有的键值对,叫做键空间(key space)。键空间会维护命中次数、最后使用时间、过期清除、键监视、键空间(关注键本身)/键事件(关注操作本身如del、sadd、set等)通知等事件。此外还有一个clients
字段保存了所有客户端 -
redisDb
有一个dict
类型的链表expire
,保存了所有键的过期时间。过期键有三种删除策略:定时删除
,设置timer,过期立即删除,对内存友好,但是浪费CPU时间片,而且大量的timer也不现实;惰性删除
,放任不管,获取的时候检查是否过期,过期再删除,会浪费一定的内存,但是节省CPU时间;定期删除
,每隔一段时间就检查过期键并删除,需要好好设计执行时长与频率。redis实际使用的是后两者 - 持久化分为
AOF
和RDB
。RDB可以手动执行也可定期执行(BGSAVE)用内存数据生成压缩过的二进制RDB文件,SAVE
命令会阻塞服务器,而BGSAVE
会fork一个子进程来创建RDB文件,所以期间新的数据变化是不能提现到RDB文件中的,此外,RDB文件的载入是服务器启动自动完成的;AOF不直接记录数据,而是记录每一个redis命令,分为append、文件写入(可能在系统缓冲中)、文件同步sync(强制刷到磁盘)三个步骤,文件同步有三种选项always(写入文件就刷盘)、everysec(默认)、no(由操作系统决定何时刷盘) - redis是一个事件驱动的程序,这种程序一般会有一个类似于
while(true)
的无限循环,叫做事件循环,每个循环里服务器要处理两种事件:文件事件,完成网络通信(基于Reactor模式);时间事件,完成定时、周期操作之类。 - Sentinel(哨兵)是redis的高可用解决方案,由>=1个
Sentinel
实例监视任意多的redis主服务器以及所属的从服务器(从服务器复制主服务器的数据),完成主备切换等功能。实现原理是Sentinel
每隔10秒就向所有主服务器发送info
命令,获取各个主服务器及其对应的从服务器的信息,然后也会以相同形式给从服务器发送info
命令。另外会以两秒一次的频率向所有主从服务器发送一个publish
的命令,然后会subscribe
这个频道,这样就能获得每个服务器的相关信息了。Sentinel
之间只有命令连接,没有频道连接。 - 在线检测通过每秒的
ping
来实现,当超过一个Sentinel
的时限down-after-milliseconds
还没获得一个服务器的回复这个Sentinel
就认为此服务器主观下线,然后会询问其他Sentinel
,如果超过quorum
个Sentinel
都认为此服务器主观下线那么这些Sentinel
就认为这个服务器就客观下线了。当一个主服务器下线时,Sentinel
们会选举一个领头Sentinel
(Raft
leader选举)来对这个服务器群进行主备切换,具体算法就不说了 - redis集群通过槽指派实现,所有数据分配到16284个槽,所有槽都有节点处理时集群才能对外服务,每个节点可以处理多个槽(一个节点可以是主从服务器群,提高该节点可用性),当一个节点收到命令发现该槽不在本节点时会向客户端返回重定向指令,让其请求对应的节点。重新分槽可以在线分,由
redis-trib
实现。 - redis 支持慢查询,超过
showlog-log-slower-than
选项执行时间的命令会被记录到慢查询日志,日志采用保存最近的,数量由slowlog-max-len决定。
MySQL技术内幕
MySQL技术内幕 (豆瓣): https://book.douban.com/subje…
通过前面这本书我们已经知道 mysql 怎么用比较好了,现在我们来看看 mysql 的实现原理。
亮点:
- mysql采用单进程多线程模式。逻辑上,采用了分层可插拔架构,上层是连接池管理模块,中层是语句解析、缓存、优化模块,下层是可插拔的存储引擎如myisam、Innodb(存储引擎基于表而不是数据库),下层依赖于操作系统层面的文件系统。
-
InnoDB
支持事务、行锁、外键,主要面向在线事务处理应用(OLTP),通过MVVC提高并发性,默认可重复读级别使用 next-key locking避免幻读,此外还提供插入缓冲、二次写、自适应hash索引、预读等高性能与高可用功能;MyISAM
不支持事务与行锁,但是支持全文索引,主要面向OLAP应用;NDB
是一个集群引擎,数据全部在内存所以速度很快,但是连接操作不是在引擎层面解决的,效率较低;Memory
也是内存存储的引擎,适合临时表存储中间结果使用,默认使用hash索引,只支持表锁;Archive
只支持insert和select,支持压缩适合存储归档数据;还有太多不常用的,就不一一列举了 - innodb采用多线程模型,
Master Thread
负责将缓冲池中的数据异步刷新到磁盘,保证数据一致性,包括脏页刷新,合并插入缓冲,undo页回收;IO thread
负责写操作AIO的回调处理;Purge Thread
负责undo log的回收,减轻Master Thread的负担;Page cleanner Thread 负责脏页刷新,减轻master thread 负担 - mysql通过
写缓冲
与WAL
将随机写改成了顺序写提高了吞吐量,同时保证数据持久性,如果宕机就会用redo
与undo
两阶段的日志来将数据恢复到数据库文件中。但是mysql基本页面一般是16KB,而操作系统基本页面一般是4KB,如果在写入到数据库文件时发生宕机,就可能引起partial write
问题,亦即一个mysql页面只写入了一部分,这时就要依靠double write
:同步缓冲区数据时,不是直接写入文件,而是写入内存中的double write buffer
再调用fsync写到共享表空间文件,然后马上调用fsync同步至数据库文件,这样如果发生了partial write
,就可以去共享表空间找到该页副本并复制到数据库文件中,然后再进行redo,undo
。当然,如果文件系统本身有防范机制就不必启用double write
了 - InnodB中页是管理数据库的最小磁盘单位,分为多个部分,其中
Fileheader、Pageheader、FileTrailer
(检查完整性)是固定的,记录一些该页的信息如checksum
、页所在B+树层数。userrecords、freespace、pagedirectory
(页相对位置,B+索引只记录一条数据所在页,真正查找还需把这一页加载如内存,靠二分搜索这一条记录)是实际的行记录存储空间,因此大小是动态的。 -
Cardinality
能帮助我们决策是否需要建立索引,可以通过show index
来观察,一般比如性别这个字段Cardinality
就很低,因为可能一个类型female
就占据了所有行的一半,根本没必要建立索引,而名字这个字段Cardinality
就很高,适合建立索引(当然也得是应用常常按名字检索,这样才有必要建立索引) - Innodb1.2.x 开始支持全文检索,并支持myisam引擎所有功能。全文检索一般通过倒排索引实现,亦即存储每一个词汇及其对应文档,这样关键词检索时就能迅速返回相应文档
- 事务分为几种:扁平事务,所有操作位于同一层次,要么都提交要么都回滚(也就是说不支持部分回滚),是应用程序成为原子操作的基本模块;带有保存点的扁平事务,在扁平事务基础上支持部分回滚,用保存点(volatile,易失的)实现;链事务,事务的链接;嵌套事务,顶层事务管理多个子事务;分布式事务,通常是在分布式场景下运行的扁平事务
事务的实现主要依靠:
-
redo
实现持久性,亦即commit
操作必须将专门的redo
日志文件fsync
刷新到磁盘才能返回成功,合并多条语句插入优于一句一句插入也是因为减少了日志刷盘频率,由于redolog
块与磁盘扇区块大小一致,所以无需doublewrite
; -
undo
实现回滚操作和MVCC,是存在于数据库内部共享表空间的一个特殊段(undo
段)中的逻辑日志,记录了事务执行的反效果便于回滚,MVCC的实现是通过若某行记录已被其他事物占用,当前事务可以通过undo
日志得到改行之前版本的信息,需注意undolog
也会产生redolog
,亦即undolog
也需要持久性维护; -
purge
最终完成delete
和update
操作,因为Innodb支持MVCC,所以记录不能在提交时立即处理,purge
操作判断合何时真正清理数据并回收undo page
,具体来说,如果一行数据不被真正引用那就可清理delete
和update
数据了; -
group commit
将多个事务的数据一次性调用fsync刷新到磁盘减少刷盘次数; - 需要注意的是
binlog
,用来实现Point-In-Time
的恢复以及主从复制的实现,非常类似于redolog
,但是两者本质有很大不同:redolog
产生于下层只针对Innodb引擎产生,是物理格式日志,记录针对每一页的修改,事务过程中不断被写入;binlog
产生于中层,针对任何引擎都会产生,是逻辑日志,记录针对每条SQL语句,事务提交后一次写入
-
- Mysql通过XA事务来保证分布式的一致性,同时内部也有XA的使用,如现在数据库为了复制一般都是打开
binlog
的,事务提交时既要写binlog
也要写redolog
,这就会涉及原子性问题,亦即两个日志必须同时写入,通过先做prepare操作,将事务的xid写入,然后写入binlog,然后提交存储引擎并写入redolog
深入Linux内核架构
深入Linux内核架构 (豆瓣): https://book.douban.com/subje…
这是一本能把linux内核全貌展现给我们的大部头,涵盖了包括进程管理,内存管理、锁与通信、设备驱动、文件系统等等。要采取观其大略,用时细读的策略,去宏观的把握linux的设计哲学。
亮点:
- 操作系统架构主要涉及两种类型:微内核,最基本的功能由中央内核实现,其他功能委托给一些进程实现,通过通信接口与中心内核通信,这种模型研究活跃但是实用性进展甚微;宏(单)内核,内核的全部代码打包到一个文件中,内核中每个函数都可以访问其他部分,目前性能强于微内核,linux也是使用这种模式,但是linux模块化做得很好(可以动态添加减少功能,内核文件也不会膨胀),也消除了单内核的一个重要弱点
- linux虚拟地址空间是每个进程管理自身资源并与其他进程隔离的工具,与物理内存无直接关系(由CPU访问总线数决定),分为内核空间(task-size~最高位)和用户空间(0~task-size),这样就为所有进程提供了一个统一的系统视图,无需计较其他进程;Intel处理器有4中特权级别,但是linux只使用两种不同的状态:核心态和用户态,用户态进程禁止直接访问内核空间(这是内核的专用领域)的数据和执行其代码,用户态进程只能借助于系统调用或者中断来陷入内核态,由内核代为执行操作。物理内存基本单位是页框(页帧),虚拟内存基本单位是页,两者大小一样,对应关系由页表(linux采用4级页表)决定
- 创建进程有两种方式,
fork
(父子进程只有PID不同,使用COW使得fork更高效,亦即子进程只复制页表,只有父子某一进程要向内存写入数据时才真正复制物理内存,若只读则可以共享而不必复制,这样就延迟甚至消除了了大量的复制,节约了内存和CPU时间)和exec
(将一个新程序加载到当前内存中执行,旧的程序内存页将刷出)。linux使用clone创建线程(或者说轻量级进程),类似于fork,但是能精确检查哪些资源与父进程共享哪些为线程独立创建 - Linux的进程调度分为两大部分:调度策略和上下文切换。非实时进程的调度策略一般考虑公平性,linux所有可运行进程都在一个红黑树中排序,vruntime(一次调度间隔的虚拟运行时间,
实际运行时间*(NICE_0_LOAD/权重nice值)
)最小的进程位于左下方会被调度器优先考虑,休眠进程被放入等待队列中,唤醒后会重新被加入红黑树。实时进程是指该进程必须在指定的时间内完成比如飞机飞行控制指令,因而一般都有相对非实时进程较高的优先级,不同于非实时进程调度 - 物理内存管理分为两个层次:伙伴系统负责物理页帧的管理,围绕多页组成的连续内存块的拆分与合并,新进的内核引入了反碎片技术(可移动页与可移动内存区)防止物理内存碎片发生;slab负责处理小块内存的分配,并提供了用户层malloc函数族的内核等价物。
- Linux使用了SystemV(Unix的一个分支)引入的机制来支持用户进程间通信(IPC)与同步。主要通过信号量、消息队列、共享内存三种方式实现。此外,linux还有其他IPC机制如信号(kill -s)、管道(以虚拟文件系统实现,适用于父子进程单向通信)及命名管道(适用于无关系的进程间通信)、socket(更通用,可多主机进程通信)
- Linux内核为了统一文件访问接口,在用户进程(或者C标准库)和文件系统(Ext2/3,XFS,Reiserfs)之间引入抽象层:虚拟文件系统(VFS)。文件系统一般分为3种:基于磁盘的FS(如Ext2/3),无持久FS(如/proc),网络文件系统。处理文件时,用户程序关注一个整型的文件描述符fd(由内核分配,只在一个进程内有效,两个进程即使有相同的fd也是指向不同的文件),而内核关注的是inode,包含了一个文件/目录的元数据但不含文件名(文件名只是inode的别称)。软连接采用文件名指向的方式,独立inode;硬链接采用引用计数,共享inode
- 无持久FS有:
proc
标准挂载点是/proc
,其信息不能从块设备读取,只有在读取文件内容时才动态生成相关信息,也可以在不重新编译内核源码的情况下修改内核行为,与内核通信,包含网络信息、内存管理、进程特征数据、文件系统、设备驱动、电源、终端、系统控制参数;sysfs
的标准挂载点是/sys
,是一个向用户空间导出内核对象的文件系统,提供了查看和修改内核数据结构的能力;用于专门目的的小文件系统 - 应用程序看到的内核是一个执行多种系统功能(内存管理、系统资源请求、访问外设、进程通信、读取文件等)的大的例程集合,标准库是一个中间层,用于在不同的体系结构和系统间,标准化并简化内核例程的管理
同学们,堪称整个现代互联网软件基石的Linux内核源码就在这里,有没有勇气去瞅瞅?
还有这本书更精简,推荐给不想看太多源码和细节的同学: Linux内核设计与实现(原书第3版)。
Kafka权威指南
Kafka权威指南 (豆瓣): https://book.douban.com/subje…
Kafka是一个高吞吐量的分布式(支持分区partition、多副本replica、使用zookeeper协调)消息系统,目前广泛用来进行实时大数据处理如:hadoop批处理、storm/Spark流式处理引擎,web日志、消息队列等等,所以作为一个后端开发者,很有必要了解一下。我想的是把kafka作为消息队列的代表进行学习,将来如果要用ActiveMQ、RabbitMQ、ZeroMQ、RocketMQ等或者要自己要开发一个MQ都可以举一反三或者进行借鉴。
亮点:
- Kafka是一个流平台,允许发布和订阅数据流,可以看做传统的MQ,但是有很多不同点:支持多生产者,多消费者,另外多个消费者还可以组成群组,共享一个消息流并且特定消息整个群组只消费一次;以集群方式运行,可伸缩,还有MirrorMaker支持不统集群之间的复制;数据可以持久化,类似于数据库,保证消息一定送达;流式处理使得你可以用很少的代码就能动态的处理派生流和数据集。也可以看做实时版的Hadoop(大数据中,Hadoop主要应用于批处理与数据分析,Kafka由于低延迟更适合业务应用,能够在业务事件发生时及时作出响应,为业务运营提供支撑,比如商品浏览数据转换为商品推荐)。也可以看做ETL,但是并非简单移动数据而是面向实时数据流。这三个方面是Kafka的主要特性
- 生产者产生一个
ProducerRecord
对象,包含Topic
和value
,还可能有key
或者partition
,对象序列化后发送到分区器上,如果有partition则不操作,若无则根据key来指定partition(partition的设计目的是取消消息大小限制和提高并行度)。选好partition后发往对应的topic和partition的记录批次,这一批记录等待一次性的发往对应的broker(所以批的大小影响延迟和吞吐量),整个过程有失败自动重试功能,超过一定次数返回客户端异常。生产者通过设置ack=n
,可以保证一个ProducerRecord
有n个副本(一个Partition的所有副本中有一个Leader负责与客户端交互,其余副本作为Follower从Leader复制数据,提供容错性)被写入成功才会收到成功的ack消息。整个topic的顺序无法保证,1个分区的顺序可以通过设置retries>0
(重试次数),和max.in.flight.requests.per.connection=1
(1次只发1条消息避免后到的消息先成功)来保证,不过会严重影响生产者性能,若非严格顺序要求不建议使用 - 一个消费者group总是能获得一个topic的全部消息不管还有没有其他group订阅该topic,group内通过添加消费者来伸缩读取能力和处理能力,每个消费者只处理一部分数据,而且消费者数量不要超过partition数量,不然多余的会闲置浪费。group内所有消费者向群组协调器发送心跳保持活跃,添加和删除消费者时的再均衡(分配分区与消费者对应关系)也是由群组协调器完成
- Kafka使用ZooKeeper维护集群成员信息,所有broker都对应
/brokers/ids
的临时节点并订阅其变更事件,在broker加入或退出时得到消息;集群中有一个控制器对应/controller
临时节点(所有broker都可以尝试创建节点成功则成为Controller,失败则知道Controller已经存在,所有broker都会监听这个节点的变更,通过递增的controller epoch来忽略旧控制器消息避免脑裂),除了完成broker功能之外还负责partition副本的leader选举;持续得到来自leader最新消息的副本叫做同步的副本,当leader失效时,只有同步的副本才可能被选为新的leader;不同partition的副本和leader都均匀的分布在所有broker之间避免热点问题,副本尽量放在不同机架提高容错性 - kafka处理常见几种请求:元数据请求,可以发送给任意broker,包含客户端感兴趣的主题列表,返回主题包含的分区、副本及其leader;生产请求,ack可以为0,1,all三个值;读取请求,零拷贝返回客户端指定的消息(省掉了用户缓冲区,直接从磁盘到内核),要注意的是,leader只有把数据安全复制到副本中才能给客户端返回这个数据;其他请求,如偏移量(不再保存在ZooKeeper中)
- 因为大文件的查找和删除都很费时易错,所以每个
partition
都分成了许多segment,一个segment包含1GB或者1周的数据(较小为准),正在写入数据的叫做活跃片段,永远不会被删除。每个分区还有一个索引提升查询效率 - 可靠性和速度(简便性)通常是负相关的,Kafka在这两者之间是可以高度配置的,适应从跟踪用户点击到支付操作的不同可靠性级别要求。kafka的可靠性机制包括:分区消息顺序性、多副本确认实现容错性、只要还有一个副本是活跃的已提交消息就不会丢失、消费者只能消费已提交数据。这些基本机制加上一些配置参数(unclean.leader.election.enable、min.insync.replicas、生产者ack)的权衡就能构建不同级别的可靠系统,可靠级别可以是针对broker或者topic。总而言之、可靠性不仅是kafka单方面的事情,需要整个系统配合如系统架构、生产者消费者的使用方式与配置、topic和broker配置等等
- 跨集群的复制在kafka中叫做mirror功能,应用场景:将不同区域的集群数据复制到中心集群便于分析数据、冗余实现灾备、云迁移。跨数据中心通信有高延迟、有限带宽、高成本等特点,常见架构有:Hub-Spoke架构(一中央多本地集群)、双活架构(两个集群都对外服务,就近原则)、主备架构(主集群服务、备集群只在主不可用时对外服务)
- 事件流是无边界的(持续增长)、有序的、可重播的、不可变的记录。流式处理就是实时的处理一个或多个事件流,是一种编程范式就像请求响应式和批处理式,但是延迟和吞吐量都位于两者之间。
看完这本书基本就能把kafka的使用方法和基本原理搞清楚了,但是翻译有时候有点点问题,我觉得普通的段落可以翻译,但是专用名词如segment
、partition
等还是直接保留比较好,免得有歧义,当然整体上来来说配合官网文档读起来还是没问题的。
要想深入的话源码在此。
NoSQL精粹
NoSQL精粹 (豆瓣): https://book.douban.com/subje…
本书先分析了传统关系数据库的不足然后引入NoSQL,讲到了键值、文档、列族、图等多种数据库并分析了其优势劣势,可以让我们对NoSQL有一个全面的了解,为我们进一步NoSQL的探索之路开一个好头。
亮点:
- NoSQL泛指最近诞生非关系型数据库如Cassandra、MongoDB、Neo4j等,他们主张无模式(
schemaless
)的数据。传统关系数据库有可持久化、模型标准便于共享和集成、事务支持等优点,NoSQL有无模式便于程序对象与数据库之间的映射、适应集群等优点。所以NoSQL和传统关系数据库各有所长,发展过程中也在互相借鉴优点,谁也不能取代谁,将来都会在各自的领域中发光发热 - 关系模型把数据分割成简单的行,通常一个对象可能需要多行数据通过外键连接在一起来表示(如果按照严格的关系数据库范式);聚合是领域驱动设计的术语,把一组相关联的对象视为一个整体单元来操作,这个单元就叫做聚合(
aggregate
)。聚合的边界划分没有标准答案,取决于你之后准备怎样操作数据。因为以聚合为单位复制和分片比较自然所以集群中操作数据库还是聚合更简单。若数据操作大多在同一聚合内执行则应使用面向聚合的数据库(键值、文档、列族数据库),若交互需要多种不同的格式则最好选聚合无知式(aggregate-ignorant
)数据库(关系数据库、图数据库),若待处理数据中有大量的关系最好就选关系数据库,但是如果关系复杂、并行交错最好选图数据库(插入费时、查询比复杂的join
快得多) - 宽泛的说数据分布有两条正交的路径:复制,一份数据拷贝到多个节点,有主从式(主节点瓶颈问题)和对等式(一致性问题)两种形式;分片,将不同的数据存放到不同的节点。这里注意不要和另一个概念混淆:数据库水平(一个表的不同行数据置于不同节点)、垂直(一个表的不同列置于不同节点或者拆为多个表)拆分。实际分布式系统中一般都是复制和分片结合的(如上面提到的kafka)
- 版本戳可以检测冲突用来实现CAS,可以用计数器、GUID、Hash、时间戳等方式实现也可以组合实现,分布式系统中,可以采用“由版本戳构成的数组”来检测不同节点是否发生了“相互冲突的更新操作”
- 键值数据库最简单,只能按键检索,适用于会话信息、用户配置信息、购物车等;文档数据库呈树状结构可以看做支持值检索的键值数据库,适用于CMS及博客、电子商务等;列族数据库将数据存在列族中,列族里的行则把许多列数据与本行的行键联系起来,列族通常把需要一并访问的相关数据分成组,提高访问效率,适用于事件记录、博客、计数器等;图数据库把具有属性的实体(节点)按关系(边)联系起来,适用于互联网数据如社交网络、安排运输线、推荐引擎等
- Neal Ford 2006年造出了多语言编程(polyglot programming):应该以多种语言混合编写同一应用程序,以各自语言的优势来解决其中不同的问题,同理数据库也可以遵循类似的原则混合持久化(
polyglot persistence
),比如购物车可以用键值数据库存储,但是订单要用关系型数据库。所以除非是编程语言狂热爱好者,我们大部分人都不应该去争论什么语言好,什么数据库好,不要想着一把刷子走遍世界,而应该广泛了解,解决问题的时候选择合适的工具,必要的时候也要自己改工具甚至造工具
本书的一个问题就是有点旧了,2013年初版,这5年来NoSQL发展很快,已经被广泛用于企业级的系统之中了,尤其是Redis和MongoDB,所以有需求就大胆的用吧。
大型网站技术架构
大型网站技术架构 (豆瓣): https://book.douban.com/subje…
把事情做大是我们许多人的追求,这本书就可以让我们了解一下一个大型网站的架构应该要注意些什么,无论是摸得着的tech还是比较摸不着的leadership,都值得借鉴。当然,书很薄,大多只能泛泛而谈,要想真正掌握,每一部分我们都得自己单独深挖,所以本书可以看做一个大目录,为接下来的学习指引方向。
亮点:
- 讲述了一个单机网站发展到大型分布式网站的完整历程,处于业务快速发展期间的公司可以借鉴,提前做好规划
- 架构设计时要注意:不要盲目追随大公司而是要考虑自己业务;要结合网站实际使用新技术,不要盲目追求新奇酷炫;不要想着用技术解决一切问题,业务模式等也需要考虑;
- 模式:每一个模式描述了一个在我们周围不断重复的问题以及该问题解决方案的核心,这样你就能1次又1次的使用该方案而不必重复劳动。模式的关键在于可重复性,包括问题和方案的可重复性。
- 大型网站有一些常见的架构模式:分层,如应用层、服务层、数据层;分割,如购物、论坛、搜索、广告;分布式,包括服务、静态资源、数据库、计算、锁、文件、配置信息等;集群,相同业务通过集群负载均衡提高服务能力;缓存,如CDN、反向代理、本地缓存、分布式缓存;异步,事务关系越少就越能独立发展,异步也是解耦的一种协作方式;冗余,冷备、热备、灾备等;自动化,包括发布、测试、部署、报警、失效转移、恢复、降级、分配资源等,自动化能提高效率并减小人为出错概率;安全,手机验证码、图像验证码、转义、风控等。所有模式都是在性能(响应时间、qps等)、可用性(冗余)、伸缩性(机器的增减)、扩展性(业务的增减)、安全性(恶意访问、窃取、攻击)这几个维度之间进行平衡
- 性能针对不同群体需要不同的定义(针对用户的响应时间,针对系统的吞吐量、并发量等)来衡量,也需要测试(压测等),同时要从架构的多个层面(前端、应用层、存储层)来进行优化
- 可用性一般用几个9来形容(4个9就是
99.99%
,一年小于53分钟不可用),实现高可用架构的主要手段是数据和服务的冗余备份和失效转移。应用层(文库、贴吧、知道)和服务层(账户服务、session服务)因为一般无状态所以很容易扩展(负载均衡),新节点上下线都很容易,但是服务层需要注意好分级管理、超时设置、异步调用、服务降级、幂等设计等。数据层(数据库、文件、缓存)为了保证数据不丢失需要复制并均匀分布在集群中 - 集群处理能力如果能和服务器数量正相关,那么网站就具有伸缩性架构,如果能成线性关系那更是高伸缩性,主要分为不同服务物理拆分和单一服务集群伸缩(类比于数据库垂直拆分和水平拆分)。应用集群伸缩性一般通过负载均衡实现如http重定向、DNS轮询、反向代理、IP负载均衡、数据链路层负载均衡等;缓存集群伸缩性仅靠负载均衡是不够的,还需要一致性hash、虚拟槽分区等方法使得缓存在节点发生变化时不会集体失效;存储集群伸缩性对持久性和可用性要求更高,但是这里作者没怎么讲具体做法
- 扩展性是在对现有系统影响最小的情况下持续提升或增加服务的能力,表现在系统基础设施稳定,应用之间耦合较少等,是系统架构层面的开闭原则体现。主要有:分布式消息队列解耦生产者与消费者、分布式服务打造可复用的业务平台、利用开放平台打造生态圈实现开放共赢等方式
- 安全性一般主要注意XSS、注入、CSRF等攻击,主要手段有:用户输入转义、表单token、验证码、加密、过滤、风控等
- 领导的真谛就是寻找一个值得共同奋斗的目标,营造一个让大家都能最大限度发挥自我价值的氛围;发掘人的优秀比发掘优秀的人更有意义
读完发现本书几乎全程“黑友商”,有的甚至好几遍(* ̄︶ ̄),有点不纯粹啊。
深入理解 Java 内存模型
这本书是infoq上的系列博客形成的一本很薄的书,对于了解JMM还是很有用的。
深入理解Java内存模型(二)——重排序: http://www.infoq.com/cn/artic…
亮点:
- 重排序的目的是提高性能,分为三种:编译器、处理器指令级并行技术、内存系统的重排序。JMM属于语言级别的内存模型,通过禁止特定类型的编译器重排序(直接可控)和处理器重排序(通过内存屏障实现)来在不同的平台对为程序员提供一致的内存模型
- 内存屏障有四种:
Store
和Load
的排列 ,其中StoreLoad Barriers
是一个全能型的屏障,同时具有其他三个屏障的效果,它确保store
数据对其他处理器的load
可见 - 处理器内存是硬件级别的内存模型,JMM是一个语言级别的内存模型,顺序一致性内存是一个理论参考模型,性能依次降低,可编程性依次上升。所以JMM主要关注易编程性和性能,在两者之间求得平衡
深入分布式缓存:从原理到实践
深入分布式缓存:从原理到实践 (豆瓣): https://book.douban.com/subje…
缓存在如今的互联网中已经几乎是标配了,分布式缓存更是大型网站不可或缺的,本书就让我们从原理到开源实现到业务实践一起打包了解了。
亮点:
- 计算机世界中,无论是硬件还是软件,无论是单机还是分布式,缓存都被用于解决响应缓慢的问题,是提升性能的关键技术。其核心就是空间换时间,解耦高速部件与低速部件。缓存由于不同硬件的访问速度和价格不同而进行分级,每一级存储对应热度级别的数据,分层逐级减少流量的冲击,保护用户体验与系统可用性。分布式环境下要关注其命中、更新、失效、一致性等
- 缓存是无所不在的,从架构的角度看可以分为三块:客户端缓存 在客户端减少网络请求,如页面缓存(文件化)、浏览器通过HTTP协议实现的缓存(etag,304 not-modified)、APP缓存(sqlite)等;网络缓存 位于客户端与服务端之间,代理部分客户端请求给服务端减负,如web代理缓存(nginx)、边缘缓存(CDN);服务端缓存 是缓存的重点和难点,如数据库缓存(query_cache)、平台级缓存(大而全的Ehcache、极简的Guava )、应用级缓存(redis)
- 书中澄清了许多分布式方面的概念和理论,如集群是一组互相独立的计算机通过网络连接构成一个组实现同一个功能,对客户端表现为一个独立服务器,可以增加可用性与伸缩性,而分布式系统就是许多不同功能的集群构成的一个实现特定业务的系统;幂等性是指调用
1次和N次
结果一样,这个在分布式系统中很重要是因为网络抖动等因素常常导致重发;分布式系统的本质是一堆廉价的硬件攒在一起获得更好的吞吐量和性能以及可用性,有几个问题普遍关心:活性检测(周期心跳、累计失效检测)、高可用(主备、双主、集群)、容错处理(冷热切换、冗余、备份)、重试机制(失败重试、事务补偿)、负载均衡(多机分担请求),也有几个实践很重要:全局ID(数据库自增id如flicker、UUID、snowflake、预生成置于缓存中)、哈希取模分配、一致性哈希(单调性、分散性)、路由表与数据拆分 - 书中在JSR规范下带领我们实现了一个缓存框架CSCache,架构由上到下是客户端、缓存提供层、缓存管理层、缓存存储层(基本存储,LRU存储,Weak存储),设计思路值得借鉴
- redis有瓶颈:单机数据量限制、单线程吞吐量限制(尤其是大块数据读取延迟明显)、单点故障等,目前已有的解决方案有:水平拆分如Proxy和Twemproxy、自带主从复制(含断点续传)、sentinel监控自动故障转移(使用raft类似算法选主)、redis3.0后出来的去中心化(采用Gossip协议在节点之间达成一致)的cluster提供了完整的sharding、replication、failover等解决方案
- 本书覆盖了广告、社交、电商等多种业务的缓存模式,能开开眼界,将来可能还真用得上,尤其是社交和电商这两块讲得很细致,几乎可以作为一精简而完整的解决方案了