2020年一年间更新,已近尾声,通篇内容为知识启蒙,认识世界,科学、哲学、宗教、历史、政治、艺术。
具体篇章发布于知乎,因文化审查等各种原因,部分篇章稍有删漏,详见知乎专栏-银河系补全计划
2020年一年间更新,已近尾声,通篇内容为知识启蒙,认识世界,科学、哲学、宗教、历史、政治、艺术。
具体篇章发布于知乎,因文化审查等各种原因,部分篇章稍有删漏,详见知乎专栏-银河系补全计划
在几年的工作过程中,我经历了多个开放平台型系统,系统中存在些许不如意的瑕疵,而出于稳定考虑,大部分尽可能保持原样不再迭代改进,我的很多想法没法付诸实现,所以渐渐萌生了一个想法:设计开发一套全新的API网关,在开发过程中随时推翻原先的设计,只求能试验我的设想,同时将其开源,供其它同志参考学习。
API网关中很重要的一部分是协议转换,而大部分已开源的API网关几乎都是http->http,很少有大型互联网公司开源自己的http->rpc,更不用说”多协议->多协议”。
协议转换是最基础的功能性需求,其它的功能性需求有限流、熔断、黑白名单、日志、验签、授权、文档、SDK、调试台等。在实现基本功能的基础上,可以针对高可用、高性能进一步优化,比如全链路异步、隔离、多级缓存。
早先,咱们的吃瓜网站部署在局域网里,通过谷歌代理穿透内网的方式对外提供服务,那时网站速度极慢,10秒内有响应算正常。毕竟浏览器的请求先到达美国洛杉矶,再绕道回中国大陆,经过联通线路抵达局域网,速度慢是肯定的。
后来,我们启用CDN,让主域名指向阿里CDN,源域名仍是通过穿透指向局域网。此时,热点的url速度较快,而冷门url的响应速度和以前一样慢。
再后来,为了解决冷门url的问题,网站从局域网搬迁至阿里云,此时网站总体响应速度提升至2~3秒。
再后来,CDN提供HTTP/2的能力,所以咱们立马申请一个免费SSL证书,强制HTTPS之后接着启用HTTP/2。开启HTTP/2以后,网站响应速度从几秒缩减至几百毫秒。(注:后来网站中加载大量第三方的插件,整体速度慢下来,此事略开不讲)
有好奇好问的同学说了:”那你这个HTTP/2有什么神奇的地方,速度怎么提升的?”咱们这一节就来探究该问题。
相信绝大部分Java攻城狮都用过Spring框架吧,我在这里总结下Spring的几个要点。
最关键最核心的部分是IOC容器和AOP框架(不信出去面试一圈你会发现所有面试官都会问你这2点内容哦),对于IOC容器,我们只需理解BeanFactory接口和ApplicationContext接口,差不多便能掌握其原理,比如要定位Resource、载入BeanDefinition、存入Map仓库、依赖注入时通过反射设置变量值等等;对于AOP框架,我们只需记住它是代理模式的包装,是简单易上手的代理。
在此基础上,深入SpringMVC的源码阅读一番,弄懂IOC容器的启动过程与SpringMVC的启动过程,DispatcherServlet在处理一个http request时如何定位Handler、如何渲染结果,这样也能掌握SpringMVC的原理(如果有同学侧重于http网络方面的开发,应该会对SpringMVC如何处理request/response感兴趣)。
这是我很久之前造的一个RPC轮子,名叫AirRPC,它基于zookeeper,和阿里dubbo、美团pigeon等框架比较类似(毕竟RPC框架原理都一样)。源码在github上,有兴趣的同学可以看看:https://github.com/qiuyongchen/AirRPC。
下面将详细描述出整个项目的设计思路与实现,包括相关的理论与模型、框架建模与框架模块设计、部署步骤与测试结果等。
团队在发展初期,由于规模和业务量小,网站开发人员只需将网站以Tomcat + Linux + MySQL + Java的形式部署在一台机器上便足以应对业务流量。
随着时间发展,团队内的业务日益复杂,仅依靠一台机器已不能应付流量压力,此时为了及时跟进业务的发展,网站开发人员通过使用分而治之的手段,把整个网站业务进行垂直拆分。比如,可以根据网站的业务的不同,把一个项目分拆成多个,每个项目专人专职,由专门的开发人员负责,各个业务的流量压力由不同的项目承担,这在一定程度上可以缓解业务发展带来的压力。
久而久之,各个项目的开发人员发现,各一个项目都需要执行许多相同的业务操作,比如用户管理,产品管理和供应商的管理等,而且,每个项目都要和数据库保持连接,给数据库带来了极大压力,数据库有拒绝服务的可能性。为了解决该问题,开发人员提出了水平切分,也就是分布式服务的解决方案,将多个项目共同拥有的业务操作提取出来,根据业务类型放到多个Service项目中,各个非Service项目均调用Service项目提供的服务。
本项目正是一个致力于解决水平切分问题的分布式服务框架,让小型开发团队可以透明地从单机服务架构扩展到分布式服务架构。
Zookeeper之有名,已不需再说,大量中间件基于zk来做分布式一致性、master选举、注册订阅服务。它的核心功能是主从绝对一致,且拥有发布/订阅能力,并拥有顺序性、原子性和单一视图等特性,着实好用(实际上吃瓜群众邱永臣在造RPC轮子时,也是将zk作为注册中心)。
Zookeeper的核心是Zab协议,即zookeeper的原子广播协议。Zab主要靠2个模式来保证一致性,分别是恢复模式(选leader)和广播模式 。
恢复模式:在集群启动之初,或是leader挂掉之后,各个服务器开始选出新leader,并更新年号纪元(要点是选zxid最大的服务器)。恢复过程中,为了保证一致性需要解决两个问题,一个是旧leader已经commit的提议必须应用到所有follower,另一个是仅有旧leader提出的提议必须回滚,解决上述俩问题的要点也是选zxid最大的服务器作为新leader。
广播模式:leader在应用事务请求之前,必须取得超半数follower的同意,过程及其类似两阶段提交。和两阶段不同的是,leader发出提议后,follower需记录在事务日志中,写磁盘成功后返回ack,leader得到超半数ack后,统一commit(因为follower有队列实现事务串行且有事务日志,所以commit大概率成功);另外,Zab没有rollback选项,若follower不同意leader的提议,该follower被踢出投票席(所以只能ack),需重新同步leader数据。
可以下载PDF脑图细看(其中包括单机事务和分布式事务的研究):Zookeeper与Zab协议深入总结脑图
零、前言
这份总结中记录redis几大数据结构的实现(包括链表、字典、跳跃表、压缩列表和整数集合),在对象上redis用压缩列表存储list/hash/sortedset而用跳跃表和字典实现sorteset,令人印象深刻;同时记录了redis的RDB和AOF持久化方案、复制特性和谣言算法实现集群化;最后对redis的事务、事件等功能也进行了一番描述。
一、脑图笔记
由于脑图长宽过大,转成jpg图片之后较难打开,所以我把它转成了PDF文件,可以点击链接进行下载:redis深入总结PDF脑图
事务是一系列动作的合集,拥有ACID四种特性,分别是原子性、一致性、隔离性和持久性,先来简单回顾一下几个特性的含义(咱们从最容易理解的开始),后面讨论下一致性和原子性之间的关系、一致性和隔离性之间的关系。
持久性,是指事务执行完毕之后,所做的修改能被保存起来,这个很容易理解,但凡是重要的东西,都要像档案一样保存起来,这是我们习惯的做法。
隔离性,是指两个事务之间互不干扰,我改我的,你改你的,咱们之间互不干扰,这也容易理解,彼此分清职责与界限,以免出问题时互相甩锅,人之常情嘛。不过这里有个问题,一定要互相隔离吗,如果不隔离会怎么样?我们先放着这个问题,后面再揭露谜底。
原子性,是指事务的一连串动作要么都完成,要么都不完成。用老江湖的话来说,那就是:“这件事咱们要干就把它干完干好,要么就不干!”
一致性,是指事务执行完毕之后,系统从一个状态转变为另一个状态,仍旧保持一致,没有中间状态。这句话什么意思?听起来和原子性很像哦,系统要么从一个状态变成另一个状态,要么不变,这可不就是原子性了吗?如果你有这个疑惑,那咱们就可以继续往下了。
仔细回想一下,在何时何地我们曾经遇到或听到过“不一致”这个词?
好像,数据库和缓存之间会不一致,刷写不及时,改完数据库还没来得及改缓存,或者是由于并发,缓存和数据库都扣减库存之后,发现总库存不对。
好像,mysql主从之间会不一致,俗称主从延迟,写进主库之后,从库里查不到。
那么一致性到底是什么意思呢?我们先放着这个问题,后面再揭露谜底。
在思考一致性之前,我们先来想一下事务的本质是什么。
从群论的角度看,事务本质是一连串动作的集合。在指挥官发号施令之后,众多动作接二连三地执行,“兄弟们冲啊”,直到最后一个动作执行完毕。如果有多个动作,把它们包装成事务,以至于从外界看起来是同一个动作,这是我们提出事务最根本的原因:把多个动作包装成一个动作,many->one。
但是,俗话说:“一个和尚挑水喝,两个和尚抬水喝,三个和尚没水喝”。一个团队中只有一个的人的时候还好,自力更生,奋发图强。但是林子大了什么鸟都有,团队大了什么人都有,很难做到众志成城齐心协力,力往一处使地干好一件事。要想抽象化团队中所有人,把他们当做一个人来,是很困难的。扯这么多,就是想说明事务说起来简单,做起来困难。
下面我们以商品交易业务作为例子,探讨一下事务的一致性。
假设我们有一个事务,里面是扣减库存的业务逻辑。要扣减某商品1个库存,那么剩余库存减1,已售库存加1,最后剩余库存和已售库存之和(即总库存)仍等于扣减之前的总库存。我们发现,执行完前面一系列动作(事务)之后,总库存不变,执行前后总库存数值保持一致。
这种在事务执行前后总库存不变的特性(一致性),由什么来保证呢?仔细想一想会发现,其实是原子性,减剩余和加已售2个动作,要么全执行,要么全都不执行。
小结:单个事务前后的一致性,可以由原子性保证。
假设剩余20个库存,有2个事务同时执行,第一个事务要扣3个库存后来由于某种原因要回滚,第二个要扣10个库存,如果处理得不好,可能会出现下面的时序:
A)第一个事务查询到仍有20个库存
B)第一个事务扣3个库存,将剩余库存置为17
C)第二个事务查询到剩余17个库存,扣10个库存,将剩余库存置为7个
D)第一个事务回滚,将剩余库存置为20个。
两个事务执行完毕之后,剩余20个库存,和我们预想中的10个剩余库存不一致!
怎么样能让最终结果与我们预想的一样呢?可以考虑让两个事务隔离开来,用最严格的方法也就是串行化,先后执行2个事务。
小结:多个事务的并发一致性,可以由隔离性保证。
虽然第三部分两个小结中指出,事务的前后并发一致性分别由原子性和隔离性保证,但这并不说明一致性=原子性+隔离性,仅仅描述一致性、原子性和隔离性之间的关系。
对于初阶Java程序员而言,只需在前人们写好的应用框架中编写DAO接口和SQL语句,便能使用MyBatis框架来实现CRUD,实现具体业务。
随着经验逐渐丰富,使用MyBatis的频次变大,需要自己搭建项目,整合数据库驱动程序、连接池、MyBatis、事务和Spring,到这时,我们得深入学习MyBatis的原理,才能很好地完成上述任务,否则,仅参考网络教程和Demo,云里雾里,知其然不知其所以然,存在很大隐患。
这篇总结面对的读者,是接触MyBatis有一段时间的新人,也可以是使用MyBatis好几年的老鸟。
总结内容包括Java JDBC的相关知识、连接池的相关概念、MyBatis的用法和基础语法、MyBatis的原理和源码解析,最后部分是MyBatis和Spring的整合、事务的整合(这段比较有意思,读者可以对照自己的项目,试试更高阶层的整合)。
这份总结中记录mysql各个存储引擎的特性,包括InnoDB、MyISAM、Memory、Archive、Maria等,还有InnoDB的架构与特性、文件结构、表空间、索引、锁和事务,另外还有一些性能优化相关的知识。
由于脑图长宽过大,转成jpg图片之后较难打开,所以我把它转成了PDF文件,可以点击链接进行下载:PDF脑图文件。
重新研究HashMap和ConcurrentHashMap时(1.8版本),再一次遇到红黑树概念,以前不太留意,现在简单地理解一下红黑树。
红黑树本质上是一种二叉查找树(就是那种可以实现二分查找,时间复杂度为O(logN)的二叉树),而且它解决了二叉查找树可能会退化成链表的缺点(这个就很牜了,不过为了解决退化的问题,红黑树的维护代价也高)。
为了防止退化,红黑树有5个约定:
从约定4和约定5,可以推导出红黑树中最长路径最多只能是最短路径的2倍(最短的路径必然全是黑色节点,最长的路径必然是间隔一红一黑),在如此严格的约定下,红黑树根本不可能退化成一条贼长的链表。
下面是红黑树的示意图(看起来确实只有红和黑2种颜色哈哈):