大家好呀,我是小菜~
帅哥美女,知道你们时间宝贵,那么就由小菜为你读好一本书,读一本好书,取其精华,与你共享~!
本文主要分享
《软件架构设计:大型网站技术架构与业务架构融合之道》
如有需要,可以参考
如有帮助,不忘 点赞 ❥
微信公众号已开启,菜农曰,没关注的同学们记得关注哦!
今天带来的是 《软件架构设计:大型网站技术架构与业务架构融合之道》 的读书笔记
(文中使用到的例子贴图均出于原书)
在正式进入分享之前,我们想看下这本树的目录架构
这本书总共分为 五
个部分,共计 17
章 ,总体来说内容还是挺多的。内容相对全面,但并没有面面俱到,还是比较推荐阅读的一本书,话不多说,进入正文!
第一部分由两个章节组成,简单的介绍了下什么是架构
现在随便找一个招聘网站或猎头发布的招聘广告,我们都能看到各式各样的架构师头衔,比如有:Java 架构师,前端架构师,后端架构师,数据架构师,中间件架构师… 等等,而且年限的要求也各不一,35年,810年。
但是从这些岗位的需求我们可以看出,“架构师”中的架构
是一个很虚的词,不同领域和行业对员工要求的能力和工作经验差异很大。
现在问起很多开发者的发展路线都不约而同的是要成为一名架构师,那么对架构师的定义是怎么样的?架构师在项目体系和团队结构中应当着一个怎么样的角色?如何成为一名架构师?这些你是否都有一个明确的答案,是否也为之目标而努力前行着!
单纯以技术的角度来看,软件系统自底向上可以分为三层
基础架构是指云平台、操作系统、网络、存储这些构成,一些中小公司大多会选择使用大公司研发的云计算平台,研发成本低,稳定有保障
中间件属于公司中必有的,类似消息中间件,数据库中间件,缓存中间件,而大数据层对于中小公司来说比较少有沉淀,类似开源的 Hadoop 生态体系,Hive、Spark、Storm、Fink等
对于第三层的划分并不是绝对,图中体现了三种架构类型:通用软件架构
、离线业务系统架构
、在线业务系统架构
,但由于现实中软件的种类过多,比如还存在嵌入式系统。这里简单描述下图中第三种具备的类种:
不禁感叹这年头聊架构,这可以上道与术的层面了。
这张图是大多数项目的基本架构图,可以将每层映射到你们的项目中,是不是不会觉得很陌生。
那么实际中这张图能够反映出架构抉择吗,架构师的任务是否就是简单的划分层级结构,然后就可以埋头进行开发了?
我们依赖这张图将问题进行扩展:
想要表达的问题实在是太多了,由此可见架构师的任务并不简单。
问题那么复杂,我们就以道与术来理解。假如你要成为一名武林高手,那么花里胡哨的招式对于某些人来说很重要,因为要追求好看,所谓的花架子,而招式我们便可理解为术,那么追求高手的层面,我们是否要修炼内功心法,底子扎实,才能成为顶级高手。
那么道重要还是术重要,这是个公说公有理婆说婆有理的问题,段誉的内功厉害,但使不出招式可能也有些徒然,招式好看,却没有内功支撑,也只能成为花架子的笑谈,而道术兼备,方能顶级。
这部分的内容颇多,重在道的修炼
语言是在是太多了,忍不住吐槽~ 尽管语言如此之多,市面上还是不断地推陈出新,我们面对语言的不断迭代要追求潮流还是岿然不动?在我看来,我们要追求道,底层掌握结实,管它日转星移,我亦坦然相对。
语言再多再繁杂,都具备共同的典型特性,无外乎一些语法糖使用熟练与否
I/O是绕不过去的一个基本问题。从文件I/O到网络I/O,存在着各式各样的概念和I/O模型
在了解两个原理之前,我们先清楚几个概念:
以上三个概念了解后,我们继续看 I/O 操作
缓冲I/O
读:磁盘 -> 内核缓冲区 -> 用户缓冲区 -> 应用程序
写: 应用程序 -> 用户缓冲区 -> 内核缓冲区 -> 磁盘
对于缓冲I/O,一个读操作会有3次数据拷贝,一个写操作会有反向的3次数据拷贝
直接I/O
读: 磁盘 -> 内核缓冲区 -> 应用程序
写: 应用程序 -> 内核缓冲区 -> 磁盘
对于直接I/O,一个读操作会有3次数据拷贝,一个写操作会有返现的2次数据拷贝
总结:直接I/O 并不是没有缓冲,而是没有用户级的缓冲,对于操作系统本身的缓冲还是有的
从缓冲I/O到直接I/O,读写操作从3次的数据拷贝缩减到2次数据拷贝。而到了内存映射文件,读写操作再次缩减到了1次数据拷贝,也就是:
应用程序虽然读写的是自己的内存,但这个内存只是一个 “逻辑地址”,实际读写的是 内核缓冲区
零拷贝(Zero Copy)又是提升 I/O 效率的一大利器,在平时有问到 Kafka 是如何做到读写那么快的时候,其中一个很大的原因便是 Kafka 用到了零拷贝技术。
整个过程从4次的数据拷贝降低到了3次,不再经过应用程序内存,直接在内核空间中从内核缓冲区拷贝到 Socket 缓冲区
利用零拷贝的话,连内存缓冲区到Socket缓冲区的数据拷贝步骤都可以省略。在内核缓冲区和 Socket 缓冲区之间并没有做数据拷贝,只是一个地址的映射,底层的网卡驱动程序要读取数据并发送到网络的时候,看似读的是Socket缓冲区中的数据,实际上读得是内核缓冲区的数据。
总结:为什么称之为零拷贝呢,因为从内存的角度上看,数据在内存中没有发生数据拷贝,只在内存与I/O之间传输。利用的还是 内核缓冲区 与 Socket缓冲区之间的映射,数据本身只有一份
网络 I/O 模型也是一个极易混淆的概念,至今为止我们听过几种网络I/O模型呢
很多时候我们容易混淆的概念是 非阻塞 和 异步
1. 网络阻塞 I/O
这种模型很好理解,就是调用的时候会被阻塞,直到数据读取完成或写入成功
2. 网络非阻塞 I/O
和上述相反,但没有数据的时候会立即返回,不会阻塞,然后通过轮询的方式不断查询直到获取到数据
如果只有几十乃至上百个连接的时候,上面两种 I/O 模型处理的方式问题都不大,当连接数达到几十万乃至上百万时,那问题就很严重了
3. I/O 多路复用
该方式也是阻塞调用,一次性将所有的连接请求传进来,当某个连接请求具备条件后,会立即将结果放回,告知应用程序有哪些连接可读或可写。常用的 I/O 多路复用的方法有:select、poll、epoll、Java的NIO,其中 epoll 的效率最高,也是目前最主流的。
epoll
整个 epoll 分为三个步骤
其中又可分为两种模式:LT(水平触发/条件触发)
和 ET(边缘触发/状态触发)
总结:实际的开发中,大家一般倾向于 LT(默认模式)。但使用的时候需要避免 "写死循环"的问题,因为写缓冲区为满的概率很小,会一直触发写事件
4. 异步 I/O
异步 I/O 是指所有的读写操作都由操作系统完成,当处理结束后,将结果通过指定的回调函数或其他机制告知应用程序
总结: 阻塞和非阻塞是从函数调用的角度来说,而同步和非同步是从 “读写是由谁来完成” 的角度来说
除了上面几种网络I/O模型,我们还经常听到 Reactor 模式与 Proactor 模型,这两种并不是网络I/O模型。而是网络框架中的两种设计模式,无论操作系统的网络 I/O 模型的设计,还是上层网络框架的网络 I/O 模型的设计,用的都是这两中设计模型之一
1. Reactor 模式
这是一种主动模式。应用程序会不断地轮询,询问操作系统或网络框架、I/O 是否就绪。select、poll、epoll、Java中的NIO 就属于这种主动模式。
2. Proactor 模式
这是一种被动模式。应用程序会将所有的读写操作都交给操作系统完成,完成后再将结果通过一定的通知机制告知应用程序
不同语言有不同的使用习惯。如 Java 一般是写 单进程多线程 ,C++ 一般是 单进程多线程 或 多进程单线程
不要通过共享内存来实现通信,而应通过通信实现共享内存
通俗理解:尽可能通过消息通信, 而不是共享内存来实现进程或线程之间的同步
读可以多线程、写必须单线程,如果多线程写,则做不到无锁
基于内存屏障(防止代码重排序),有了Java中的volatile关键字,再加上单线程写的原则,就有了Java中的无锁并发框架
如果是多线程写,内存屏障并不适用,这是就需要用到 CAS。CAS是在CPU层面提供的一个硬件原子指令,实现对同一个值的Compare和Set 两个操作的原子化。
网络的具体认知可以空降:掌握《网络》,见微才能知著
看了三大范式不禁有些汗颜,实际开发中为了性能或便于开发,违背范式的设计比比皆是,但也无可厚非,虽然范式不一定要遵守,但还是需要仔细权衡。
分库分表使分布式系统设计中一个非常普遍的问题。
业务拆分
。通过业务拆分我们可以把一个大的复杂系统拆成多个业务子系统,系统与系统之间可以通过 RPC 或消息中间件的方式通信。
应对高并发
。高并发我们可以具体分为是读多写少还是读少写多的并发场景。读多我们可以利用缓存中间件减少压力,而写多我们就需要考虑是否要进行分库分表
数据隔离
。核心业务区分开来,区别对待,投入的开发和运维成本也可以侧重点偏移
B+ 树具备了哪些查询特性:
这个是一个 B+ 树结构,相对来说比较抽象,我们提取下其中的几个关键特征:
从小到大
的顺序排列,形成一个双向链表。叶子节点的每一个key都指向一条记录为什么只支持前缀匹配模糊查询 - like abc%
前缀匹配模糊查询可以转换为范围查询,例如 abc% 可以转换为 key in [abc, abcz],而如果是全模糊查询是没有办法转换的
上面描述的树只是一个逻辑结构,而不是实际上的物理结构,因为数据最终都是要存储到磁盘上的。
磁盘都是以 块 为单位
在 InnoDB 引擎中默认的块大小是 16 KB(可通过 innodb_page_size参数指定),这里的块,指的是逻辑单位,而不是磁盘扇区的物理块,块是 InnoDB 读写磁盘的基本单位,InnoDB 每次进行磁盘I/O读取的都是 16 KB的整数倍,无论是叶子节点还是非叶子节点都是装在 Page
里面
一个Page大概可以装1000个key(意味着B+树有1000个分叉),每个Page大概可以装200条记录(叶子节点),那么三层结构可以装多少?1000 * 1000 * 200 = 2亿条,约16GB的数据,这就是 B+ 数的强大之处
每一个非主键索引都对应一个 B+ 树,与主键索引不同的是非主键索引每个叶子节点存储的是主键的值而不是记录的指针。也就是说对于非主键索引的查询,会先查到主键的值,再拿主键的值去查询主键的B+数,这就需要两次 B+ 树的查询操作,也就我们常说的 回表查询
什么事务?事务就是一个"代码块"
(一条船的蚂蚱),要么都不执行,要么都执行。多个事务之间,为了想完成任务,那么它们之间就很容易发生冲突,冲突产生就容易带来问题,比如:有两个事务分别是 小王 和 小李
看了上面的 4 个问题,我们都觉得小李实在是太坏了,那有没有什么方法可以帮助到小王?
尽管串行化可以解决所有问题,但所有操作都是串行的,性能无法结局,所以常用的隔离级别是 RC 和 RR(默认)。RR 可以解决 脏读、不可重复读、幻读 问题,那最后一个 丢失更新 我们就要另外想方法解决了
悲观心态,认为数据发生冲突的概率很大,在读之前就直接上锁,可以利用 select xxx for update
语句。但会存在拿到锁之后一直没释放的问题,在高并发场景下会造成大量请求阻塞
乐观心态,认为数据发生冲突的概率很小,读之前不上锁,写的时候才判断原有的数据是否被其他事务修改了,也就是常说的 CAS。
CAS的核心思想是:数据读出来的时候有一个版本v1,然后在内存里面修改,当再写回去的时候,如果发现数据库中的版本不是v1(比v1大),说明在修改的期间内别的事务也在修改,则放弃更新,把数据重新读出来,重新计算逻辑,再重新写回去,如此不断地重试。
事务的四大核心属性:
一个事务存在修改多张表的多条记录,而多条记录又可分布在不同的 Page 里面,对应着磁盘的不同文职。如果每个事务都直接写磁盘,性能势必达不到要求。
解决的方式就是在内存中进行事务提交,然后通过后台线程异步地把内存中的数据写入到磁盘中。但这个时候又会有个问题,那就是如果发生宕机,内存中的数据没来得及刷盘就丢失了。
而这个时候 Redo Log 就是用来解决这种问题
一样是先在内存中提交事务,然后写日志(Redo Log),然后后台任务把内存中的数据异步刷到磁盘中。日志是顺序的记录在尾部,这样就可以避免一个事务发生多次磁盘随机I/O 问题。
从图中我们可以看到,在事务提交之后,Redo Log先写入到内存中的 Redo Log Buffer 中,然后异步地刷到磁盘的 Redo Log。因此不光光事务修改的操作是异步刷盘的,Redo Log 的写入也是异步刷盘的。
既然都是先写到内存中,那么发生宕机还是会出现丢失数据的问题,因此 InnoDB 有个参数 innodb_flush_log_at_trx_commit 可以控制刷盘策略:
总结:0 和 2 都可能丢失数据,1 是最安全的,但是性能是最差的
从物理结构上来看,日志是一个永不结束的字节流,但从逻辑结构上看,日志不可能是一个永不结束的字节流
因此在 Redo Log 中存在一个 LSN(Log Sequence Number)的编号(按照时间顺序),在一定时间后之前的历史日志就会归档,并从头开始循环使用
在 Redo Log 中会采用逻辑和物理的方式总和记录,先以 Page 为单位记录日志,然后每个 Page 中在采用逻辑记法(记录 Page 里面的哪一行被修改了),这种记法也称为 Physiological Logging
不同事务的日志在Redo Log 中是交叉存在的,也就意味着未提交的事务也在 Redo Log 中。而崩溃后恢复就会用到一个名为 ARIES 算法,不管事务有没有提交,日志都会记录到 Redo Log 中,当崩溃再恢复的时候就会把 Redo Log 全部重放一遍,提交和未提交的事务都会重放,从而让数据库回到宕机之前的状态,称之为 Repeating History 。重放结束后再把宕机之前未完成的事务找出来,然后逐一利用 Undo Log 进行回滚。
上面说到进行 Redo Log 宕机回滚的时候,如果 Redo Log 中存在未提交的事务,那么就需要借助 Undo Log进行辅助,换言之,如果 Redo Log 里面记录的都是已经提交的事务,那么回滚的时候也就不需要 Undo Log 的帮助
那么 Undo Log 除了在宕机恢复时对未提交的事务进行回滚,还具备以下两个核心作用:
在多线程的场景中应对并发问题的策略通常有三种:
Undo Log 的作用就是在 CopyOnWrite 部分。每个事务修改记录之前,都会先把记录拷贝一份出来,拷贝出来的那个备份就是存在Undo Log 里面。每个事务都有唯一的编号,ID从小到大递增,每一次修改就是一个版本,因此Undo Log负责的就是维护数据从旧到新的每个版本,各个版本之间的记录通过链表串联
为了不能让事务读取到正在修改的数据,只能读取历史版本,这就实现了隔离性
Undo Log 不是 log 而是数据
,因为 Undo Log 只是临时记录,当事务提交之后,对应的 Undo Log 文件就可以删除了,因此 Undo Log 成为记录的备份数据更为准确
正是有了 MVCC 这种特性,通常的 select 语句都是不加锁的,读取的全部是数据的历史版本,从而支撑高并发的查询,也就是所谓的 快照读,与之相对应的是 当前读
读取历史数据的方式就叫做快照读
,而读取数据库最新版本数据的方式叫做 当前读
当执行 select 操作时,InnoDB 默认会执行快照读,会记录下这次 select 后的结果,之后 select 的时候就会返回这次快照的数据,即使其他事务提交了不会影响当前 select 的数据,这就实现了可重复读。
快照的生成当在第一次执行 select 的时候,也就是说假设当 A 开启了事务,然后没有执行任何操作,这时候 B insert 了一条数据然后 commit,这时 A 执行 select,那么返回的数据中就会有 B 添加的那条数据。之后无论再有其他事务 commit 都没有关系,因为快照已经生成了,后面的 select 都是根据快照来的。
对于会对数据修改的操作(update、insert、delete)都是采用 当前读 的模式。在执行这几个操作时会读取最新的版本号记录,写操作后会把版本号改为当前事务的版本号,所以即使是别的事务提交的数据也可以查询到。
假设要 update 一条记录,但是在另一个事务中已经 delete 掉这条数据并且 commit 了,如果 update 就会产生冲突,也正是因为这样所以会产生幻读,所以在 update 的时候需要知道最新的记录。
Binlog 称之为记录日志,它与 Redo Log 和 Undo Log 不同之处在于,后两者是 InnoDB 引擎层面的,而 Binlog 是Mysql 层面的,它的主要作用是用来做主从复制,它同样具有刷盘机制:
总结:0 和 n 都是不安全的,为了不丢失数据,一般都是建议双 1 保证,即 sync_binlog 和 innodb_flush_log_at_trx_commit 的值都是 1
Mysql 有三种主从复制的方式
总结:无论异步复制,还是半异步复制(可能退化为异步复制),都可能在主从切换的时候丢数据。业务一般的做法是牺牲一致性来换取高可用性,即在Master宕机后切换到Slave,忍受少量的数据丢失,后续再人工修复
原生的 MySQL 主从复制都是单线程的,将 Master 的 Binlog 发送到 Slave 上后生成 RelayLog 文件,Slave 再对 RelayLog 文件进行重放
而所谓的并行复制实际上是并行回放,传输还是单线程,但是回放是使多线程
开源运行的兴起,最不缺的便是开发框架,现市面上有各种各样的轮子
任何问题都是速途同归,到最后只能通过两种操作:读和写。
缓存可分为 本地缓存 和 集中式缓存 。使用缓存的同时我们需要思考缓存的更新策略:
同样使用缓存可能会面临的几个问题:
那么缓存的使用无外乎都是对数据进行冗余,达到空间换时间的效果
单线程不行,通常就会使用多线程。这种明显治标不指标,容易达到性能瓶颈
当微博这种大流量的平台,查看关注人和自己发布的微博列表看似很简单需求,通常只需要两张表,一个是 关注关系表 ,一个是 微博发布表。但是对于高并发查询的时候很容易将数据库打崩。
那我们就需要改成 重写轻读 的方式,不是查询的时候才聚合,,而是提前为每个 userId 准备一个 收件箱
当某个被关注的用户发布微博时,只需要将这条微博发送给所有关注自己每个用户的收件箱中,这样用户查询的时候只需要查看自己的收件箱即可。
但通过使用 重写轻读 容易带来一个问题,那就是如果一个人拥有了 500 万粉丝,那就意味着他需要往 500 万个收件箱中推送,这对系统来说同样是个不小的挑战,那这个时候就需要采用 推拉结合
的方式
对于粉丝量少的用户(设个阈值),发送微博后可以直接推送到用户的收件箱,对于粉丝较多的用户,只推送给在线的用户,对于读的一端,用户有些可以通过收件箱获取,有些需要自己手动去拉,这种就是推拉结合的方式
常见的有: 分库分表
、Java的ConcurrentHashMap
、Kafka的partition
数据分片是对要处理的数据(或请求)进行分片,任务分片是对处理程序本身进行分片。
常见的有:CPU 的指令流水线
、Map/Reduce
、Tomcat 的1+N+M网络模型
通过消息中间件,分流处理
不管是Mysql、Redis、Kafka 通常上都不会将数据一条一条的进行处理,而是多条合并成一条,一次性写入
高并发读写是一种定性分析,而压力测试和容量规划就是一种定量分析
这三个概念都是比较常见的
三者关系:吞吐量 * 响应时间 = 并发数
关键点说明:谈论吞吐量(QPS)的时候,一定需要谈对应的响应时间是多少,随着QPS的增加,响应时间也在增加,虽然 QPS 提上来了,但用户端的响应时间却变长了,客户端的超时率增加,用户体验变差,所以这两者需要权衡,不能一昧地提升 QPS,而不顾及响应时间
容量评估的基本思路:
机器数 = 预估总流量/单机流量
其中分子是一个预估的值(通过历史数据预估),分母通过压力测试得到
在计算的时候需要使用峰值测算,而不能使用均值。尽管有时候峰值持续的时间很短,但不容忽视。
压力测试方法:
高并发使系统更有效率,高可用使系统更可靠
不要把所有鸡蛋放到一个篮子里
利用消息中间(发布/订阅机制),一条消息发出,多台机器收到后更新自己的本地缓存
Redis Cluster 提供了 Master - Slave 之间的复制机制,当 Master 宕机后可以切换到 Slave。
MySQL 之间可以用到异步复制或半异步复制,同步复制性能较差,比较少用
对于Kafka类的消息中间件,一个Partition通常至少会指定三个副本,为此Kafka专门设计了一种称为ISR的算法,在多个副本之间做消息的同步
隔离是指将系统或资源分割开,在系统发生故障时能限定传播范围和影响范围,即发生故障后不会出现滚雪球的效应
信号量隔离是 Hystrix 提出的一种隔离方式,比线程池隔离更要轻量,由于线程池太多会导致线程过多从而导致切换的开销大,而使用信号量隔离不会额外增加线程池,只在调用线程内部执行。信号量本质上是一个数字,记录当前访问某个资源的并发线程数,在线程访问资源之前获取信号量,访问结束时释放信号量,一旦信号量达到阈值,便申请不到信号量,会直接 丢弃请求,而不是阻塞等待
限流可以分为技术层面的限流和业务层面的限流。技术层面的限流比较通用,各种业务场景都可以用到;业务层面的限流需要根据具体的业务场景做开发。
具体操作可以空降:《餐厅小故事》| 服务限流的实施
注意点: 限流是服务端,根据其能力上限设置一个过载保护;而熔断是调用方对自己的一个保护。能熔断的服务肯定不是核心链路上的必选服务,如果是的话,则服务超时或者宕机,前端就不能用了,而不是熔断。熔断其实也是降级的一种方式
降级是一种兜底方案,是在系统出故障之后的一个尽力而为的措施,比较偏向业务层面
频繁进行系统变更是个风险较高的操作。灰度与回滚可以使该操作变的相对可靠稳定
当一个新的功能上线时,可以将一部分流量导入到这个新的功能,如果验证功能没有问题,再一点点增加流量,最终让所有流量都切换到这个新功能上。
如果旧的系统被重构了,我们不可能在一瞬间把所有旧的系统下线,完全变成新的系统,一般会持续一段时间,新旧系统同时共存,就需要增加流量分配机制。
回滚的方式:
2 PC 中有两个角色:事务协调者与事务参与者
每一个数据库就是一个参与者,调用方也就是协调者,2 PC 将事务的提交分为两个阶段:
这种方式也存在了许多问题:
解决了 2PC 同时挂掉的问题,将 2PC 的准备阶段再次一分为二
该方式依然会造成数据不一致问题:如果 preCommit 阶段存在部分节点返回 nack,那么协调者刚要中断事务便挂掉了,一定时间后参与者便会继续提交事务,造成数据不一致问题
TCC(try-confirm-cancel)是服务化的二阶段编程模型,核心思想是:针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)。他同样也是分为三个步骤
例子:转账操作,第一步在 try 阶段,首先调用远程接口把自己和对方的钱冻结起来,第二步在 confirm 阶段,执行转账操作,如果成功则进行解冻,否则执行 cancel
它解决了数据最终一致性的问题,通过 confirm 和 cancel 的幂等性,保证数据一致性
可以基于 RocketMQ 实现最终一致性。为了能通过消息中间件解决该问题,同时又不和业务耦合,RocketMQ提出了“事务消息”的概念
RocketMQ会定期(默认是1min)扫描所有的预发送但还没有确认的消息,回调给发送方,询问这条消息是要发出去,还是取消。发送方根据自己的业务数据,判断这条消息是应该发出去(DB更新成功了),还是应该取消(DB更新失败)
无论是 MySQL的 Master/Slave,还是 Redis 的 Master/Slave,或是Kafka的多副本复制,都是通过牺牲一致性来换取高可用性的。
本章主要对 Paxos、Zab、Raft 三种算法进行解析。做出的笔记内容较多,保证本篇篇幅的情况下,考虑单独抽出讲解,有兴趣的小伙伴可以后续关注~!
强一致性 Consistency
:是指所有节点同时看到相同的数据。可用性 Availability
:任何时候,读写操作都是成功的,保证服务一直可用分区容错性 Partition tolerance
:当部分节点出现消息丢失或分区故障的时候,分布式系统仍然能够运行CP的系统追求强一致性,比如Zookeeper,但牺牲了一定的性能
AP的系统追求高可用,牺牲了一定的一致性,比如数据库的主从复制、Kafka的主从复制
可以利用 Zookeeper 的 瞬时节点 的特性。每次加锁都是创建一个瞬时节点,释放锁则删除瞬时节点。因为 Zookeeper 和客户端之间通过心跳探测客户端是否宕机,如果宕机,则 Zookeeper 检测到后自动删除瞬时节点,从而释放锁。
Redis的性能比Zookeeper更好,所以通常用来实现分布式锁。但 Redis 相对 Zookeeper 也存在些许问题
产品经理从某种意义上来说就称之为需求分析师。作为一个技术人员,不需要像产品经理或需求分析师那样对需求了如指掌,但具有良好的业务意识确是做业务架构的基本条件
那么什么业务意识?
有时需求来自何处,技术为谁而坐,往往和公司的基因、盈利模式紧密挂钩,公司本身决定了需求从什么地方来
很多原因都会导致伪需求,比如老板的决定,面向 KPI 的需求。而其中存在一个因素便是:信息传播的递减效应
当发生一个事件时,第一个人 A 看到事件的全过程,掌握 100 的信息量,描述给 B 的时候,受制于记忆力、表达力等因素只能描述出 90 的信息,往下递推,到 D 的时候可能只剩 60 的信息。
所以,作为一个技术人员,当从产品经理接到需求的时候,一定要回溯,明确需求是在什么背景下提出的,究竟要解决用户的什么问题。
人力资源和时间资源是有限的。如何合理分配尤为重要
一个内容能称为一个业务,往往具备一个特点,就是闭环。
什么是闭环?
业务架构既关乎组织架构,也关乎技术架构
不管是业务架构还是技术架构,C端业务还是B端业务,我们都会用到分层技术
一个函数、一个类、一个模块只做一件事,不要把不同的职责糅在一起,这就是边界思维的一种体现
首先想到的不是如何实现,而是把系统当做一个黑盒,看系统对外提供的接口是什么,接口也就是系统的边界,定义了系统可以支持什么、不支持什么。所以接口的设计往往比接口的实现更重要!
内部实现很复杂,用户界面很简单,把复杂留给自己,把简单留给用户
总结: 边界思维的重点在于约束,是一个 “负方法” 的思维方式。架构强调的不是系统能支持什么,而是系统的“约束”是什么,不管是业务约束,还是技术约束。没有“约束”,就没有架构。一个设计或系统,如果“无所不能”
系统化系统不在于头痛医头脚痛医脚,而是追溯源头,关注整体上的影响,把不同的东西串在一起考虑,而不是割裂后分开来看
当谈到系统的时候,首先要确定的是系统为哪几类人服务,同哪几个外部系统交互,也就确定了系统的边界。
软件有功能需求和非功能需求,非功能性需求有:
语言只是对现实中我们所注意到的事务特征的一种抽象,每一次命名,都是一个抽象化的过程,这个过程会忽略掉现实事务的许多特征。但是抽象的目的是为了交流提供便利,而不是给交流带来负担,因此我们需要对自己的每一次抽象负责,不能抽象到最后自己都不明白抽象的含义是什么。
抽象的几种特征:
越抽象的词,在词典中个数越少;越具象的词,在词典中个数越多。
越抽象的词,本身所表达的特征越少;越具象的词,特征越丰富。
越抽象的词,意义越容易被多重解读;越具象的词,意义越明确
建模的本质:把重要的东西进行显性化,进而把这些显性化的构造块互相串联起来,组成一个体系
分解是一个很朴素的思维方式,把一个大的东西分成几个部分。比分解更为严谨,更为系统的是 正交分解,需要保证两个原则:
该章节主要是对 DDD(领域驱动模型) 做出解释,比较泛化,这里推荐一本好书 《实现领域驱动设计》
,书中对 DDD 解说的相对具体,这本书小菜最近也在啃读中,后续会出相应的读书笔记,请伙伴们点点关注,后续不会迷路!
对于程序员来说,我们是干技术,很纯粹,技术很好表示你能力越强。但是当你慢慢职位上涨的时候,会发现技术不能代表你的全部。
打开格局,打开格局,平时常说的一句调侃的话却格外重要。
做技术我们需要开阔视野打开格局,我们才能了解更多的技术栈,更好的运用到项目中。
做产品我们需要开阔视野打开格局,我们才能了解市面上的竞品是什么样子,更好的借鉴到自己的项目中。
格局 是从 空间 的角度看待问题,而 历史观 则是从 时间的角度看待问题。任何一种技术,都不是凭空想出来的,任何一个需求,都不是凭空捏造的,我们需要进行回溯,了解它诞生的背景,才能知其所以然。
有些人抽象出来的事物可以让别人一眼贯通,有些人抽象出来的事物却连自己的看不懂。这就是抽象能力的表现。
很多写代码的人习惯利用 自底向上 的思维解决问题,讨论需求的时候首先想到的是这个需求如何实现,而不是这个需求本身合不合理,对于很多新人来说 需求的合不合理,依赖于需求好不好实现,这样的方式很容易导致 只见树木,不见森林
,最后淹没在各种错综复杂的细节中。
深入思考的能力主要考察技术的深度
深度并不表示要在所有领域都很精通,而是专注于某个领域,对于专家和全栈工程师的区别,想想哪个职位的薪资可能会更高
落地能力值的就是执行力,有空头画大饼的能力,却无落地去实现的能力,只会阻碍项目的正常前行。这大概就是技术不喜销售的原因吧
进入职场的前几年尤为关键,有的人平步青云,有的人却止步不前。那就是没能很好的塑造自己的影响力。影响力该如何塑造?
最怕的是 事不关己高高挂起 的心态,如果下次摊上事的是你如何?如果当团队中遇到问题,这个时候能够迎上,绝对可以让人知道还有你这一号人物(当然要斟酌抗下的风险,迎难而上并不意味着逞强)
虽然我们常说自己是打工人,但有的时候何不把自己当成合伙人?
打工的思维,安排的事需要干一件,绝不多一点,只管好自己的一亩三分地
老板的思维,这个产品的价值在哪?这个产品存在哪些问题,需要如何改进?为何用户一直投诉的事,还没及时处理?
术业有专攻,水平再高的人都需要谨记山外有山人外有人,否则就会一直待在自己的舒适圈中,刚愎自用
不必害怕自己的回答是否正确,而瞻前顾后不敢发言,充分发挥 圆桌文化, 有建议有想法大胆提出,不然你是想留给自己的蛔虫知道吗
技术管理的首要任务就是项目管理,通常存在以下几种不确定性
由于各种外部条件,导致需求提议的想法不是很成熟(可能只是头脑风暴),处于需要不断优化的阶段,那么这个时候过早的进行开发容易浪费资源。作为技术负责人就需要和产品经理以及相关的业务方进行广泛的头痛,需要达成共识的情况,才能投入。
启动新项目的时,最怕的就是一开始技术没有很好的选型,到中间开发阶段时候再进行替换,这种劳民伤财的事情还是尽量避免发生。必须在项目早期的时候就进行过多的调研和测试。
现在的大多数职员都是面向金线开发,大多数在职情况并不是那么稳定,而将项目的大多权限与业务集中在一名成员上是个不明智的选择,能够进行 AB岗位开发是个不错的选择,两人之间的业务相互熟悉,哪怕是因为请假的原因也能很快的进行替代补充
公司越大,业务越复杂,部门越多。随便做一个项目,都可能与好几个业务部门打交道。这些部门可能还在异地,平时只能即时通信,或者远程电话沟通。对于这种情况,在项目前期必须要做尽可能多的沟通,调研对方提供的业务能力,哪些目前有,哪些还在开发中,哪些还没有开发。在充分沟通的基础上,和对方敲定排期表,不定期地同步进度,保证对方的进度和自己在一个节奏上。