当前位置: 首页 > news >正文

架构师知识体系梳理

文章目录

  • 1、架构师的职责和能力
    • 1.1 架构师的主要能力
    • 1.2 架构师的思维模式
    • 1.3 架构师具备的架构原则
    • 1.4 架构师深知的架构质量属性
    • 1.5 程序设计SOLID原则
    • 1.6 架构CAP定理
    • 1.7 领域驱动设计DDD
  • 2 搜索引擎
    • 2.1 系统架构
    • 2.2 原理篇
    • 2.3 常见问题
    • 2.4 质量保障
  • 3 分布式缓存架构
    • 3.1 系统架构
    • 3.2 原理篇
    • 3.3 常见问题
    • 3.4 常见缓存类型
  • 4 消息队列和异步架构
    • 4.1 系统架构
    • 4.2 原理篇
    • 4.3 常见问题
  • 5 数据库架构及优化
    • 5.1 系统架构
    • 5.2 原理篇
    • 5.3 常见问题
  • 6 计算机网络
    • 6.1 网络通信协议
    • 6.2 网络访问组件
  • 7 负载均衡架构
    • 7.1 负载均衡典型架构
    • 7.2 负载均衡分类
    • 7.3 负载均衡算法
  • 8 SpringCloud 微服务
    • 8.1 系统架构
    • 8.2 SpringCloud 主要组件
  • 9 高可用架构
    • 9.1 系统拆分
    • 9.2 解耦
    • 9.3 异步
    • 9.4 重试
    • 9.5 补偿
    • 9.6 备份
    • 9.7 多活策略
    • 9.8 隔离
    • 9.9 限流
    • 9.10 熔断
    • 9.11 降级
    • 9.12 其他保障方式
  • 10 安全架构
    • 10.1 反攻击
    • 10.2 信息加密
    • 10.3 安全攻击
  • 12 常见Java开发框架
    • 12.1 Spring
    • 12.2 SpringBoot
  • 13 JVM虚拟机原理
    • 13.1 系统架构
    • 13.2 原理篇
    • 13.3 常见问题
    • 13.1 JVM组成架构
    • 13.2 JVM垃圾回收
  • 14 Java基础篇
  • 15 数据结构和算法
    • 15.1 数据结构
    • 15.2 常用算法
    • 15.3 衡量维度
  • 11 秒杀系统 todo
    • 11.1 更轻量快速的服务器
    • 11.2 海量数据透明垂直切分
    • 11.3 秒杀器的预防
    • 11.4 反向代理加速
    • 11.5 下单页面优化
    • 11.6 组件CDN
    • 11.7 前端优化自动化
    • 11.8 基于TT的阀门设计
    • 11.9 静态化及页面优化
  • 16 性能测试与性能优化
    • 16.1 性能优化
    • 16.2 全链路压测
    • 16.3 性能测试方法
    • 16.4 性能指标
  • 17 大数据系统
    • 17.1 大数据平台架构
    • 17.2 数据传输层
    • 17.3 数据存储层
    • 17.4 资源管理层
    • 17.5 数据计算层
    • 17.6 任务调度层
    • 17.7 业务模型层
    • 17.8 大数据算法与机器学习

1、架构师的职责和能力

1.1 架构师的主要能力

  • 决策能力:架构决策前一般都会经过深思熟虑,从而在面对诸多不确定因素的情况下,能够做出最佳的架构决策。
  • 沟通能力:架构决策需要适量的信息,但这些信息从来就不会从天而降,架构师应当快速识别架构决策可能会影响的相关利益方,并与他们充分沟通来获取信息。
  • 取舍能力:架构决策的背后是不断取舍的过程,而取舍的目的是实现架构决策的合理。
  • 认知能力:架构师不但需要从“不知道自己不知道”向“知道自己不知道”迈进,还要再向“知道自己知道”跨越,从而做出更正确的架构决策。
  • 实践能力:架构师需要时刻观察实践所带来的影响,从而不断对架构决策进行求证,及时发现问题,尽早调整方向,避免更多损失,从而形成一个良性循环。
  • 传道能力:公开及分享是架构师传道的基本手段,在内部他们可以颁布适用于内部的规范指南,及开展培训课程,在外部,他们可以发表学术论文,及与同行业进行学术交流。
  • 总结能力:架构师不但需要对成功案例进行总结,沉淀方法论,运用于未来的架构决策,更需要对失败案例进行总结,寻找不足,弥补差距,增强认知来提升架构决策能力。

1.2 架构师的思维模式

  • 抽象:架构师需要从大处着眼,隐藏细节,架构师抽象能力的高低,决定解决问题的复杂性和规模大小。在做架构时,需要先形成抽象概念,在进行模块细节设计,先宏观再微观。
  • 分层:分层和分裂是架构师需要掌握的架构模式之一,重点在于降低系统间的耦合性,提升团队协作效率。
  • 分治:架构师需要对问题进行分解,大问题分解小问题,小问题在分解小问题,先解决核心问题,影响架构质量要素的问题。
  • 演化:架构既是设计出来,也是演化出来的,三分靠设计,七分靠演化,架构师需要根据业务发展演化架构设计,避免刻舟求剑。

1.3 架构师具备的架构原则

  • 分层分裂:通过分层降低系统的耦合性,通过三层架构,每层专注自己职责;通过分裂提升协同开发的效率,一般按团队职责划分系统。
  • 异步:通过异步削峰提升系统可用性和响应速度,特别是瞬间流量情况下,系统无法立即扩容的情况下保障系统可用性。
  • 分布式:通过抽象共性模块,做到分布式服务,进而提升水平伸缩能力,比如分布式应用,分布式数据库,分布式存储。
  • 集群冗余:互联网最核心的挑战有高并发、大流量,网络复杂,安全环境等,通过集群冗余,在出现故障时能够进行主备容灾,IDC容灾等快恢。
  • 自动化:分布式系统核心的挑战是复杂,运维成本高,提升架构治理的自动化能力,做到自动化问题发现,定位,报警,故障转移,限流降级等提升系统稳定性。
  • 安全防控:互联网本来是安全的,但是因为有人研究安全就变得不安全了,所以架构还需要通过一些技术安全手段,比如HTTPS,存储加密,Web安全风控,DDoS防御等手段提升系统的安全性。

1.4 架构师深知的架构质量属性

  • 性能:性能是用户访问系统第一感受,性能太慢,会逐渐降低用户的忍耐度,最终离开。优化性能需要从客户端-网络-服务端-数据逐层优化,提升系统的响应时间,吞吐量,并发数。
  • 可用性:在出现流量高峰,故障,网络问题时,能够具备快速发现、快速定位,快速恢复能力。常用的方法有负载均衡,主从备份,限流降级,多机房容灾部署。
  • 伸缩性:过去通过购买小型机提升硬件能力,进而提升系统容量,但是互联网的高并发,无论垂直硬件能力如何提升,最终还是会触及天花板,互联网提升伸缩能力主要是分布式部署,通过负载均衡算法实现流量均衡分布。
  • 扩展性:主要是从后期维护成本,和新业务支撑效率,通过对扩展开放和修改关闭,避免新需求对老业务产生影响。
  • 安全性:核心是保障用户数据在网络传输上的完整性,避免被篡改。在遇到攻击时系统能够稳如泰山,不出现系统宕机。在网络传输,数据存储时保障机密性,比如密码不能明文传输。
  • 成本:互联网大流量,意味着带宽,机器,存储,CDN资源都需要巨大的成本,如何降低各种资源成本,人力成本,成为架构师的必要工作。

1.5 程序设计SOLID原则

  • 开闭原则:对扩展开放,对变更是封闭的。
  • LSP(接口分离原则):接口保持最小精简和颗粒度,避免客户端依赖不需要的接口,简单理解为只需要暴露必要的接口。
  • 里氏替换原则:子类替换父类后,功能运行正常。
  • SRP(单一职责原则):一个类只有一个职责,如果修改一个类有多种理由,就不属于单一职责原则。
  • 依赖倒置原则DIP:高层不能依赖底层,抽象不能依赖实现。

1.6 架构CAP定理

  • C是一致性,A是可用性,P是分区容错性。但是在软件架构设计时一般无法同时满足CAP定理。
  • 比如金融系统要求高一致性,为了做到一致性,可能会牺牲可用性。软件设计上,一般会通过备份冗余的方式提高系统的可用性,但是备份冗余会存在数据的同步(不同副本间数据需要同步),数据肯定会存在延迟,所以金融系统的高一致性,背后是可用性降低。但是随着系统发展,可用性降低不代表系统完全不可用,遇到故障时,达到基本可用,或者核心链路服务可用,其他功能降级。
  • 反过来,互联网企业更在意高可用性,牺牲一致性,但是随着系统发展,一致性不是真的牺牲,只是短暂不一致,最终还是会形成最终一致性,比如通过解析mysql binglog完成副本数据同步实现最终一致性。

1.7 领域驱动设计DDD

1.7.1 设计思路

  • 实体:领域模型对象
  • 限界上下文:对应一个组件、一个模块或一个微服务
  • 领域:包含一个组织所作事情的业务范围和做事方式
  • 战术设计:实体、值对象、聚合
  • 战略设计:领域、子域、界限上下文、上下文映射图

1.7.2 分层架构

  • 用户接口层:协议转换和适配、鉴权、参数校验和异常处理。
  • 应用层:编排领域服务、事务管理、发布应用事件。
  • 领域层:代码组织以聚合为基本单元。
  • 基础设施实现层:该层主要提供领域层接口(资源库、防腐层接口)和应用层接口(防腐层接口)的实现。

1.7.3 目的
以领域作为解决问题切入点,面对业务需求,先提炼出领域概念,并构建领域模型来表达业务问题,而构建过程中我们应该尽可能避免牵扯技术方案或技术细节。而编码实现更像是对领域模型的代码翻译,代码(变量名、方法名、类名等)中要求能够表达领域概念,让人见码明义。

2 搜索引擎

2.1 系统架构

2.1.1 搜索系统整体架构图

2.1.2 es整体架构图

2.1.3 es存储架构图

2.1.4 数据逻辑结构图

2.1.5 存储目录结构图

2.2 原理篇

2.2.1 query理解层

  • 智能分词

  • 实体识别
  • 多日期识别
  • 智能纠错
    • 输入:啊里巴巴的收益率
    • 输出:啊里巴巴 》阿里巴巴
    • 智能纠错将序列标注识别到的标签链接到到知识库中的最有可能的标签。纠错一般分为以下两个个步骤。
    • 1)基于字典:根据维护好的字典,将“啊里巴巴”映射到“阿里巴巴”,若能够成功映射,则返回对应结果即可,若无结果返回,则进行下一步操作。
    • 2)基于编辑距离:返回“啊里巴巴”与库中编辑距离最近的标签
  • 查询改写:搜索后台配置同义词库,对于非标准词改写成对应的标准词进行查询。
  • 意图识别
    • 使用文本分类模型来进行意图识别。模型的输入是问句,输出标签为意图识别的类型。需要标注一定量的模型训练语料,可以结合句式模板的进行快速扩展语料。
    • 一般情况下首选BERT文本分类模型,能够获得很高的文本分类准确率;在需要轻量化模型或硬件资源有限等情况下可以选择TextCNN等模型。
  • 归一处理
  • 类目偏好预测

2.2.2 es搜索数据过程

  • 客户端发送请求到一个 coordinate node;
  • 协调节点将搜索请求转发到所有的 shard 对应的 primary shard 或 replica shard,采用的是随机轮询算法;
  • query phase:每个 shard 将自己的搜索结果(其实就是一些 doc id)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果;
  • fetch phase:接着由协调节点根据 doc id 去各个节点上拉取实际的 document 数据,最终返回给客户端;

2.2.3 lucene底层快速检索的原理

  • 倒排索引(inverted index):构建词元到倒排列表的映射
  • 词元字典(term dictionary):ES 为了能快速查找到 term,将所有的 term 排了一个序,二分法查找。
    • term dictionary 在磁盘上面是分 block 保存的,一个 block 内部利用公共前缀压缩,比如都是 Ab 开头的单词就可以把 Ab 省去
  • 词元索引(term index):为了快速查找term dictionary中的term而设计的字典树,这棵树不会包含所有的 term,它包含的是 term 的一些前缀,通过 term index 可以快速地定位到 term dictionary 的某个 offset,然后从这个位置再往后顺序查找。
    • term index 在内存中是以 FST(finite state transducers)的数据结构保存的。
  • 倒排列表(postings list)FOR 压缩技术:
    • Step1: 通过增量编码的方式压缩;
    • Step2: 把所有的文档分成很多个 block,每个 block 正好包含 256 个文档;
    • Step3: 计算出存储这个 block 里面所有文档最多需要多少位来保存每个 id,并且把这个位数作为头信息(header)放在每个 block 的前面。
  • 多个倒排列表取交集原理:SkipList(跳表)
    • 查找包括两个循环,外层循环是从上层Level到底层Level,内层循环是在同一个Level,从左到右;
    • 跳表的高度大概率为O(logn),所以外层循环的次数大概率为O(logn);
    • 在上层查找比对过的key,不会再下层再次查找比对,任意一个key被查找比对的概率为1/2,因此内存循环比对的期望次数是2也就是O(1);
    • 因此最终的时间复杂度函数O(n) = O(1)*O(logn)也就是O(logn);

2.2.4 es写数据过程

  • 客户端选择一个节点作为协调节点(coordinating node);
  • coordinating node 对 document 进行路由,将请求转发给对应的 node(有 primary shard的node);
  • 实际的 node 上的 primary shard 处理请求,然后将数据同步到 replica node;
  • coordinating node 如果发现 primary node 和所有 replica node 都搞定之后,就返回响应结果给客户端。

2.2.5 lucene写数据底层原理

  • 先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件;
  • refresh:如果 buffer 快满了,或者到一定时间(默认1s),就会将内存 buffer 数据 refresh 到一个新的 segment file 中,但是此时数据不是直接进入 segment file 磁盘文件,而是先进入 os cache ;
  • flush:当 translog 达到一定长度的时候,就会触发 commit 操作,就是将 buffer 中现有数据 refresh 到 os cache 中去,清空 buffer,同时强行将 os cache 中目前所有的数据都 fsync 到磁盘文件中去。最后清空 现有 translog 日志文件,重启一个 translog(默认30分钟执行一次);

2.2.6 删除/更新数据/段合并底层原理

  • 删除:如果是删除操作,commit 的时候会生成一个 .del 文件,里面将某个 doc 标识为 deleted 状态,那么搜索的时候根据 .del 文件就知道这个 doc 是否被删除了。
  • 更新:将原来的 doc 标识为 deleted 状态,然后新写入一条数据;
  • 段合并:buffer 每 refresh 一次,就会产生一个 segment file,这样下来 segment file 会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个 segment file 合并成一个,同时这里会将标识为 deleted 的 doc 给物理删除掉,然后将新的 segment file 写入磁盘,这里会写一个 commit point,标识所有新的 segment file,然后打开 segment file 供搜索使用,同时删除旧的 segment file。

2.2.7 内存分配机制

  • ES 堆内存建议不超过32G,底层lucene需要一定的堆外内存空间进行访问索引段文件。
  • 超过32G的话,内存指针压缩技术(指针采用偏移量而非实际地址)会失效,反而浪费了内存。

2.3 常见问题

2.3.1 脑裂问题

  • 现象:ES在主节点上产生分歧,产生多个主节点,从而使集群分裂,使得集群处于异常状态。这个现象叫做脑裂。
  • 解决办法:
    • 保证网络稳定,及时预警,重启集群;
    • 避免master节点因为工作负载过大出现响应中断从而引发脑裂;
    • 降低master节点压力,可以考虑做读写分离、分担客户端请求等。
    • 参数设置优化:discovery.zen.minimum_master_nodes
      • 一个节点需要看到的具有master节点资格的最小数量,然后才能在集群中做操作。官方的推荐值是(N/2)+1,其中N是具有master资格的节点的数量,设置这个参数后,只有足够的master候选节点时,才可以选举出一个master。

2.3.2 深度分页

  • 背景:由于ES是分布式的,在请求第1000页的第10条数据时,没办法在各个分片先排好序再取10条过来,需要在各个分片各取10010个结果,然后在协调节点合并后,再排序,然后取10条。这个过程随着页数的越深,排序的成本越高。
  • 解决办法:
    • 调大index.max_result_window参数:但是这个办法只是提高了分页的页数,没有最终解决问题,而且页数越深,所需要的内存越高,容易应发OOM问题,故调整此参数一定要慎重!
    • 尝试避免深度分页:目前主流搜索引擎都是采用 截取多少页+下一页 的方式处理此问题,首先搜索引擎的初衷就是快速搜索到用户想要的数据,若前10页都没有的话,跳转到后面的意义也不大,但是同时保留用户习惯,允许用户点击下一页的方式继续浏览后面的内容。
    • scroll遍历:对当前查询的索引维护一份快照,下次查询时传入scroll_id进行分页处理。
      • 优点:适用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的。
      • 缺点:scroll_id会占用大量的资源(特别是排序的请求);是生成的历史快照,对于数据的变更不会反映到快照上。
    • Scroll Scan:基于scroll提高性能,但是不支持排序,不适合排序场景。
    • Search After:es维护一个实时游标,它以上一次查询的最后一条记录为游标,方便对下一页的查询,它是一个无状态的查询,因此每次查询的都是最新的数据。
      • 优点:无状态查询,可以防止在查询过程中,数据的变更无法及时反映到查询中;不需要维护scroll_id,不需要维护快照,因此可以避免消耗大量的资源。
      • 缺点:容易导致跨页面的不一致问题;至少需要一个全局唯一的排序字段;不适用于大幅度跳页查询。
      • 注:为防止跨页不一致问题,可以创建一个时间点(PIT)来在搜索过程中保留当前索引状态。

2.3.3 性能优化

  • 关闭内存交换:bootstrap.memory_lock:true 允许 JVM 锁住内存,禁止操作系统交换出去。
  • 内存配置:-Xms和-Xmx设置为相同的值,jvm heap建议不要超过32G,一般略低于系统内存的一半,不然内部的对象指针压缩技术会失效,反而浪费更多的内存。
  • 加大文件句柄数:Lucene 使用了大量的文件,linux默认运行单个进程打开1024个文件句柄,这显然是不够的,最好按照此方式调整( ulimit -n 65536)。
  • 配置最大系统内存映射数量:便有足够的虚拟内存可用于 mmapped 文件。
  • 索引文件大小最好不要超过OS Cache大小:这样可以确保索引数据都是从内存搜索,避免直接从磁盘查找。
  • 数据预热:定时每隔段时间访问一些热点数据,确保热点数据是在OS Cache中。
  • 冷热分离:将访问频率低的数据单独放到一个索引中,确保热数据可以大概率进OS Cache。
  • Doc模型设计优化:复杂join操作尽量在SQL层面就做好,ES层面尽量只做基础的搜索查询即可。
  • 避免深度分页问题:产品层面优化,不允许跳页,超过一定的页数后用search_after实现下一页操作。

2.3.4 普通子对象、嵌套对象和父子文档的区别

  • 普通子对象:实现一对多关系,会损失子对象的边界,子对象的属性之前关联性丧失。
  • 嵌套对象:可以解决普通子对象存在的问题,但是它有两个缺点,一个是更新主文档的时候要全部更新,另外就是不支持子文档从属多个主文档的场景,适用于读多写少场景。
  • 父子文档:能解决前面两个存在的问题,但是它适用于写多读少的场景。
    2.3.5 聚合统计不精确问题
  • 问题分析

    如上图所示,我们进行一个terms分桶查询,取前面3个结果。ES给出的结果是 A,B,C三个term,文档数量分别是12, 6, 4。
    但是我们看最下面两个分片上的文档分布,人工也能看出来其实D应该是在结果中的,因为D的文档数量有6个,比C多,所以比较精确的结果应该是A,B,D。
    产生问题的原因在于ES在对每个分片单独处理的时候,第一个分片的结果是A,B,C,第二个分片是A,B,D,并且第一个分片的C的文档数量大于D。所以汇总后的结果是A,B,C。
  • 如何提高精准度
    • 不分片:非必要不建议。
    • 提高聚合的数量:只能解决部分问题,不能解决根本问题。
    • 调大shard_size值:这个值表示要从分片上拿来计算的文档数量。默认情况下和size是一样的。取得size的值越大,结果会越接近准确,不过很明显会影响性能。

2.3.6 搜索排名优化

  • 仅作为过滤条件的查询用filter实现,减少相关度维度分值计算;
  • 对于重要的查询可以通过调整boost配置来增加权重;
  • 使用 function_score 增加更多的评分因素:时间、热度、文档质量、运营推广等各因素;
  • 衰减函数:可以帮我们实现平滑过渡,使距离某个点越近的文档分数越高,越远的分数越低,常用于时间衰减;
  • 优化相关性算法:7.0版本默认BM25 算法,是对TF-IDF 算法的改进。
  • 分析用户行为日志,持续调优排序效果。
  • 使用 _explain 做 bad case 分析

2.4 质量保障

2.4.1 研发规范

  • 设计阶段:产品需求详细评估,要采用高可用、可扩展设计;
  • 编码阶段
    • 要符合一定的代码规范,包括通用代码规范和工程结构规范;
    • 单测覆盖率要符合要求,包括单元测试通过率和代码覆盖率;
    • 系统需要记录一些关键节点的日志和性能日志;
    • 系统需要符合安全漏洞修复规范。
  • 发布阶段:核心关键系统要在非高峰期发布,并且重要的功能点需要先进行灰度发布或AB测试,没问题后方可全量。

2.4.2 容量保障

  • 容量评估
    • 机器容量:包括CPU、内存、磁盘以及访问带宽容量评估;
    • DB容量:根据业务数据的规模评估数据的量级和更新TPS;
    • 缓存容量:根据业务的使用场景和技术方案评估缓存容量。
  • 压测摸底:压测的目标至少要达到线上QPS的两倍;
  • 限流方案:{X}分钟内达到{Y}QPS的话则进行限流;
  • 熔断方案:{X}分钟内平均响应时间达到{Y}秒或者{X}分钟内错误率大于{Y}%则进行熔断;
  • 降级方案:包括限流降级、超时降级和故障降级。

2.4.3 监控告警

  • 应用基础监控
  • 网关监控
  • 服务监控
  • 业务监控
  • 限流监控

2.4.4 应急快反

  • 日常预案
    • 硬件异常预案
    • 中间件异常预案
    • 业务异常预案
  • 大促预案
  • 预案执行规范和演练

3 分布式缓存架构

3.1 系统架构

3.1.1 单机模式+混合持久化

当 AOF rewrite 时,Redis 先以 RDB 格式在 AOF 文件中写入一个数据快照,再把在这期间产生的每一个写命令,追加到 AOF 文件中。因为 RDB 是二进制压缩写入的,这样 AOF 文件体积就变得更小了。

3.1.2 主从模式+哨兵集群

主从模式可以分担读请求的压力,哨兵集群可以检测master节点是否正常,一旦异常,将会自动进行master节点切换。
3.1.3 分片集群+主从+哨兵

分片集群可以分担写请求压力,主从+哨兵模式的功能可以参考 4.1.2 主从模式+哨兵集群。

3.2 原理篇

3.2.1 Redis为什么这么快?

  • 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)。
  • 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的;
  • 核心处理模块采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题。
  • 使用多路I/O 复用模型,非阻塞 IO。

3.2.2 Redis有哪些持久化方式?各自有什么优缺点?

  1. RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期。
  • 优点:IO性能最大化,fork子线程异步处理,不影响主线程;文件保存到磁盘,容灾性好;相对于数据集大时,比 AOF 的启动效率更高。
  • 缺点:数据安全性低,RDB是间隔一段时间进行的持久化,会存在数据丢失可能。
  1. AOF持久化方式:是指所有的命令行记录以 redis 命令请求协议的格式完全持久化存储)保存为aof文件。
  • 优点:数据安全,aof采用不同的刷盘策略,若采用always级别,则每执行一次命令,就记录一次aof日志;即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题;AOF可以采用rewrite模式,对日志进行定期合并重写。
  • 缺点:AOF文件比RDB文件大,且恢复速度慢;数据集大的时候,比rdb启动效率低。

3.2.3 在生成RDB期间,Redis可以同时处理写请求么?

  • 可以的,Redis 使用操作系统的多进程写时复制技术**COW(Copy On Write)**来实现快照持久化,保证数据一致性。
  • Redis在持久化时会调用glibc的函数fork产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。
  • 当主线程执行写指令修改数据的时候,这个数据就会复制一份副本,bgsave子进程读取这个副本数据写到RDB文件。
  • 这既保证了快照的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。

3.2.4 一致性hash算法的原理是什么?

  • 一致性hash算法本质上也是一种取模算法,不过,不同于上边按服务器数量取模,一致性hash是对固定值2^32 取模。
  • hash环: 我们可以将这2^32 个值抽象成一个圆环⭕️,圆环的正上方的点代表0,顺时针排列,以此类推,1、2、3、4、5、6……直到2^32 - 1,而这个由2的32次方个点组成的圆环统称为hash环。
  • 服务器映射到hash环: 运用公式 hash(服务器ip)% 2^32 ,使用服务器IP地址进行hash计算,用哈希后的结果对2^32 取模,结果一定是一个0到2^32 - 1之间的整数,而这个整数映射在hash环上的位置代表了一个服务器,依次将node0、node1、node2三个缓存服务器映射到hash环上。
  • 对象key映射到hash环: 接着在将需要缓存的key对象也映射到hash环上,hash(key)% 2^32 ,从缓存对象key的位置开始,沿顺时针方向遇到的第一个服务器,便是当前对象将要缓存到的服务器。
  • 说明: 因为被缓存对象与服务器hash后的值是固定的,所以在服务器不变的条件下,对象key必定会被缓存到固定的服务器上。具体过程入图所示:

3.2.5 Redis为什么不用一致性hash算法进行集群扩容而用哈希槽?

  • 原因:一致性哈希算法对于数据分布、节点位置的控制并不是很友好。
  • 哈希槽
    • 哈希算法:redis cluster 的 hash 算法不是简单的 hash(),而是 crc16 算法,一种校验算法。
    • 槽位的概念:空间分配的规则,其实哈希槽的本质和一致性哈希算法非常相似,不同点就是对于哈希空间的定义。一致性哈希的空间是一个圆环,节点分布是基于圆环的,无法很好的控制数据分布。而 redis cluster 的槽位空间是自定义分配的,类似于 windows 盘分区的概念。这种分区是可以自定义大小,自定义位置的。

3.2.6 为什么Redis集群设计为16384个槽?

  • 如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
  • 集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
  • 槽位越小,节点少的情况下,压缩比高。

3.2.7 什么是IO多路复用?

  • IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。
  • 使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。完整的流程图如下:

3.2.8 Redisson实现的原理是什么?

  • 背景:分布式锁可能存在锁过期释放,业务没执行完的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
  • 当前开源框架Redisson就解决了这个分布式锁问题。我们一起来看下Redisson底层原理是怎样的吧:
  • 只要线程一加锁成功,就会启动一个watch dog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。
  • 因此,就是使用Redisson解决了锁过期释放,业务没执行完问题。

3.2.9 Redis主从数据同步(主从复制)的过程是什么?

  • slave启动后,向master发送sync命令。
  • master收到sync之后,执行bgsave保存快照,生成RDB全量文件。
  • master把slave的写命令记录到缓存。
  • bgsave执行完毕之后,发送RDB文件到slave,slave执行。
  • master发送缓冲区的写命令给slave,slave接收命令并执行,完成复制初始化。
  • 此后,master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性。

3.2.10 Redis线程模型是什么?

  • Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
    • 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
    • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
  • 虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。

3.3 常见问题

3.3.1 Redis有哪些应用场景?

  • 缓存: 将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。
  • 分布式锁: 在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用Redis自带的SETNX命令或者RedLock分布式锁实现,详细请点击这里。
  • 限流: 主要基于Redis的令牌桶算法实现,详细请点击这里。
  • 计数器: 可以对String类型的key进行自增自减运算,从而实现计数器功能。
  • 会话缓存: 可以使用 Redis 来统一存储多台应用服务器的会话信息。当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。
  • 消息队列(发布/订阅功能): Redis的消息队列定位的是轻量级场景,重度场景建议使用kafka或者mq。
  • 其它: Redis数据类型Set可以实现交集、并集等操作,从而实现共同好友等功能。ZSet可以实现有序性操作,从而实现排行榜等功能。

3.3.2 为什么Redis6.0之后改多线程呢?

  • Redis6.0之前,Redis在处理客户端的请求时,包括读socket、解析、执行、写socket等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。
  • Redis6.0之前为什么一直不使用多线程?使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。
  • redis使用多线程并非是完全摒弃单线程,redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程
  • 这样做的目的是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率,从而整体提高redis的性能。

3.3.3 Redis哪些情况会有丢数据的可能?

  • AOF落数据的策略设置为不是每次写入都刷盘的话,会有丢数据的可能。
  • 主从同步时若主库的生产速度比从库的拉取速度快很多时,就会出现套圈现象,此时从库数据丢失。

3.3.4 什么是Redis缓存穿透以及对应的解决方案是什么?

  • 定义: 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
  • 解决办法: 使用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

3.3.5 什么是Redis缓存雪崩以及对应的解决方案是什么?

  • 定义: 如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
  • 解决办法:
    • 失效时间随机:在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
    • 数据预热:可以通过缓存reload机制,预先去更新缓存,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀;
    • 双缓存策略:Cache1为原始缓存,Cache2为拷贝缓存,Cache1失效时,可以访问Cache2,Cache1缓存失效时间设置为短期,Cache2设置为长期。
    • 加锁排队:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待;

3.3.6 什么是Redis缓存击穿以及对应的解决方案是什么?

  • 定义: 缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
  • 解决办法: 用分布式锁控制访问的线程,如:使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数据库。

3.3.7 如何保证缓存与数据库双写时的数据一致性?

  • 背景:你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题。
  • 结论:没办法做到数据库与缓存绝对的一致性,可以做到最终一致性,缓存系统适用的场景就是非强一致性的场景。
  • 常规解决措施:
    • 缓存延时双删:写请求 -> 删除缓存 -> 更新数据库 -> 休眠一会儿,再删除缓存。
    • 删除缓存重试机制:若删除缓存失败,可以进行重试多删除几次,确保数据正常删除。
    • 给key设置过期时间,达到最终数据一致。
  • 详细内容请点击这里。

3.4 常见缓存类型

  • 分布式对象缓存:目前主要是基于Redis实现

  • 应用程序缓存:本地缓存,主要基于 Guava 、Caffeine 实现,其中两者之间的区别如下:

    • Guava: 已经比较完善了,满足了绝大部分本地缓存的需求,Guava的读写操作后,同时会对数据做过期时间处理,性能会受到一点影响。
    • Caffine:除了提供 Guava 已有的功能外,同时还加入了一些扩展功能,Caffine对读写时间是异步操作的,性能会比Guava高出很多。
    • 详细内容请点击这里
  • 前端缓存:主要包含Service Worker、Memory Cache、Disk Cache,按照上述优先级分别访问无数据后,则开始通过网络请求的方式获取数据。

  • 代理与反向代理缓存

    • 代理:可以缓存一些预计不会发生改变的数据,比如一些静态数据等。
    • 反向代理:当另一个客户端发送对相同内容的请求时,它可以从其本地缓存中获取数据返回,而不用请求真实服务器。这样可以降低请求延迟、减少服务器负载。
  • CDN缓存

    • 缓存系统最基本的工作单元就是许许多多的Cache节点(缓存服务器),Cache节点负责直接响应最终用户的访问请求,把缓存在本地的内容快速提供给用户。
    • Cache节点也会与源站进行内容同步,把更新的内容以及本地没有的内容从源站点获取并保存在本地。
  • JVM编译缓存:本地内存缓存

  • 数据库缓存

    • 所以缓存池,简单来说就是一块「内存区域」,通过内存的速度来弥补磁盘速度较慢,导致对数据库造成性能的影响。
    • 读操作:在数据库中进行读取页的操作,首先把从磁盘读到的页存放在缓存池中,下一次读取相同的页时,首先判断该页是不是在缓存池中。
    • 写操作:对于数据库中页的修改操作,首先修改在缓存池中的页,然后在以一定的频率刷新到磁盘,并不是每次页发生改变就刷新回磁盘,而是通过 checkpoint 的机制把页刷新回磁盘。
  • 操作系统缓存

    • 在Linux的实现中,文件Cache分为两个层面,一是Page Cache,另一个Buffer Cache,每一个Page Cache包含若干Buffer Cache。
    • Page Cache:主要用来作为文件系统上的文件数据的缓存来用,尤其是针对当进程对文件有read/write操作的时候。
    • Buffer Cache:则主要是设计用来在系统对块设备进行读写的时候,对块进行数据缓存的系统来使用。
  • CPU缓存

    • L1 缓存分成两种,一种是指令缓存,一种是数据缓存。L2 缓存和 L3 缓存不分指令和数据。
    • L1 和 L2 缓存在每一个 CPU 核中,L3 则是所有 CPU 核心共享的内存。
    • L1、L2、L3 的越离 CPU 近就越小,速度也就越快,越离 CPU 远,速度也越慢。

4 消息队列和异步架构

4.1 系统架构

4.1.1 Kafka 整体架构

4.1.2 RabbitMQ 整体架构

4.1.3 RocketMQ 整体架构

  • RocketMQ 5.0 的定位是云原生的"消息、事件、流"超融合处理平台,用以帮助用户更容易地构建下一代事件驱动和流处理应用。

4.2 原理篇

4.3 常见问题

4.3.1 Kafka 和 RabbitMQ 有什么区别,该如何选型?

  • RabbitMQ:主要用于实时的,对可靠性要求较高的消息传递上。
  • Kafka:用于处于活跃的流式数据,大数据量的数据处理上,对高吞吐会有一定的要求,对数据丢失相对不敏感。

4.3.2 RabbitMQ 是如何做持久化的?

  • RabbitMQ的消息默认是在内存里的,实际上不光是消息,Exchange路由等信息实际都在内存中。内存的优点是高性能,问题在于故障后无法恢复。所以RabbitMQ也支持持久化的存储,也就是写磁盘。
  • 要在RabbitMQ中持久化消息,要同时满足三个条件:消息投递时使用持久化投递模式、目标交换器是配置为持久化的、目标队列是配置为持久化的。
  • RabbitMQ持久化消息的方式是常见的写日志方式:
    • 当一条持久化消息发送到持久化的Exchange上时,RabbitMQ会在消息提交到日志文件后,才发送响应。
    • 一旦这条消息被消费后,RabbitMQ会将会把日志中该条消息标记为等待垃圾收集,之后会从日志中清除。
    • 如果出现故障,自动重建Exchange,Bindings和Queue,同时通过重播持久化日志来恢复消息。
  • 消息持久化的优缺点很明显,拥有故障恢复能力的同时,也带来了性能的急剧下降。同时,由于RabbitMQ默认情况下是没有冗余的,假设一个持久化节点崩溃,一直到该节点恢复前,消息和队列都无法恢复。

4.3.3 RabbitMQ 如何保证消息的顺序性?

  • 同一时序性的消息发往同一个队列,然后由固定的某个消费者消费。
  • 若单个消费者存在性能瓶颈,可开启多个本地队列消费,但是必须要保证消息的时序性。

4.3.4 RabbitMQ 如何保证消息不丢失?

  • 生产者发送消息至MQ的数据丢失:在生产者端开启comfirm 确认模式,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 ack 消息,告诉你说这个消息 ok 了。
  • 消息中心数据丢失:MQ设置为持久化。将内存数据持久化到磁盘中。
  • 消费者消费数据丢失:用 RabbitMQ 提供的 ack 机制,每次消费者确保处理完的时候,再在程序里 ack消息,没ack的消息MQ会重新分配给消费者。

4.3.5 如何保证消息不被重复消费?

  • 若是将消息进行写库操作,则先查是否存在,若存在,则update,否则insert。
  • 若是写缓存,每次都是set操作即可。
  • 若是其他场景,则让生产端每次在消息中产生全局唯一ID,每次消费者在消费消息的时候将此全局ID写入redis中,每次消费前判断下是否消费过即可。

4.3.6 RabbitMQ 如何处理消息堆积问题?

  • 产生问题背景:1)当生产者生产速度远远消费者消费速度;2)当消费者宕机没有及时重启。
  • 解决办法:
    • 运维层面:扩大队列的容量,提高堆积上限
    • 应用层面:在消费者机器重启后,增加更多的消费者进行处理
    • 应用层面:在消费者处理逻辑内部开辟线程池,利用多线程的方式提高处理速度
    • 惰性队列:接收到消息后直接存入磁盘而非内存,消费者要消费消息时才会从磁盘中读取并加载到内存中,它支持百万级消息的存储
      • 缺点:消息的时效性会降低,性能受限于磁盘的IO。

4.3.7 Kafka为什么吞吐量大、速度快?

  • 分区分段 + 索引
    • Kafka消息按照topic分类,然后topic还按照分区(partition)存储,分区内部实际是按照segment分段存储的。
    • Kafka又默认为分段后的数据文件建立了索引文件,就是文件系统上的.index文件。
    • 这种分区分段+索引的设计,不仅提升了数据读取的效率,同时也提高了数据操作的并行度。
  • 顺序读写:Kafka是将消息记录持久化到本地磁盘中的,采用顺序读写的方式落数据(磁盘顺序读写性能很高)。
  • Page Cache:磁盘文件会先刷新操作系统缓存,Kafka的读写操作基本上是基于堆外内存的,读写速度得到了极大的提升。
  • 零拷贝:linux操作系统 “零拷贝” 机制使用了sendfile方法, 允许操作系统将数据从Page Cache 直接发送到网络,只需要最后一步的copy操作将数据复制到 NIC 缓冲区, 这样避免重新复制数据 。示意图如下:
  • 批量读写:在向Kafka写入数据时,可以启用批次写入,这样可以避免在网络上频繁传输单个消息带来的延迟和带宽开销。
  • 批量压缩:在很多情况下,系统的瓶颈不是CPU或磁盘,而是网络IO,进行数据压缩会消耗少量的CPU资源,却带来较大的网络IO性能提升。

5 数据库架构及优化

5.1 系统架构

  • 一主多从+读写分离:适合高读低写并发、低数据量场景
  • 分库分表+数据库中间件:适合高读写并发、高数据量场景
  • MySQL内部体系架构图

5.2 原理篇

5.2.1 索引分类有哪些?

  • 根据叶子节点的内容,索引类型分为主键索引和非主键索引;
  • 主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引;
  • 非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引。

5.2.2 InnoDB 为什么设计B+树,而不是B-Tree,Hash,二叉树,红黑树?

  • 哈希索引能够以 O(1) 的速度处理单个数据行的增删改查,但是面对范围查询或者排序时就会导致全表扫描的结果。
  • 二叉树:树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且IO代价高。
  • 红黑树:树的高度随着数据量增加而增加,IO代价高。
  • B树:可以在非叶结点中存储数据,由于所有的节点都可能包含目标数据,我们总是要从根节点向下遍历子树查找满足条件的数据行,这个特点带来了大量的随机I/O,造成性能下降。
  • B+树:所有的数据行都存储在叶节点中,而这些叶节点可以通过『指针』依次按顺序连接,当我们在如下所示的 B+ 树遍历数据时可以直接在多个子节点之间进行跳转,这样能够节省大量的磁盘 I/O 时间。

5.2.3 Innodb为什么要用自增id作为主键?

  • 表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。
  • 如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置,频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构。

5.2.4 讲一讲MySQL的最左前缀原则?

  • 最左前缀原则就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
  • count(*)、count(0)、count(id)实现方式的区别?
  • 对于上述三个count函数来说,优化器可以选择扫描成本最小的索引执行查询,从而提升效率,因此它们的执行过程是一样的。
  • 而对于count(非索引列)来说,优化器选择全表扫描,说明只能在聚集索引的叶子结点顺序扫描。
  • count(二级索引列)只能选择包含我们指定的列的索引去执行查询,可能导致优化器选择的索引执行的代价并不是最小。

5.2.5 一条 MySQL 语句执行步骤是什么样的?

5.2.6 order by 排序内部原理是什么样的?

  • MySQL会为每个线程分配一个内存(sort_buffer)用于排序该内存大小为sort_buffer_size。
  • 如果排序的数据量小于sort_buffer_size,排序将会在内存中完成。
  • 如果排序数据量很大,内存中无法存下这么多数据,则会使用磁盘临时文件来辅助排序,也称外部排序。
  • 在使用外部排序时,MySQL会分成好几份单独的临时文件用来存放排序后的数据,然后在将这些文件合并成一个大文件。

5.2.7 MySQL加锁规则?

  • 原则 1:加锁的基本单位是 next-key lock,next-key lock 是前开后闭区间。
  • 原则 2:查找过程中访问到的对象才会加锁
  • 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
  • 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。

5.2.8 Mysql 主从复制原理的是什么样的?

  • Master的更新事件(update、insert、delete)会按照顺序写入bin-log中。当Slave连接到Master的后,Master机器会为Slave开启binlog dump线程,该线程会去读取bin-log日志。
  • Slave连接到Master后,Slave库有一个I/O线程 通过请求binlog dump thread读取bin-log日志,然后写入从库的relay log日志中。
  • Slave还有一个 SQL线程,实时监控 relay-log日志内容是否有更新,解析文件中的SQL语句,在Slave数据库中去执行。

5.2.9 MyISAM 和 InnoDB 索引有什么区别?

  • MyISAM
    • 索引与数据分开存储;
    • 索引叶子节点存储指针,主键索引与普通索引无太大区别;
  • InnoDB
    • 聚集索引和行数据统一存储;
    • 聚集索引存储数据行本身,普通索引存储主键;
    • 不宜使用较长的列作为PK;
    • 普通索引可能存在回表查询,常见的解决方案是覆盖索引;

5.2.10 MySQL 一棵 B+ 树到底能存多少条数据呢?

  • 数据持久化存储磁盘里,磁盘的最小单元是扇区,一个扇区的大小是 512个字节;
  • 文件系统的最小单元是块,一个块的大小是 4K;
  • InnoDB存储引擎,有自己的最小单元,称之为页,一个页的大小是16K;
  • mysql数据库中,table表中的记录都是存储在页中,那么一页可以存多少行数据?假如一行数据的大小约为1K字节,那么按 16K / 1K = 16,可以计算出一页大约能存放16条数据。
  • B+树的存储结构如下:
  • 假设主键ID为bigint类型,长度为8字节,而指针大小在InnoDB源码中设置为6字节,这样一共14字节。
  • 那么一个页中能存放多少这样的组合,就代表有多少指针,即 16384 / 14 = 1170。
  • 那么可以算出一棵高度为3的B+树,能存放 1170(健值和指针) * 1170(健值和指针) * 16(实际数据,叶子节点) = 21902400 条这样的数据记录。
  • 千万级的数据存储只需要约3层B+树,查询数据时,每加载一页(page)代表一次IO。所以说,根据主键id索引查询约3次IO便可以找到目标结果。

5.3 常见问题

5.3.1 日常开发中你是怎么优化SQL的?

  • 添加合适索引、优化表结构、优化查询语句。

5.3.2 MySQL大表查询为什么不会爆内存?

  • MySQL 是“边读边发的”,服务端并不需要保存一个完整的结果集。取数据和发数据的流程都是通过一个next_buffer来操作的。内存的数据页是在 Buffer Pool (BP) 中管理的,BP采用了改进的LRU算法,使得可以淘汰过期不用的数据。

5.3.3 深度分页(超大分页)怎么处理?

  • 1)用id优化:先找到上次分页的最大ID,然后利用id上的索引来查询;2)在业务允许的情况下限制页数。

5.3.4 只查询一条数据,但是也执行非常慢,原因一般有哪些?

  • 1)MySQL数据库本身被堵住了,比如:系统或网络资源不够;2)SQL语句被堵住了,比如:表锁,行锁等;3)确实是索引使用不当,没有走索引;4)表中数据的特点导致的,走了索引,但回表次数庞大。

5.3.5 有哪些场景会导致索引失效?

  • 对索引使用左或者左右模糊匹配;
  • 对索引使用函数/对索引进行表达式计算;
  • 对索引隐式类型转换;
  • WHERE子句中的OR;

5.3.6 非聚簇索引一定会回表查询吗?

  • 不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。一个索引包含(覆盖)所有需要查询字段的值,被称之为“覆盖索引”。

5.3.7 Mysql主从同步延时产生原因?怎么优化?

  • 主节点如果执行一个很大的事务,那么就会对主从延迟产生较大的影响
  • 网络延迟,日志较大,slave数量过多
  • 主上多线程写入,从节点只有单线程同步
  • 机器性能问题,从节点是否使用了“烂机器”
  • 锁冲突问题也可能导致从机的SQL线程执行慢

5.3.8 binlog/redolog/undolog是什么?

  • binlog是Mysql数据库级别的文件,记录对Mysql数据库执行修改的所有操作,不会记录select和show语句。
  • redolog中记录的是要更新的数据,比如一条数据已提交成功,并不会立即同步到磁盘,而是先记录到redo log中,等待合适的时机再刷盘,为了实现事务的持久性。
  • undolog用于数据的撤回操作,它保留了记录修改前的内容。通过undo log可以实现事务回滚,并且可以根据undo log回溯到某个特定的版本的数据,实现MVCC。

5.3.9 分库分表常见方案有哪些?

  • 垂直分表:系统绝对并发量并没有上来,表的记录并不多,但是字段多,并且热点数据和非热点数据在一起,单行数据所需的存储空间较大。以至于数据库缓存的数据行减少,查询时会去读磁盘数据产生大量的随机读IO,产生IO瓶颈。
  • 垂直分库:系统绝对并发量上来了,并且可以抽象出单独的业务模块。
  • 水平分表:系统绝对并发量并没有上来,只是单表的数据量太多,影响了SQL效率,加重了CPU负担,以至于成为瓶颈。
  • 水平分库:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。

6 计算机网络

6.1 网络通信协议

6.1.1 非阻塞网络I/O

  • Socket:网络上的两个应用程序通过一个双向通信连接实现数据交换的编程接口API。
  • IO操作阶段:1)等待数据准备;2)数据从内核空间拷贝到用户空间。
  • BIO(阻塞IO):从进程发起IO操作,一直等待上述两个阶段完成,此时两阶段一起阻塞。
  • NIO(非阻塞IO):进程一直询问IO准备好了没有,准备好了再发起读取操作,这时才把数据从内核空间拷贝到用户空间。第一阶段不阻塞但要轮询,第二阶段阻塞。
  • 异步IO:进程发起读取操作会立即返回,等到数据准备好且已经拷贝到用户空间了再通知进程拿数据。两个阶段都不阻塞。
  • 多路复用IO:多个连接使用同一个select去询问IO准备好了没有,如果有准备好了的,就返回有数据准备好了,然后对应的连接再发起读取操作,把数据从内核空间拷贝到用户空间。两阶段分开阻塞。
  • 信号驱动IO:进程发起读取操作会立即返回,当数据准备好了会以通知的形式告诉进程,进程再发起读取操作,把数据从内核空间拷贝到用户空间。第一阶段不阻塞,第二阶段阻塞。
  • 详细请点击这里。

6.1.2 网络模型

  • OSI七层模型
    • 应用层:OSI参考模型中最靠近用户的一层,是为计算机用户提供应用接口,也为用户直接提供各种网络服务。我们常见应用层的网络服务协议有:HTTP,HTTPS,FTP,POP3、SMTP等。
    • 表示层:提供各种用于应用层数据的编码和转换功能,确保一个系统的应用层发送的数据能被另一个系统的应用层识别。如果必要,该层可提供一种标准表示形式,用于将计算机内部的多种数据格式转换成通信中采用的标准表示形式。数据压缩和加密也是表示层可提供的转换功能之一。
    • 会话层:会话层就是负责建立、管理和终止表示层实体之间的通信会话。该层的通信由不同设备中的应用程序之间的服务请求和响应组成。
    • 传输层:建立了主机端到端的链接,传输层的作用是为上层协议提供端到端的可靠和透明的数据传输服务,包括处理差错控制和流量控制等问题。
    • 网络层:本层通过IP寻址来建立两个节点之间的连接,为源端的运输层送来的分组,选择合适的路由和交换节点,正确无误地按照地址传送给目的端的运输层。
    • 数据链路层: 将比特组合成字节,再将字节组合成帧,使用链路层地址 (以太网使用MAC地址)来访问介质,并进行差错检测。
    • 物理层:实际最终信号的传输是通过物理层实现的。通过物理介质传输比特流。规定了电平、速度和电缆针脚。常用设备有(各种物理设备)集线器、中继器、调制解调器、网线、双绞线、同轴电缆。这些都是物理层的传输介质。
  • TCP/IP五层模型
    • TCP/IP五层协议和OSI的七层协议对应关系如下,在每一层都工作着不同的设备,比如我们常用的交换机就工作在数据链路层的,一般的路由器是工作在网络层的。
    • 在每一层实现的协议也各不同,即每一层的服务也不同.下图列出了每层主要的协议。其中每层中具体的协议,我会在后面的逐一学习。
  • 详细请点击这里。

6.2 网络访问组件

6.2.1 Tomcat

  • Servlet 请求和响应流程
    • Tomcat的工作原理:Servlet 容器负责定义 Servlet 接口规范和管理 Servlet 程序,所有的 Web 应用都会以 Servlet 的形式存在,每个 Servlet 都需要遵循 Servlet 接口的定义,根据 Servlet 的处理流程响应用户的请求。
  • Tomcat 的连接器与容器
    • 当浏览器有 Request 到 Tomcat 的时候,连接器会对其进行解析生成对应的 ServletRequest 并且传给容器进行处理。
    • 容器处理完毕以后会返回连接器 ServletResponse,同理连接器会将 ServletResponse 解析成 Response 并且返回给浏览器。

6.2.2 Netty

  • todo

7 负载均衡架构

7.1 负载均衡典型架构

  • 地理级别负载均衡:www.xxx.com部署在北京、广州、上海三个机房,当用户访问时,DNS会根据用户的地理位置来决定返回哪个机房的IP,图中返回了广州机房的IP地址,这样用户就访问到广州机房了。
  • 集群级别负载均衡:广州机房的负载均衡用的是F5设备,F5收到用户请求后,进行集群级别的负载均衡,将用户请求发给3个本地集群中的一个,我们假设F5将用户请求发给了“广州集群2”。
  • 机器级别的负载均衡:广州集群2的负载均衡用的是Nginx,Nginx收到用户请求后,将用户请求发送给集群里面的某台服务器,服务器处理用户的业务请求并返回业务响应。

7.2 负载均衡分类

7.2.1 DNS负载均衡

  • DNS是最简单也是最常见的负载均衡方式,一般用来实现地理级别的均衡。例如,北方的用户访问北京的机房,南方的用户访问深圳的机房。DNS负载均衡的本质是DNS解析同一个域名可以返回不同的IP地址。
  • DNS负载均衡实现简单、成本低,但也存在粒度太粗、负载均衡算法少等缺点。

7.2.2 硬件负载均衡

  • 硬件负载均衡是通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似,可以理解为一个用于负载均衡的基础网络设备。
  • 目前业界典型的硬件负载均衡设备有两款:F5和A10。这类设备性能强劲、功能强大,但价格都不便宜,一般只有“土豪”公司才会考虑使用此类设备。普通业务量级的公司一是负担不起,二是业务量没那么大,用这些设备也是浪费。

7.2.3 软件负载均衡

  • 软件负载均衡通过负载均衡软件来实现负载均衡功能,常见的有Nginx和LVS,其中Nginx是软件的7层负载均衡,LVS是Linux内核的4层负载均衡。4层和7层的区别就在于协议和灵活性,Nginx支持HTTP、E-mail协议;而LVS是4层负载均衡,和协议无关,几乎所有应用都可以做,例如,聊天、数据库等。
  • 软件和硬件的最主要区别就在于性能,硬件负载均衡性能远远高于软件负载均衡性能。Ngxin的性能是万级,一般的Linux服务器上装一个Nginx大概能到5万/秒;LVS的性能是十万级,据说可达到80万/秒;而F5性能是百万级,从200万/秒到800万/秒都有(数据来源网络,仅供参考,如需采用请根据实际业务场景进行性能测试)。
  • 软件负载均衡的最大优势是便宜,一台普通的Linux服务器批发价大概就是1万元左右,相比F5的价格,那就是自行车和宝马的区别了。

7.3 负载均衡算法

  • 轮询:负载均衡系统收到请求后,按照顺序轮流分配到服务器上。
  • 加权轮询:负载均衡系统根据服务器权重进行任务分配,这里的权重一般是根据硬件配置进行静态配置的,采用动态的方式计算会更加契合业务,但复杂度也会更高。
  • 负载最低优先:负载均衡系统将任务分配给当前负载最低的服务器,这里的负载根据不同的任务类型和业务场景,可以用不同的指标来衡量。例如:
    • LVS这种4层网络负载均衡设备,可以以“连接数”来判断服务器的状态,服务器连接数越大,表明服务器压力越大。
    • Nginx这种7层网络负载系统,可以以“HTTP请求数”来判断服务器状态(Nginx内置的负载均衡算法不支持这种方式,需要进行扩展)。
    • 如果我们自己开发负载均衡系统,可以根据业务特点来选择指标衡量系统压力。如果是CPU密集型,可以以“CPU负载”来衡量系统压力;如果是I/O密集型,可以以“I/O负载”来衡量系统压力。
  • 性能最优类:负载最低优先类算法是站在服务器的角度来进行分配的,而性能最优优先类算法则是站在客户端的角度来进行分配的,优先将任务分配给处理速度最快的服务器,通过这种方式达到最快响应客户端的目的。
  • Hash类:负载均衡系统根据任务中的某些关键信息进行Hash运算,将相同Hash值的请求分配到同一台服务器上,这样做的目的主要是为了满足特定的业务需求。

8 SpringCloud 微服务

8.1 系统架构


8.2 SpringCloud 主要组件

8.2.1 Eureka

  • Netflix Eureka 是由 Netflix 开源的一款基于 REST 的服务发现组件,包括 Eureka Server 及 Eureka Client。

8.2.2 Ribbon

  • Ribbon Netflix 公司开源的一个负载均衡的组件。

8.2.3 Feign

  • Feign是是一个声明式的Web Service客户端。

8.2.4 Hystrix

  • Hystrix是Netstflix 公司开源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。

8.2.5 Zuul

  • Zuul 是由 Netflix 孵化的一个致力于“网关 “解决方案的开源组件。

8.2.6 Gateway

  • Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0、 Spring Boot 2.0 和 Project Reactor 等技术开发的网关, Spring Cloud Gateway 旨在为微服务架构提供简单、 有效且统一的 API 路由管理方式。

8.2.7 Config

  • Spring Cloud 中提供了分布式配置中 Spring Cloud Config ,为外部配置提供了客户端和服务器端的支持。

8.2.8 Bus

  • 使用 Spring Cloud Bus, 可以非常容易地搭建起消息总线。

8.2.9 OAuth2

  • Sprin Cloud 构建的微服务系统中可以使用 Spring Cloud OAuth2 来保护微服务系统。

8.2.10 Sleuth

  • Spring Cloud Sleuth是Spring Cloud 个组件,它的主要功能是在分布式系统中提供服务链路追踪的解决方案。

9 高可用架构

9.1 系统拆分

  • 早前的系统都是单体系统,比如电商业务,会员、商品、订单、物流、营销等模块都堆积在一个系统。每到节假日搞个大促活动,系统扩容时,一扩全扩,一挂全挂。只要一个接口出了问题,整个系统都不可用。
  • 慢慢的就有了我们现在看到的微服务架构,将一个复杂的业务域按DDD的思想拆分成若干子系统,每个子系统负责专属的业务功能,做好垂直化建设,各个子系统之间做好边界隔离,降低风险蔓延。

9.2 解耦

  • 软件开发有个重要原则“高内聚、低耦合”。小到接口抽象、MVC 分层,大到 SOLID 原则、23种设计模式。核心都是降低不同模块间的耦合度,避免一处错误改动影响到整个系统。
  • 就以开闭原则为例,对扩展是开放的,对修改是关闭的。随着业务功能迭代,如何做到每次改动不对原来的旧代码产生影响。
  • Spring 框架给我们提供了一个很好的思路,里面有个重要设计 AOP ,全称(Aspect Oriented Programming),面向切面编程。核心就是采用动态代理技术,通过对字节码进行增强,在方法调用的时候进行拦截,以便于在方法调用前后,增加我们需要的额外处理逻辑。
  • 当然还有一个重要思路就是事件机制,通过发布订阅模式,新增的需求,只需要订阅对应的事件通知,针对性消费即可。不会对原来的代码侵入性修改,是不是会好很多。

9.3 异步

  • 同步指一个进程在执行请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去,效率会大大降低。
  • 如果是非实时响应的动作可以采用异步来完成,线程不需要一直等待,而是继续执行后面的逻辑。

9.4 重试

  • 重试主要是体现在远程的RPC调用,受 网络抖动、线程资源阻塞 等因素影响,请求无法及时响应。
  • 为了提升用户体验,调用方可以通过 重试 方式再次发送请求,尝试获取结果。比过:浏览器的 F5 刷新机制就是类似道理。
  • 重试通常跟幂等组合使用,如果一个接口支持了 幂等,那你就可以随便重试。
  • 关于的 幂等 的解决方案:
    • 插入前先执行查询操作,看是否存在,再决定是否插入;
    • 增加唯一索引;
    • 建防重表;
    • 引入状态机,比如付款后,订单状态调整为已付款,SQL更新记录前增加条件判断;
    • 增加分布式锁;
    • 采用 Token 机制,服务端增加 token 校验,只有第一次请求是合法的。

9.5 补偿

  • 数据有异常后,我们还可以采用补偿玩法,实现数据最终一致性。业务补偿根据处理的方向分为两部分:
    • 正向:多个操作构成一个分布式事务,如果部分成功、部分失败,我们会通过最大努力机制将失败的任务推进到成功状态;
    • 逆向:同上道理,我们也可以采用反向操作,将部分成功任务恢复到初始状态。
  • 补偿有很多的实现方式:
    • 本地建表方式,存储相关数据,然后通过定时任务扫描提取,并借助反射机制触发执行;
    • 也可以采用简单的消息中间件,构建业务消息体,由下游的的消费任务执行。如果失败,可以借助MQ的重试机制,多次重试。

9.6 备份

  • 任何服务器都有宕机的可能性,一旦存储了数据,带上状态,如果发生故障,数据丢失,后果是我们无法承受的。
  • 所以,容灾备份也就变成了互联网的基本能力。那如何备份,不同的框架有不用的玩法。我们以 Redis 为例:
  • Redis 借助 RDB 和 AOF 来实现两台服务器间的数据同步;
  • 一旦主节点挂了怎么办?这里引入哨兵机制。哨兵机制可以实现主从库的自动切换,有效解决了故障转移。整个过程分为三个阶段:监控、选主、通知。

9.7 多活策略

  • 在一些极端情况,如:机房断电、机房火灾、地震、山洪等不可抗力因素,所有的服务器都可能出现故障,无法对外提供服务,导致整体业务瘫痪。
  • 为了降低风险,保证服务的24小时可用性,我们会采用 多活策略。
  • 常见的多活方案有,同城双活、两地三中心、三地五中心、异地双活、异地多活。
  • 不同的方案技术要求、建设成本、运维成本也都不一样。多活的技术方案复杂,需要考虑的问题点也非常多。

9.8 隔离

  • 隔离属于物理层面的分割,将若干的系统低耦合设计,独立部署,从物理上隔开。
  • 每个子系统有自己独立的代码库,独立开发,独立发布。一旦出现故障,也不会相互干扰。当然如果不同子系统间有相互依赖,这种情况比较特殊,需要有默认值或者异常特殊处理,这属于业务层面解决方案。
  • 隔离使得系统间边界更加清晰,故障可以更加隔离开来,问题的发现与解决也更加快速,系统的可用性也更高。
  • 常见的隔离方式有:
    • 虚拟机与容器隔离
    • 生产者与消费者隔离
    • 微服务与中台架构隔离
    • 业务与子系统隔离

9.9 限流

  • 单机版限流:主要借助于本机内存来实现计数器,比如通过AtomicLong#incrementAndGet(),但是要注意之前不用的key定期做清理,释放内存。
    • 纯内存实现,无需和其他节点统计汇总,性能最高。但是优点也是缺点,无法做到全局统一化的限流。
  • 分布式限流:以集群为维度,可以方便的控制这个集群的请求限制,从而保护下游依赖的各种服务资源。
    • 常见的限流算法:计数器限流、滑动窗口限流、漏桶限流、令牌桶限流。

9.10 熔断

  • 熔断,其实是对调用链路中某个资源出现不稳定状态时(如:调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
  • 断路器有三种状态,关闭、打开、半打开。
    • 关闭(Closed)状态:在这个状态下,请求都会被转发给后端服务。同时会记录请求失败的次数,当请求失败次数在一段时间超过一定次数就会进入打开状态。
    • 打开(Open)状态:在这个状态下,熔断器会直接拒绝请求,返回错误,而不去调用后端服务。同时,会有一个定时器,时间到的时候会变成半打开状态。目的是假设服务会在一段时间内恢复正常。
    • 半打开(Half Open)状态:在这个状态下,熔断器会尝试把部分请求转发给后端服务,目的是为了探测后端服务是否恢复。如果请求失败会进入打开状态,成功情况下会进入关闭状态,同时重置计数。

9.11 降级

  • 降级是通过暂时关闭某些非核心服务或者组件从而保护核心系统的可用性。
  • 比如电商大促,业务在峰值时刻,系统抵挡不住全部的流量时,系统的负载、CPU 的使用率都超过了预警水位,可以对一些非核心的功能进行降级,降低系统压力,比如把商品评价、成交记录等功能临时关掉。弃车保帅,保证 创建订单、订单支付 等核心功能的正常使用。
  • 当然,不同业务、不同公司,处理方式也各不相同,需要结合实际场景,和业务方同学一块讨论,最后达成一个统一认可的降级方案。

9.12 其他保障方式

  • 监控管理
  • 业务运行数据报告
  • 服务器性能监控
  • 用户行为日志收集
  • 监控数据采集
  • 网站运行监控
  • 代码版本管理:灰度发布、自动化发布、分支开发、主干发布
  • 预发布验证
  • 自动化测试

10 安全架构

10.1 反攻击

  • 机器学习
  • 规则引擎
  • 过滤黑名单
  • 贝叶斯分类算法、过滤垃圾邮件

10.2 信息加密

  • 对称加密:todo
  • 非对称加密:todo
  • 单项散列加密:todo

10.3 安全攻击

  • CSRF攻击:伪造用户请求
  • SQL注入:恶意SQL命令
  • XSS攻击:恶意脚本url

12 常见Java开发框架

12.1 Spring

12.2 SpringBoot

13 JVM虚拟机原理

13.1 系统架构

13.2 原理篇

13.3 常见问题

13.1 JVM组成架构

  • 堆:一个JVM实例一个堆
  • 栈:一个线程一个栈
  • 方法区:类字节码,static常量和引用
  • 程序计数器:记录执行到哪一行的字节码指令
  • 类加载:todo
  • 字节码执行流程:todo

13.2 JVM垃圾回收

1.2.1 垃圾回收器算法

  • G1回收器
  • CMS并发回收器
  • 串行回收器
  • 并行回收器
    1.2.2 分代垃圾回收
    1.2.3 回收:标记清理、标记压缩、复制回收

14 Java基础篇

  • 什么是字节码、JVM、JRE、JDK ?*
    • 字节码:JVM 可以理解的代码就叫做字节码,即扩展名为 .class 的文件。它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。
    • JVM:Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
    • JRE:JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
    • JDK:JDK 是 Java Development Kit,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
    • 说明:通常情况下,如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。
  • Java 程序从源代码到运行一般分几步?
    • 步骤:源代码 -> 字节码(JVM类加载器加载) -> 机器码(OS系统执行)
    • 详细说明:jvm 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的,也就是所谓的热点代码,所以后面引进了 JIT 编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
  • Oracle JDK 和 OpenJDK 有什么区别?
    • OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是OpenJDK 的一个实现,并不是完全开源的;
    • Oracle JDK 版本将每三年发布一次,而 OpenJDK 版本每三个月发布一次;
    • Oracle JDK 比 OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择 Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用 OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题;
    • 在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能;
    • Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
    • Oracle JDK 根据二进制代码许可协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。
  • 谈谈对String、 StringBuilder、StringBuffer的理解
    • String 中的对象是不可变的,也就可以理解为常量,主要是为了线程安全,建议操作少量的数据的场景下使用。
    • 每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。
    • StringBuilder 和 StringBuffer 每次都会对对象本身进行操作,而不是生成新的对象并改变对象引用。
    • StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。建议单线程操作字符串缓冲区下操作大量数据的场景中使用。
    • StringBuffer线程安全,但是有些性能损失,建议多线程操作字符串缓冲区下操作大量数据的场景下使用。
  • 在 Java 中为什么要定义一个不做事且没有参数的构造方法?
    • Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行,所以需要在父类里加上一个不做事且没有参数的构造方法。
  • 接口和抽象类的区别是什么?
    • 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
    • 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法。
    • 接口中的实例变量默认是 final 类型的,而抽象类中则不一定。
    • 一个类可以实现多个接口,但最多只能实现一个抽象类。
    • 一个类实现接口的话要实现接口的所有方法,而抽象类不一定。
    • 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象。
  • == 与 equals有什么区别?
    • == : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型比较的是值,引用数据类型比较的是内存地址)
    • equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
      • 情况 1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
      • 情况 2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
  • 你重写过 hashcode 和 equals 么,为什么重写 equals时必须重写 hashCode 方法?
    • hashCode():hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
    • 过程解析:我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
      • 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
    • hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
  • 线程有哪些基本状态?
    • 参考如下截图:
  • 关于 final 关键字的一些总结
    • 对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
    • 当用 final 修饰一个类时,表明这个类不能被继承。final 类中的所有成员方法都会被隐式地指定为 final 方法。
    • 使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。
  • Java 中的异常处理
    • 在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable类。Throwable: 有两个重要的子类:Exception(异常) 和 Error(错误) ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。
      • Error(错误): 是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java 虚拟机运行错误(Virtual MachineError),当JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
      • Exception(异常): 是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 异常由 Java 虚拟机抛出。
  • 什么时候抛出异常,什么时候处理异常?
    • 在通用的方法里,不要try去捕获错误,而是直接抛出异常给调用层处理。
    • 用户访问界面处理掉所有可能的异常,并记录详细错误日志,然后返回友好的错误界面给用户,不要抛异常给用户,不友好。
    • 异常应当在下层方法中不符合逻辑、出现异常的时候抛出,在上层进行捕获。

15 数据结构和算法

15.1 数据结构

  • 数组:随机快速读写
  • 链表:零散的内存空间、增删数据效率较高
  • 队列:先进先出
  • 栈:后进先出
  • Hash表
  • 平衡二叉树:左右子树深度差不超过1
  • 二叉排序树:左子树小于等于根,右子树大于根
  • 红黑树:适合大量增删

15.2 常用算法

  • 排序算法
    • 冒泡排序:遍历若干次要排序的数列,每次遍历时,它都会从前往后依次的比较相邻两个数的大小;如果前者比后者大,则交换它们的位置。这样,一次遍历之后,最大的元素就在数列的末尾!采用相同的方法再次遍历时,第二大的元素就被排列在最大元素之前。重复此操作,直到整个数列都有序为止!
    • 快速排序:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
    • 直接插入排序:把n个待排序的元素看成为一个有序表和一个无序表。开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。
    • 希尔(Shell)排序(缩小增量排序):把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。
    • 选择排序:首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置;接着,再从剩余未排序的元素中继续寻找最小(or最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
    • 归并排序:利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
    • 基数排序:根据关键字中各位的值,通过对排序的N个元素进行若干趟“分配”与“收集”来实现排序的。
    • 堆排序:利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
  • 动态规划
  • 贪心算法
  • 递归算法
  • 穷举算法
  • todo

15.3 衡量维度

  • 时间复杂度:算法执行语句的次数
  • 空间复杂度:算法临时占用存储空间大小的量度

11 秒杀系统 todo

11.1 更轻量快速的服务器

11.2 海量数据透明垂直切分

11.3 秒杀器的预防

11.4 反向代理加速

11.5 下单页面优化

11.6 组件CDN

11.7 前端优化自动化

11.8 基于TT的阀门设计

11.9 静态化及页面优化

16 性能测试与性能优化

16.1 性能优化

  • 软件代码性能优化:缓存、异步、集群
  • 软件架构性能优化:todo
  • 基础组件性能优化
  • 虚拟机性能优化:todo
  • 操作系统性能优化:todo
  • 服务器与硬件性能优化:todo
  • 机房与网络性能优化:异地多活、专网与CDN建设

16.2 全链路压测

  • 流量构造
  • 数据隔离
  • 数据构造

16.3 性能测试方法

  • 稳定性测试
  • 压力测试
  • 负载测试
  • 性能测试

16.4 性能指标

  • 服务器性能指标:System Load、CPU使用率、内存使用率、磁盘IO、网络带宽等
  • 吞吐量:TPS、QPS
  • 并发数
  • 响应时间(rt)

17 大数据系统

17.1 大数据平台架构

17.2 数据传输层

1.2.1 Spoop数据传递
1.2.2 Flume日志收集
1.2.3 Kafka消息队列

17.3 数据存储层

1.3.1 HDFS文件存储

  • NameNode
    • 机制、流程
      • Client故障
      • NameNode故障
      • DataNode故障
      • 写流程
      • 读流程
      • 节点失效处理流程
      • 高可用机制
    • 作用
      • 文件源数据的操作
      • 名字空间及客户端对文件访问
      • 中心服务器
  • HDFS系统架构
    • Client
    • DataNodes
    • Secondary NameNode
    • NameNode
      1.3.2 HBase非关系型数据库

17.4 资源管理层

1.4.1 YARN资源管理

  • 工作流程
  • 架构
    • Application Master
    • 容器
    • 节点管理器
    • 资源管理器
      1.4.2 Mesos资源管理

17.5 数据计算层

1.5.1 MapReduce离线计算

  • 处理流程
    • Record容错
    • TaskTracker进程
    • DataNode进程
    • 大数据应用程序服务器
    • JobTracker服务器
  • Reduce:输入、输出
  • Map:输入、输出
    1.5.2 Hive:脚本语法
  • HiveQL:Join in / shuffer sort
  • 执行流程
  • 架构
    • Compile:hive编译器,Hive SQL编成操作符
    • Driver
    • Client
      1.5.3 Spark Core内存计算
  • 执行过程
  • 计算阶段:根据DAD的依赖执行计算
  • RDD:弹性数据集
  • 特点优势:内存存储中间过程,更高效
    1.5.4 Flink实时计算
    1.5.5 Storm实时计算

17.6 任务调度层

1.6.1 Oozie任务调度
1.6.2 Azkaban任务调度
1.6.3 Firflow
1.6.4 Crontab

17.7 业务模型层

1.7.1 业务模型、数据可视化、业务应用
1.7.2 Kylin多维分析

17.8 大数据算法与机器学习

1.8.1 推荐引擎算法

  • 基于人口统计的推荐
  • 基于商品的协同过滤推荐
  • 基于用户的协同过滤推荐
  • 基于商品属性的推荐
    1.8.2 K-Means聚类算法
    1.8.3 贝叶斯算法:分类
    1.8.4 KNN算法:数据距离
    1.8.5 PageRank算法
    1.8.6 机器学习
  • 预测
  • 模型
  • 学习算法
  • 样本数据
    1.8.7 神经网络

相关文章:

  • LaTex使用技巧9:argmin / argmax下标写法
  • MySQL表的操作
  • 给定一个已排序的数组,使用就地算法将重复的数字移除,使数组中的每个元素只出现一次,返回新数组的长度
  • Vue2基础篇-01-Vue2 入门概述
  • CleanMyMac2023一键清除垃圾缓存和恶意广告插件 时刻保持Mac畅快运行
  • 通信原理 | 彻底搞懂卷积
  • 微服务框架 SpringCloud微服务架构 20 RestClient 操作索引库 20.5 删除和判断索引库
  • Vue3知识点之数据侦测
  • 【Python恶搞】Python实现祝福单身狗的恶搞项目,快@你的好朋友,祝福他吧 | 附源码
  • 共享车位|基于SpringBoot+vue+node共享车位平台的设计与实现
  • 【Android - 技术期刊】第004期
  • 一起用Go做一个小游戏(上)
  • HDU1074 Doing Homework(状压dp)
  • 前端面试题合集
  • Python均匀分布和三角形分布
  • IT行业面试技巧,90%的人都不知道
  • 数值处理--特征工程
  • java-net-php-python-springboot校园招聘系统计算机毕业设计程序
  • 火到爆的扩散模型(Diffusion Model)帮你具象化幻想世界
  • Nature子刊 | 空间转录组技术及其发展方向
  • 电加热油锅炉工作原理_电加热导油
  • 大型电蒸汽锅炉_工业电阻炉
  • 燃气蒸汽锅炉的分类_大连生物质蒸汽锅炉
  • 天津市维修锅炉_锅炉汽化处理方法
  • 蒸汽汽锅炉厂家_延安锅炉厂家
  • 山西热水锅炉厂家_酒店热水 锅炉
  • 蒸汽锅炉生产厂家_燃油蒸汽发生器
  • 燃煤锅炉烧热水_张家口 淘汰取缔燃煤锅炉
  • 生物质锅炉_炉
  • 锅炉天然气_天燃气热风炉