总结

Posted by 盈盈冲哥 on July 5, 2020

目录

大型网站技术架构

大型网站技术架构

  1. 分层:应用层、服务层、数据层

  2. 如何优化性能?

    • 缓存:CDN、反向代理、本地缓存、分布式缓存(一致性Hash算法)
    • 均衡负载:分布式(不同服务部署在不同机器上)、集群(多台机器提供相同的服务)
    • 异步:消息队列削峰、线程池
    • 数据库分库分表、读写分离
  3. 分布式缓存的一致性Hash算法

    一致性Hash算法通过一致性Hash环实现key到缓存服务器的Hash映射。

    具体算法过程为:先构造一个长度为$2^{32}$的整数环(一致性Hash环),根据节点名称的Hash值将缓存服务器节点放置在这个Hash环上。然后根据需要缓存的数据的key值计算得到其Hash值,在Hash环上顺时针查找距离这个key的Hash值最近的缓存服务器节点。

    当缓存服务器集群需要扩容时,只需要将新加入的节点名称的Hash值放入一致性Hash环中,由于key是顺时针查找其最近的节点,因此新加入的节点只影响整个环中的一小段。

  4. session管理

    • session复制:在集群中的几台服务器之间同步session对象,使得每台服务器上都保存所有用户的session信息。

    • session绑定:负载均衡服务器总是将来源于同一IP的请求分发到同一台服务器上。

    • 利用cookie记录session

    • session服务器

  5. 指标:吞吐量(TPS、QPS、HPS)、响应时间、并发数

  6. 安全

    • XSS攻击(跨站点脚本攻击):注入恶意HTML脚本

      手段:消毒、HTTPOnly

    • SQL注入攻击:攻击者在HTTP请求中注入恶意SQL命令

      手段:关闭错误回显、消毒、参数绑定

    • CSRF攻击(跨站点请求伪造):攻击者通过跨站请求,以合法的用户身份伪造请求进行非法操作

      手段:表单Token、验证码、Referer check

  7. 加密

    • 单项散列加密:MD5, SHA1

      虽然不能通过算法将单向散列密文反算得到密文,但是由于人们设置密码具有一定的模式,因此通过彩虹表(人们常用密码和对应的密文关系表)等手段可以进行猜测式破解。

      为了加强单项散列计算的安全性,还会给散列算法加点盐,盐相当于加密的密钥,增加破解难度。

    • 对称加密:DES、RC

      对称加密是指加密和解密使用的密钥是同一个密钥(或者可以互相推算)。

    • 非对称加密:RSA

      不同于对称加密,非对称加密和解密使用的密钥不是同一密钥,其中一个对外界公开,被称作公钥,另一个只有所有者知道,被称为私钥。用公钥加密的信息必须用私钥才能解开,反之,用私钥加密的信息只有用公钥才能解开。

  8. 高可用设计:服务降级、服务限流、超时设置、缓存、异步、幂等性设计

幂等

幂等

  1. 概念:一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

  2. 我们常用的HTTP协议的方法是具有幂等性语义要求的,比如:get方法用于获取资源,不应有副作用,因此是幂等的;post方法用于创建资源,每次请求都会产生新的资源,因此不具备幂等性;put方法用于更新资源,是幂等的;delete方法用于删除资源,也是幂等的。

  3. 幂等的应用场景:微服务框架超时重试、用户交互多次点击、MQ消息重复消费。

  4. 保证幂等的手段:通过悲观锁、乐观锁、唯一索引、Redis缓存来保证业务单号或者token的唯一性。

分布式

分布式

  1. 微服务

    完全拆分后各个服务可以采用异构的技术。比如数据分析服务可以使用数据仓库作为持久化层,以便于高效地做一些统计计算;商品服务和促销服务访问频率比较大,因此加入了缓存机制等。

    微服务架构还有一个技术外的好处,它使整个系统的分工更加明确,责任更加清晰,每个人专心负责为其他人提供更好的服务。

    微服务架构整个应用分散成多个服务,定位故障点非常困难。在微服务架构中,一个服务故障可能会产生雪崩效应,导致整个系统故障。

  2. SOA

    把系统按照实际业务,拆分成刚刚好大小的、合适的、独立部署的模块,每个模块之间相互独立。

    当服务越来越多,调用方也越来越多的时候,它们之间的关系就变得非常混乱,需要对这些关系进行管理。

    所以这个时候就需要能进行服务治理的框架,比如dubbo+zookeeper,比如SpringCloud,有了服务治理功能,我们就能清晰地看到服务被谁谁谁调用,谁谁谁调用了哪些服务,哪些服务是热点服务需要配置服务器集群,而对这个服务集群的负载均衡也是服务治理可以完成的重要功能之一。

    当然,还可以更进一步,加上服务监控跟踪等等等等之类的。

  3. 分布式

    什么是分布式?

    分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。

    为什么要分布式?

    从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。

    另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?

  4. Spring Cloud

    Spring Cloud抽象了一套通用的开发模式,依赖于RPC、网关、服务发现、配置管理、限流熔断、分布式链路跟踪的具体实现。

    • Eureka 服务注册发现框架:服务提供者、服务消费者、服务中介
    • Ribbon 进程内负载均衡器:和 Ribbon 不同的是,Nginx是一种集中式的负载均衡器。Nginx将所有请求都集中起来,然后再进行负载均衡。在 Nginx 中请求是先进入负载均衡器,而在 Ribbon 中是先在客户端(Consumer 端)进行负载均衡才进行请求的。常见策略有轮询、加权轮询、随机、Hash等。
    • Open Feign 服务调用映射
    • Hystrix 服务降级熔断器:所谓 熔断 就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 断路器 直接将此请求链路断开。降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。
    • Zuul 微服务网关:路由功能、过滤功能(限流、权限校验、灰度发布)。
    • Config 微服务统一配置中心
    • Bus 消息总线:管理和广播分布式系统中的消息。
  5. RPC

    • RPC解决的问题:让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。

    • RPC的原理

      img

      1. 服务消费方(client)调用以本地调用方式调用服务;
      2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
      3. client stub找到服务地址,并将消息发送到服务端;
      4. server stub收到消息后进行解码;
      5. server stub根据解码结果调用本地的服务;
      6. 本地服务执行并将结果返回给server stub;
      7. server stub将返回结果打包成消息并发送至消费方;
      8. client stub接收到消息,并进行解码;
      9. 服务消费方得到最终结果。
    • RPC和HTTP的区别

      RPC 只是一种概念、一种设计,就是为了解决 不同服务之间的调用问题, 它一般会包含有 传输协议序列化协议 这两个。

      但是,HTTP 是一种协议,RPC框架可以使用 HTTP协议作为传输协议或者直接使用TCP作为传输协议,使用不同的协议一般也是为了适应不同的场景。

      RPC框架功能更齐全:成熟的 RPC框架还提供好了“服务自动注册与发现”、”智能负载均衡”、“可视化的服务治理和运维”、“运行期流量调度”等等功能,这些也算是选择 RPC 进行服务注册和发现的一方面原因吧!

    • 常见的 RPC 框架:RMI(JDK自带)、Dubbo、gRPC、Hessian、Thrift

  6. Dubbo

    它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现

    框架由5种节点构成:

    • Provider: 暴露服务的服务提供方
    • Consumer: 调用远程服务的服务消费方
    • Registry: 服务注册与发现的注册中心
    • Monitor: 统计服务的调用次数和调用时间的监控中心
    • Container: 服务运行容器

    分为10层,每层解决一个关键问题

  7. Thrift

    Thrift的协议栈如下图所示:

    ![img](https://upload-images.jianshu.io/upload_images/6302559-75e0caadd951d14d.png?imageMogr2/auto-orient/strip imageView2/2/w/424/format/webp)
    • 底层IO模块,负责实际的数据传输
    • TTransport负责以字节流方式发送和接收Message
    • TProtocol主要负责结构化数据组装成Message,或者从Message结构中读出结构化数据。
    • TServer负责接收Client的请求,并将请求转发到Processor进行处理。

    利用Thrift用户只需要做三件事:

    (1). 利用IDL定义数据结构及服务 (2). 利用代码生成工具将(1)中的IDL编译成对应语言(如C++、JAVA),编译后得到基本的框架代码 (3). 在(2)中框架代码基础上完成完整代码(纯C++代码、JAVA代码等)

    Mtthrift

    Mtthrift继承自thrift,可以通过idl的形式描述服务的方法和参数结构,然后通过idl编译器生成thrift类。同时也支持了基于注解的描述方式,直接在javaBean上通过注解来描述服务的方法和参数。使用IDL的优点是一次编写,多种语言复用。使用注解的优点是代码即配置,方便管理服务描述。使用IDL时要注意选择对应版本的IDL编译器,否则可能出现兼容问题。

    • 命名服务、服务注册中心:MNS
    • 配置中心:MCC
    • 分布式链路追踪系统:Mtrace
    • 负载均衡:默认使用roundrobin策略,Octo平台配置服务权重,配置服务分组优先策略
    • 熔断降级:使用rhino组件配置熔断降级策略,保护你的服务
    • 监控报警:使用Raptor组件实现应用级别的主动监控和报警配置
    • 服务鉴权:在OCTO平台主动配置服务鉴权,确保服务安全
  8. protobuf

    为了实现RPC步骤,许多RPC工具被研发出来。这些RPC工具大多使用“接口描述语言” —— interface description language (IDL) 来提供跨平台跨语言的服务调用。现在生产中用的最多的IDL是Google开源的protobuf。

    protobuf为什么快?

    每个字段我们都用tag value的方式来存储的,在tag当中记录两种信息,一个是value对应的字段的编号,另一个是value的数据类型(比如是整形还是字符串等),因为tag中有字段编号信息,所以即使没有传递height字段的value值,根据编号也能正确的配对。

    protobuf当中定义了Varint这种数据类型,可以以不同的长度来存储整数,将数据进一步的进行了压缩。

    为了能够快速解析字符串类型的数据,protobuf在存储的时候,做了特殊的处理,分成了三部分:tag leg value,其中的leg记录了字符串的长度,同样使用了varint来存储,一般一个字节就能搞定,然后程序从leg后截取leg个字节的数据作为value,解析速度非常快。
  9. 分布式id

    • UUID:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。
    • 数据库自增 id:业务系统每次需要一个ID时,都需要请求数据库获取,性能低,并且如果此数据库实例下线了,那么将影响所有的业务系统。
    • 数据库多主模式:两台数据库分别设置不同起始值和相同步长,生成不重复ID的策略来实现高可用。问题是无法新增实例。
    • 利用redis生成id
    • 号端模式

      1
      2
      3
      4
      5
      6
      
      CREATE TABLE id_generator (
        id int(10) NOT NULL,
        current_max_id bigint(20) NOT NULL COMMENT '当前最大id',
        increment_step int(10) NOT NULL COMMENT '号段的长度',
        PRIMARY KEY (`id`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      1
      
      update id_generator set current_max_id=#{newMaxId}, version=version+1 where version = #{version}
      
    • twitter snowflake雪花算法:时间戳、工作机器id、序列号
    • 美团Leaf
  10. 限流

    • 固定窗口计数器算法:规定我们单位时间处理的请求数量。
    • 滑动窗口计数器算法:例如我们的接口限流每分钟处理60个请求,我们可以把 1 分钟分为60个窗口。每隔1秒移动一次, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
    • 漏桶算法:往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
    • 令牌桶算法:首先我们会有个桶,如果里面没有满那么就会以一定 固定的速率 会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。
  11. 秒杀、超卖

    • 秒杀活动的技术挑战

      • 对现有网站业务造成冲击
      • 高并发下的应用、数据库负载
      • 突然增加的网络及服务器带宽
      • 得到下单URL可以直接下单
    • 秒杀系统的应对策略

      • 秒杀系统独立部署
      • 秒杀商品页面静态化
      • 租借秒杀活动网络带宽
      • 动态生成随机下单页面URL
    • 秒杀系统架构设计

      • 如何控制秒杀商品页面购买按钮的点亮

        刷新秒杀商品页面从CDN服务器获取静态页面,在静态页面中加入一个JavaScript的文件引用。每次刷新秒杀商品页面都从JavaScript服务器集群加载一次JavaScript文件,该JavaScript文件中加入秒杀是否开始的标志和下单页面URL的随机数参数。秒杀开始前定时任务服务器生成新的JavaScript文件并推送到JavaScript服务器。

      • 如何只允许第一个提交的订单被发送到订单子系统

        为了减轻下单页面服务器的负载压力,可以控制进入下单页面的入口,只有少数用户能进入下单页面,其他用户直接进入秒杀结束页面。假设下单服务器集群有10台服务器,每台服务器只接受最多10个下单请求。活动开始用户点击购买按钮,请求发送至下单服务器,下单服务器检查本机已处理下单请求数目,如果已经超过10,直接显示秒杀活动已结束页面;如果未超过10,用户填写订单点击提交按钮,检查全局已提交订单数目,如果已超过商品数目,显示秒杀活动已结束页面,否则提交到订单子系统。

    • 超卖

      • 单点用线程锁,集群用分布式锁
      • 库存放在Redis里,秒杀结束再异步修改数据库中的库存
      • 请求放到Redis队列或者放到消息队列中,再利用一个线程去执行秒杀任务

Zookeeper

Zookeeper

  1. CAP原理

    • 数据一致性:数据强一致、数据用户一致、数据最终一致
    • 数据可用性
    • 分区耐受性(系统具有跨网络分区的伸缩性)
  2. BASE理论

    • 基本可用
    • 软状态
    • 最终一致性
  3. 2PC

    • 阶段一:提交事务请求(投票)

      协调者给所有参与者发送prepare请求。参与者收到prepare消息后,会开始执行事务但不提交,并将 Undo 和 Redo 信息记入事务日志中。之后参与者就向协调者反馈是否准备好了。

    • 阶段二:执行事务提交(执行)

      协调者根据参与者反馈的情况来决定接下来是否可以进行事务的提交操作,即提交事务或者回滚事务。

  4. 3PC

    三阶段提交将两阶段提交协议的提交事务请求(投票)过程一分为二,形成由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。

    • 阶段一:CanCommit

      协调者向所有参与者发送 CanCommit 请求,参与者收到请求后会根据自身情况查看是否能执行事务,如果可以则返回 YES 响应并进入预备状态,否则返回 NO 。

    • 阶段二:PreCommit

      如果第一阶段参与者返回的都是YES,那么协调者将向所有参与者发送 PreCommit 预提交请求,参与者收到预提交请求后,会进行事务的执行操作,并将 Undo 和 Redo 信息写入事务日志中,最后如果参与者顺利执行了事务则给协调者返回成功的响应。

      如果在第一阶段协调者收到了 任何一个 NO 的信息,它会向所有参与者发送中断请求(abort)。

    • 阶段三:doCommit

      这个阶段和 2PC 的第二阶段差不多。协调者根据参与者反馈的情况来决定接下来是否可以进行事务的提交操作,即提交事务或者回滚事务。

  5. Paxos算法:解决分布式一致性的算法

    • prepare阶段:提案者将具有全局唯一性的递增的编号N发送给表决者。表决者同意大于本地编号maxN(批准过的最大提案编号)的提案。
    • accept阶段:提案者收到半数以上表决者的批准,就会发送提案和编号。表决者再次比较,同意大于等于批准过的最大提案编号的提案。提案者收到半数以上同意,向所有表决者发送提案提交编号。
  6. ZAB协议

    ZAB 中的三个角色

    Leader 领导者:集群中 唯一的写请求处理者 ,能够发起投票(投票也是为了进行写请求)。

    Follower 跟随者:能够接收客户端的请求,如果是读请求则可以自己处理,如果是写请求则要转发给 Leader 。在选举过程中会参与投票,有选举权和被选举权 。

    Observer 观察者:就是没有选举权和被选举权的 Follower 。

    协议内容

    消息广播模式:Zab 协议中,所有的写请求都由 leader 来处理。正常工作状态下,leader 接收请求并通过广播协议来处理。

    1. Leader 接收到消息请求后,将消息赋予一个全局唯一的 64 位自增 id,叫做:zxid,通过 zxid 的大小比较即可实现因果有序这一特性。
    2. Leader 通过先进先出队列(通过 TCP 协议来实现,以此实现了全局有序这一特性)将带有 zxid 的消息作为一个提案(proposal)分发给所有 follower。
    3. 当 follower 接收到 proposal,先将 proposal 写到硬盘,写硬盘成功后再向 leader 回一个 ACK。
    4. 当 leader 接收到合法数量的 ACKs 后,leader 就向所有 follower 发送 COMMIT 命令,同时会在本地执行该消息。
    5. 当 follower 收到消息的 COMMIT 命令时,就会执行该消息

    崩溃恢复模式:当服务初次启动,或者 leader 节点挂了,系统就会进入恢复模式,直到选出了有合法数量 follower 的新 leader,然后新 leader 负责将整个系统同步到最新状态。

  7. Zookeeper

    ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。

    Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心(提供发布订阅服务)。 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。

    为什么最好使用奇数台服务器构成 ZooKeeper 集群?

    所谓的zookeeper容错是指,当宕掉几个zookeeper服务器之后,剩下的个数必须大于宕掉的个数的话整个zookeeper才依然可用。假如我们的集群中有n台zookeeper服务器,那么也就是剩下的服务数必须大于n/2。先说一下结论,2n和2n-1的容忍度是一样的,都是n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。 比如假如我们有3台,那么最大允许宕掉1台zookeeper服务器,如果我们有4台的的时候也同样只允许宕掉1台。 假如我们有5台,那么最大允许宕掉2台zookeeper服务器,如果我们有6台的的时候也同样只允许宕掉2台。

    概念

    • 会话(Session)

      Session 指的是 ZooKeeper 服务器与客户端会话。在 ZooKeeper 中,一个客户端连接是指客户端和服务器之间的一个 TCP 长连接。客户端启动的时候,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向Zookeeper服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的Watch事件通知。 Session的sessionTimeout值用来设置一个客户端会话的超时时间。当由于服务器压力太大、网络故障或是客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTimeout规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。

      在为客户端创建会话之前,服务端首先会为每个客户端都分配一个sessionID。由于 sessionID 是 Zookeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 sessionID 的,因此,无论是哪台服务器为客户端分配的 sessionID,都务必保证全局唯一。

    • Znode

      在谈到分布式的时候,我们通常说的“节点”是指组成集群的每一台机器。然而,在Zookeeper中,“节点”分为两类,第一类同样是指构成集群的机器,我们称之为机器节点;第二类则是指数据模型中的数据单元,我们称之为数据节点一一ZNode。

      Zookeeper将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个Znode,例如/foo/path1。每个上都会保存自己的数据内容,同时还会保存一系列属性信息。

      在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。 另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:SEQUENTIAL.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。

    • 版本

      在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 Stat 的数据结构,Stat 中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 aversion(当前ZNode的ACL版本)

    • Watcher

      Watcher(事件监听器),是Zookeeper中的一个很重要的特性。Zookeeper允许用户在指定节点上注册一些Watcher,并且在一些特定事件触发的时候,ZooKeeper服务端会将事件通知到感兴趣的客户端上去,该机制是Zookeeper实现分布式协调服务的重要特性。

    • ACL

      Zookeeper采用ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。Zookeeper 定义了5种权限。

    ZooKeeper 设计目标

    • 简单的数据模型:ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相互协调,这与标准文件系统类似。 名称空间由 ZooKeeper 中的数据寄存器组成 - 称为znode,这些类似于文件和目录。 与为存储设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以实现高吞吐量和低延迟。
    • 可构建集群:为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么zookeeper本身仍然是可用的。
    • 顺序访问:对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。 这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)
    • 高性能:ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。

    Zookeeper 应用场景

    • 选主

      还记得上面我们的所说的临时节点吗?因为 Zookeeper 的强一致性,能够很好地在保证 在高并发的情况下保证节点创建的全局唯一性 (即无法重复创建同样的节点)。

      利用这个特性,我们可以 让多个客户端创建一个指定的节点 ,创建成功的就是 master。

      但是,如果这个 master 挂了怎么办???

      你想想为什么我们要创建临时节点?还记得临时节点的生命周期吗?master 挂了是不是代表会话断了?会话断了是不是意味着这个节点没了?还记得 watcher 吗?我们是不是可以 让其他不是 master 的节点监听节点的状态 ,比如说我们监听这个临时节点的父节点,如果子节点个数变了就代表 master 挂了,这个时候我们 触发回调函数进行重新选举 ,或者我们直接监听节点的状态,我们可以通过节点是否已经失去连接来判断 master 是否挂了等等。

      总的来说,我们可以完全 利用 临时节点、节点状态 和 watcher 来实现选主的功能,临时节点主要用来选举,节点状态和watcher 可以用来判断 master 的活性和进行重新选举。

    • 分布式锁

      分布式锁的实现方式有很多种,比如 Redis 、数据库 、zookeeper 等。个人认为 zookeeper 在实现分布式锁这方面是非常非常简单的。

      上面我们已经提到过了 zk在高并发的情况下保证节点创建的全局唯一性,这玩意一看就知道能干啥了。实现互斥锁呗,又因为能在分布式的情况下,所以能实现分布式锁呗。

      如何实现呢?这玩意其实跟选主基本一样,我们也可以利用临时节点的创建来实现。

      首先肯定是如何获取锁,因为创建节点的唯一性,我们可以让多个客户端同时创建一个临时节点,创建成功的就说明获取到了锁 。然后没有获取到锁的客户端也像上面选主的非主节点创建一个 watcher 进行节点状态的监听,如果这个互斥锁被释放了(可能获取锁的客户端宕机了,或者那个客户端主动释放了锁)可以调用回调函数重新获得锁。

      zk 中不需要向 redis 那样考虑锁得不到释放的问题了,因为当客户端挂了,节点也挂了,锁也释放了。

      那能不能使用 zookeeper 同时实现 共享锁和独占锁 呢?答案是可以的,不过稍微有点复杂而已。

      还记得 有序的节点 吗?

      这个时候我规定所有创建节点必须有序,当你是读请求(要获取共享锁)的话,如果 没有比自己更小的节点,或比自己小的节点都是读请求 ,则可以获取到读锁,然后就可以开始读了。若比自己小的节点中有写请求 ,则当前客户端无法获取到读锁,只能等待前面的写请求完成。

      如果你是写请求(获取独占锁),若 没有比自己更小的节点 ,则表示当前客户端可以直接获取到写锁,对数据进行修改。若发现 有比自己更小的节点,无论是读操作还是写操作,当前客户端都无法获取到写锁 ,等待所有前面的操作完成。

      这就很好地同时实现了共享锁和独占锁,当然还有优化的地方,比如当一个锁得到释放它会通知所有等待的客户端从而造成 羊群效应 。此时你可以通过让等待的节点只监听他们前面的节点。

      具体怎么做呢?其实也很简单,你可以让 读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点。

    • 命名服务

      如何给一个对象设置ID,大家可能都会想到 UUID,但是 UUID 最大的问题就在于它太长了。。。(太长不一定是好事,嘿嘿嘿)。那么在条件允许的情况下,我们能不能使用 zookeeper 来实现呢?

      我们之前提到过 zookeeper 是通过 树形结构 来存储数据节点的,那也就是说,对于每个节点的 全路径,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的ID设置可以更加便于理解。

    • 集群管理和注册中心

      可能我们会有这样的需求,我们需要了解整个集群中有多少机器在工作,我们想对集群的每台机器的运行时状态进行数据采集,对集群中机器进行上下线操作等等。

      而 zookeeper 天然支持的 watcher 和 临时节点能很好的实现这些需求。我们可以为每条机器创建临时节点,并监控其父节点,如果子节点列表有变动(我们可能创建删除了临时节点),那么我们可以使用在其父节点绑定的 watcher 进行状态监控和回调。

      至于注册中心也很简单,我们同样也是让 服务提供者 在 zookeeper 中创建一个临时节点并且将自己的 ip、port、调用方式 写入节点,当 服务消费者 需要进行调用的时候会 通过注册中心找到相应的服务的地址列表(IP端口什么的) ,并缓存到本地(方便以后调用),当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从地址列表中取一个服务提供者的服务器调用服务。

      当服务提供者的某台服务器宕机或下线时,相应的地址会从服务提供者地址列表中移除。同时,注册中心会将新的服务地址列表发送给服务消费者的机器并缓存在消费者本机(当然你可以让消费者进行节点监听,我记得 Eureka 会先试错,然后再更新)。

  8. 分布式锁

    • 基于MySQL实现:对method_name字段做唯一性约束
    • 基于Redis实现
    • 基于zk实现

      Znode分为四种类型:

      1.持久节点 (PERSISTENT)

      默认的节点类型。创建节点的客户端与zookeeper断开连接后,该节点依旧存在 。

      2.持久节点顺序节点(PERSISTENT_SEQUENTIAL)

      所谓顺序节点,就是在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号。

      3.临时节点(EPHEMERAL)

      和持久节点相反,当创建节点的客户端与zookeeper断开连接后,临时节点会被删除。

      4.临时顺序节点(EPHEMERAL_SEQUENTIAL)

      顾名思义,临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与zookeeper断开连接后,临时节点会被删除。

      Zookeeper分布式锁的原理

      获取锁

      首先,在Zookeeper当中创建一个持久节点ParentLock。当第一个客户端想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点 Lock1。

      之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。

      这时候,如果再有一个客户端 Client2 前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock2。

      Client2查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。

      于是,Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。

      释放锁

      释放锁分为两种情况:

      1.任务完成,客户端显示释放

      当任务完成时,Client1会显示调用删除节点Lock1的指令。

      2.任务执行过程中,客户端崩溃

      获得锁的Client1在任务执行过程中,如果Duang的一声崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点Lock1会随之自动删除。

      由于Client2一直监听着Lock1的存在状态,当Lock1节点被删除,Client2会立刻收到通知。这时候Client2会再次查询ParentLock下面的所有节点,确认自己创建的节点Lock2是不是目前最小的节点。如果是最小,则Client2顺理成章获得了锁。

Kafka

Kafka

  1. 异步、解耦、削峰

  2. 流程

    1: 第一步心跳请求,客户端启动以后,主动链接castle,请求需要往哪个集群上生产和消费

    2: 第二步心跳响应,castle返回客户端,“你往192.168.1.1” 这个broker上生产消息或拉取消息。

    3: 第三步,客户端链接Broker,生产消息到broker上,或从broker上拉取消息消费。

    4: 以后第一步和第二步会一直重复,这就是所谓的“Mafka心跳”,客户端会始终和Castle保持一个“请求/响应”循环,目的是为了接收服务端的调度和控制指令。

  3. 名词

    Broker: 存储实际消息的地方,一台服务器,多个服务器(broker)组成一个集群。

    Castle: 中控调度,调度客户端从哪个机器上拉取消息,或把消息生产到哪台机器上去。

    ClientSDK: 业务使用的api,来生产或消费消息。

    Topic: 主题、队列

    生产者、上游

    消费者、下游,多台相同的消费者组成消费组

    Partition、主题分区、消息分片、分片:设想你发给Mafka 一万条消息,Mafka保存的时候,把它切成了4块,每块2500条消息,分别放到了四个不同的机器上。

    消息副本、replica、消息复制

    Topic的Ack属性:3个副本(一个主本,两个副本),如果Ack设为-1表示3个副本都接受到消息才算成功,如果是1表示主本收到消息就算成功,存在消息丢失风险(主本刚接收到消息返回给发送端成功,同步线程还未将消息复制给副本,此时主本机器宕机了,副本机器转换为主本,消息丢失)。同步线程会保证消息主本和副本复制时延在1秒内,所以Ack设置为1的极端情况下最大可能会丢失1秒内的消息。

    死信:某条消息无法消费成功,一直卡在这条消息处,无法消费后面的消息。解决方法:在topic管理页面里打开死信,处理消息如果失败会扔进死信队列,先消费后面的消息,过一段时间再消费。

  4. Kafka 的消息模型知道吗?

    发布-订阅模型:Kafka 消息模型

    发布订阅模型(Pub-Sub) 使用主题(Topic) 作为消息通信载体,类似于广播模式;发布者发布一条消息,该消息通过主题传递给所有的订阅者,在一条消息广播之后才订阅的用户则是收不到该条消息的。

  5. Kafka 的多副本机制了解吗?带来了什么好处?

    还有一点我觉得比较重要的是 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。

    生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。当 leader 副本发生故障时会从 follower 中选举出一个 leader,但是 follower 中如果有和 leader 同步程度达不到要求的参加不了 leader 的竞选。

    Kafka 的多分区(Partition)以及多副本(Replica)机制有什么好处呢?

    Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力(负载均衡)。

    Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性, 提高了容灾能力,不过也相应的增加了所需要的存储空间。

  6. Zookeeper 在 Kafka 中的作用知道吗?

    ZooKeeper 主要为 Kafka 提供元数据的管理的功能。

    从图中我们可以看出,Zookeeper 主要为 Kafka 做了下面这些事情:

    1. Broker 注册 :在 Zookeeper 上会有一个专门用来进行 Broker 服务器列表记录的节点。每个 Broker 在启动时,都会到 Zookeeper 上进行注册,即到/brokers/ids 下创建属于自己的节点。每个 Broker 就会将自己的 IP 地址和端口等信息记录到该节点中去
    2. Topic 注册 : 在 Kafka 中,同一个Topic 的消息会被分成多个分区并将其分布在多个 Broker 上,这些分区信息及与 Broker 的对应关系也都是由 Zookeeper 在维护。比如我创建了一个名字为 my-topic 的主题并且它有两个分区,对应到 zookeeper 中会创建这些文件夹:/brokers/topics/my-topic/Partitions/0、/brokers/topics/my-topic/Partitions/1
    3. 负载均衡 :上面也说过了 Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力。 对于同一个 Topic 的不同 Partition,Kafka 会尽力将这些 Partition 分布到不同的 Broker 服务器上。当生产者产生消息后也会尽量投递到不同 Broker 的 Partition 里面。当 Consumer 消费的时候,Zookeeper 可以根据当前的 Partition 数量以及 Consumer 数量来实现动态负载均衡。
    4. ……
  7. Kafka 如何保证消息的消费顺序?

    我们在使用消息队列的过程中经常有业务场景需要严格保证消息的消费顺序,比如我们同时发了 2 个消息,这 2 个消息对应的操作分别对应的数据库操作是:更改用户会员等级、根据会员等级计算订单价格。假如这两条消息的消费顺序不一样造成的最终结果就会截然不同。

    我们知道 Kafka 中 Partition(分区)是真正保存消息的地方,我们发送的消息都被放在了这里。而我们的 Partition(分区) 又存在于 Topic(主题) 这个概念中,并且我们可以给特定 Topic 指定多个 Partition。

    每次添加消息到 Partition(分区) 的时候都会采用尾加法,如上图所示。Kafka 只能为我们保证 Partition(分区) 中的消息有序,而不能保证 Topic(主题) 中的 Partition(分区) 的有序。

    所以,我们就有一种很简单的保证消息消费顺序的方法:1 个 Topic 只对应一个 Partition。这样当然可以解决问题,但是破坏了 Kafka 的设计初衷。

    Kafka 中发送 1 条消息的时候,可以指定 topic, partition, key,data(数据) 4 个参数。如果你发送消息的时候指定了 Partition 的话,所有消息都会被发送到指定的 Partition。并且,同一个 key 的消息可以保证只发送到同一个 partition,这个我们可以采用表/对象的 id 来作为 key 。

    总结一下,对于如何保证 Kafka 中消息消费的顺序,有了下面两种方法:

    1. 1 个 Topic 只对应一个 Partition。
    2. (推荐)发送消息的时候指定 key/Partition。
  8. Kafka 如何保证消息不丢失

    生产者丢失消息的情况

    生产者(Producer) 调用send方法发送消息之后,消息可能因为网络问题并没有发送过去。

    所以,我们不能默认在调用send方法发送消息之后判断消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。

    消费者丢失消息的情况

    我们知道消息在被追加到 Partition(分区)的时候都会分配一个特定的偏移量(offset)。偏移量(offset)表示 Consumer 当前消费到的 Partition(分区)的所在的位置。Kafka 通过偏移量(offset)可以保证消息在分区内的顺序性。

    当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。

    解决办法也比较粗暴,我们手动关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己手动提交 offset 。 但是,细心的朋友一定会发现,这样会带来消息被重新消费的问题。比如你刚刚消费完消息之后,还没提交 offset,结果自己挂掉了,那么这个消息理论上就会被消费两次。

  9. kafka是如何保证消息不被重复消费的

    kafka有个offset的概念,当每个消息被写进去后,都有一个offset,代表他的序号,然后consumer消费该数据之后,隔一段时间,会把自己消费过的消息的offset提交一下,代表我已经消费过了。下次我要是重启,就会继续从上次消费到的offset来继续消费。

 但是当我们直接kill进程了,再重启。这会导致consumer有些消息处理了,但是没来得及提交offset。等重启之后,少数消息就会再次消费一次。

1
通过保证消息队列消费的幂等性来保证消息不被重复消费。

ElasticSearch

ElasticSearch

  1. ES和MySQL的区别

    • 索引-数据库,类型-表,文档-行,字段-列
    • 倒排索引(单词->文档)-B+树索引
    • 分布式搜索[一个索引包含多个分片,分片包括主分片(读写)和副本分片(读);选举主节点]-遍历式搜索
    • 没有用户权限控制,没有事务,不支持回滚
  2. 倒排索引的原理:FST(有限状态转录机)数据结构,利用前缀后缀压缩空间,O(len(str))的查询复杂度

  3. 主节点选举:查找活跃的主节点,选择id最小的主节点。

  4. 写入:客户端请求协调节点,协调节点将请求转发主节点,主节点对文档路由将文档写入主分片,同步到副本分片。

    文档写入主分片的过程是:写入缓冲,同时将命令记录到transaction log,每隔1s缓冲将数据刷新进OS Cache。刷新操作后倒排索引才会建立,因此ES是近实时的。

  5. 查询:客户端请求协调节点,协调节点根据id路由到相应分片,随机选择主分片或副本分片。

  6. 搜索:query and fetch, query then fetch (默认,解决数量问题), dfs query and fetch (解决排名问题), dfs query then fetch

    客户端请求协调节点,协调节点转发给所有分片,每个分片将搜索结果(id)返回协调节点,协调节点进行合并、排序、分页,根据最终结果的id去各分片上拉取文档。

  7. 如何优化

    • 数据建模:只写入MySQL表中会被搜索的字段,禁止在ES中处理关联关系,可以直接写入或在应用端处理。
    • OS Cache:使数据量等于OS Cache。或者做一个缓存预热子系统,定时搜索热数据进入OS Cache。或者一个索引放热数据,一个索引放冷数据(冷热分离)。用滚动翻页(search after)来代替深度分页(from/size),防止出现深度分页的情况。
  8. 节点

    • Master Node

      • 处理创建、删除索引请求 / 决定分⽚分配到哪个节点
      • 维护并更新Cluster State
    • Master Eligible Node

      • 在Master节点出现故障时,参与选主流程,成为Master Node
    • Data Node

      • 保存分⽚数据,通过增加Data Node,可以解决数据⽔平扩展
      • 和数据单点的问题
    • Coordinating Node

      • 负责将请求转发到正确的节点
      • 处理query AND fetch的结果集
  9. 分⽚—分布式存储的基⽯

    • Primary Shard

      • 主分⽚在索引创建时指定,默认不能修改
    • Replica Shard

      • 数量可以动态调整,提⾼数据的可⽤性
      • 提升查询性能
      • 副本数量过多,会降低写⼊性能,增加磁盘负担
  10. 倒排索引&正排索引

    倒排索引可以实现从term到document的快速查询,但是没法实现排序,这个时候就需要⽤到正排索引。es是通过doc_value和field_data来实现正排索引的。

    doc_value是在lucene构建倒排索引时,根据原始mapping配置判断是否额外建⽴⼀个有序的正排索引(基于documentId—>field value的映射列表)

    doc_value默认对所有字段启⽤,除了analyzed string,针对analyzed string,如果需要排序,需要开启field_data。

    doc_value本质上是⼀个序列化的列式存储,这个结构⾮常适⽤于聚合、排序、脚本等操作,也⾮常便于压缩。和倒排索引⼀样,doc_value也会序列化到磁盘,这样对性能和扩展性有很⼤帮助。

  11. indexing的过程

    1. 将document写⼊index buffer,同时记录trans_log;
    2. 将index buffer写⼊segment,但不执⾏fsync,也不清理trans_log;
      • 这个过程叫做refresh,默认1s⼀次;
      • Index buffer占满时,也会触发refresh;
    3. 调⽤fsync,将缓存中的segments写⼊磁盘,清空trans_log;
      • 这个动作叫做flush,默认30min⼀次;
      • trans_log满(默认512M),也会触发flush;
  12. 索引定义

    字段类型—>是否需要搜索—>是否需要分词—>是否需要聚合及排序

    字段类型:Text vs keyword

    • Text
      • ⽤于⽂本分析,⽂本会被Analyzer分词
      • 默认不⽀持聚合分析和排序,需要设置fielddata=true
    • Keyword
      • 适⽤于不需要分词的场景,精确匹配查询效率最⾼
      • ⽀持sorting和aggregations
  13. 关于分⻚

    es提供三种分⻚查询的机制

    • from / size(es默认限定10000个⽂档)
    • search after(不⽀持跳⻚)
    • scroll API(遍历期间,更新的数据不会拿到)

    使⽤场景及建议

    • 需要全部⽂档,例如导出数据,允许⼀定误差(⽐如导出期间新增的数据)——scroll
    • 分⻚
      • 数据量⼩,且只查询最新的数据,使⽤from / size
      • 深度分⻚,使⽤search after,禁⽤跳⻚
  14. 常⽤的优化设计建议

    • 基于时间序列拆分索引
    • 节点职责分离
      • eligible master node:使⽤低配的CPU,RAM和磁盘
      • data node:使⽤⾼配的CPU,RAM和磁盘
    • 写⼊性能优化
      • 客户端通过bulk API批量写
      • 服务端降低IO开销,如提⾼refresh interval
      • 降低CPU和存储开销,如减少不必要的索引 / 分词 / doc_value
      • 极致的性能追求(牺牲可靠性)
      • 临时将副本修改为0(针对初始化导⼊数据场景)
      • 修改trans_log配置(不每次落盘,60s fsync⼀次)
    • 定期做segment merge,segments过多,term_index会占⽤较⼤内存,影响集群性能

Redis

Redis

  1. Redis到底有多快:100000+的QPS

  2. Redis为什么这么快

    • 完全基于内存
    • 数据结构简单
    • 采用单线程
    • 多路I/O复用模型
  3. redis的缓存淘汰策略

    • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
    • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
    • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
    • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
    • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
    • no-enviction(驱逐):禁止驱逐数据

    4.0 版本后增加以下两种:

    • volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
    • allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
  4. redis如何持久化数据

    Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)。

  5. redis有哪几种数据结构

    • String:string 数据结构是简单的 key-value 类型,value其实不仅可以是String,也可以是数字。一般常用在需要计数的场景,例如微博数,粉丝数等。
    • Hash:hash 是一个 string 类型的 field 和 value 的映射表。可以用 hash 数据结构来存储用户信息,商品信息等等。
    • List:list 就是链表,Redis list 的应用场景非常多,比如微博的关注列表,粉丝列表,消息列表等功能。
    • Set:set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能,这个过程也就是求交集的过程。
    • Sorted Set:和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。
  6. redis集群

    • 主从复制

    • 哨兵模式

      Sentinel(哨兵)进程是用于监控redis集群中Master主服务器工作的状态,在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换,保证系统的高可用。

    • Redis官方 Cluster集群模式(服务端sharding)

      redis的服务器节点中的任何两个节点之间都是相互连通的。客户端可以与任何一个节点相连接,然后就可以访问集群中的任何一个节点。对其进行存取和其他操作。

    • Jedis sharding集群(客户端sharding)

      一致性Hash算法

    • 利用中间件代理

  7. 如何保证redis和DB中的数据一致性

    读数据:读缓存,读数据库,写缓存

    写数据:淘汰缓存,写数据库,(写缓存)

  8. 如何解决缓存穿透和缓存雪崩

    缓存雪崩

    什么是缓存雪崩?

    缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

    解决方法:

    • 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
    • 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
    • 事后:利用 redis 持久化机制保存的数据尽快恢复缓存

    缓存穿透

    什么是缓存穿透?

    缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。

    解决方法:布隆过滤器

  9. 用redis实现分布式锁

    在单节点上实现分布式锁

    加锁:当key不存在时设值,设置过期时间。

    释放锁:如果key对应的值就是设置的值,则删除。

    Redlock 算法

    起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作:

    1. 得到当前的时间,微秒单位
    2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间
    3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。
    4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间
    5. 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态
  10. 降低内存占用

    • 短结构:当列表、散列、有序集合的长度较短或者体积较小的时候,redis将会采用一种名为ziplist的紧凑存储方式来存储这些结构。
    • 分片结构:不管列表、散列、有序集合、集合,当超出限制的条件后,就会转换为更为典型的底层结构类型。因为随着紧凑结构的体积不断变大,操作这些结构的速度将会变得越来越慢。分片的本质就是基于简单的规则将数据划分为更小的部分,然后根据数据所属的部分来决定将数据发送到哪个位置上。
  11. Redis 和 Memcached 的区别和共同点

    共同点 :

    • 都是基于内存的数据库,一般都用来当做缓存使用。
    • 都有过期策略。
    • 两者的性能都非常高。

    区别 :

    • Redis 支持更丰富的数据类型(支持更复杂的应用场景)。
    • Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。
    • Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.
    • Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。 (Redis 6.0 引入了多线程 IO )
    • Memcached过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。

MySQL

高性能MySQL

MySQL

  1. 内连接、左外连接、右外连接、全连接

  2. MySQL 七大约束

    • not null 不允许为空
    • default 默认值
    • comment 列描述
    • zerofill 在数据前面自动填充0
    • primary key 主键不能为空,不能重复,一张表有且只能有一个主键,可以是复合主键
    • auto_increment 自增长
    • unique 唯一键允许为空,但是不能重复
  3. MyISAM和InnoDB区别

    • 是否支持行级锁
    • 是否支持事务和崩溃后的安全恢复
    • 是否支持外键
    • 是否支持MVCC
  4. 索引:B树索引和哈希索引

    • MyISAM:非聚簇索引,叶节点中取出data域的值,然后以data域的值为地址读取相应的数据记录。
    • InnoDB:聚簇索引的树的叶节点data域保存了完整的数据记录。在根据主键索引(聚簇索引)搜索时,直接找到key所在的节点即可取出数据;再根据辅助索引(二级索引)查找时,则需要先取出主键的值,再走一遍索引。
  5. 事务

    事务是逻辑上的一组操作,要么都执行,要么都不执行。

    事务的四大特性 (ACID)

    • 原子性:事务是最小的执行单位,不允许分割,事务中的动作要么全部完成,要么完全不起作用。利用undolog实现。
    • 一致性:事务是的系统从一个一致的状态转换到另一个一致的状态。例子是转账前后需要满足余额不能小于0的约束。建立在AID上,并且依赖于开发者。
    • 隔离性:一个事务不被其他事务干扰,各并发事务之间是独立的。包括4种隔离级别。利用锁和MVCC实现。
    • 持久性:一个事务被提交之后,它对数据库中数据的改变是持久的。利用redolog实现。
  6. 并发事务带来的问题

    • 脏读:一个事务读到另一个事务还未提交的数据。
    • 丢失数据:例如事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
    • 不可重复读:一个事务两次读同一个数据发现不一样。
    • 幻读:一个事务两次查询发现多了一些原本不存在的记录,好像出现了幻觉。
  7. MySQL的事务隔离级别

    • 读取未提交
    • 读取已提交(解决脏读)
    • 可重复读(解决不可重复读)
    • 可串行化(解决幻读)
  8. 不同事务隔离级别分别会加哪些锁

    • 读取已提交级别:读取不加锁,写入、修改、删除加行锁。
    • 可重复读级别:next-key锁=gap锁+行锁。行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。
    • 可串行化:读加共享锁,写加排他锁,读写互斥。并发能力非常差。
  9. MVCC

    数据库并发场景有三种,分别为:

    • 读-读:不存在任何问题,也不需要并发控制
    • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
    • 写-写:有线程安全问题,可能会存在更新丢失问题

    多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。

    • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。
    • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题。

    有了MVCC,这两个组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题

    • MVCC + 悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突
    • MVCC + 乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突

    版本链

    对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:

    • trx_id:每次对某条记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
    • roll_pointer:每次对某条记录进行改动时,这个隐藏列会存一个指针,可以通过这个指针找到该记录修改前的信息。

    ReadView

    对于使用READ UNCOMMITTED隔离级别的事务来说,直接读取记录的最新版本就好了,对于使用SERIALIZABLE隔离级别的事务来说,使用加锁的方式来访问记录。对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,就需要用到我们上边所说的版本链了,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。

    ReadView中主要包含4个比较重要的内容:

    1. m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
    2. min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
    3. max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
    4. creator_trx_id:表示生成该ReadView的事务的事务id。

    有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:

    1. 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
    2. 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
    3. 如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
    4. 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问
  10. 快照读、当前读

    快照读:select …

    快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

    当前读:select … lock in share mode (共享锁), select … for update (排他锁), update, insert, delete

    它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

  11. undolog, redolog, binlog

    • redo log 是重做日志,提供 前滚 操作。Redo Log 保证事务的持久性。Redo 记录某 数据块 被修改 后 的值,可以用来恢复未写入 data file 的已成功事务更新的数据。redo 解决的问题之一就是事务执行过程中的强制刷脏。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到最新的状态。

    • undo log 是回退日志,提供 回滚 操作。Undo Log 保证事务的原子性(在 InnoDB 引擎中,还用 Undo Log 来实现 MVCC)。Undo 记录某 数据 被修改 前 的值,可以用来在事务失败时进行 rollback.

    redo/undo log 和 binlog

    • 层次不同。redo/undo 是 innodb 引擎层维护的,而 binlog 是 mysql server 层维护的,跟采用何种引擎没有关系,记录的是所有引擎的更新操作的日志记录。
    • 记录内容不同。redo/undo 记录的是 每个页/每个数据 的修改情况,属于物理日志+逻辑日志结合的方式(redo log 是物理日志,undo log 是逻辑日志)。binlog 记录的都是事务操作内容,binlog 有三种模式:Statement(基于 SQL 语句的复制)、Row(基于行的复制) 以及 Mixed(混合模式)。不管采用的是什么模式,当然格式是二进制的。
    • 记录时机不同。redo/undo 在 事务执行过程中 会不断的写入,而 binlog 是在 事务最终提交前 写入的。binlog 什么时候刷新到磁盘跟参数 sync_binlog 相关。
    • 共享锁Shared Locks(S锁)

      兼容性:加了S锁的记录,允许其他事务再加S锁,不允许其他事务再加X锁

      加锁方式:select … lock in share mode

    • 排他锁Exclusive Locks (X锁)

      兼容性:加了X锁的记录,不允许其他事务再加S锁或者X锁

      加锁方式:select … for update

    • 表锁:意向锁 Intention Locks

      意向共享锁(IS锁):事务在请求S锁前,要先获得IS锁

      意向排他锁(IX锁):事务在请求X锁前,要先获得IX锁

      意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。

      例子:事务A修改user表的记录r,会给记录r上一把行级的排他锁(X),同时会给user表上一把意向排他锁(IX),这时事务B要给user表上一个表级的排他锁就会被阻塞。意向锁通过这种方式实现了行锁和表锁共存且满足事务隔离性的要求。

    • 行锁:记录锁 Record Locks

      record lock锁住的永远是索引,而非记录本身

    • 行锁:间隙锁 Gap Locks

      区间锁, 仅仅锁住一个索引区间(开区间,不包括双端端点)。

      间隙锁可用于防止幻读,保证索引间的不会被插入数据。

    • 行锁:临键锁(Next-Key Locks)

      record lock + gap lock, 左开右闭区间。

      默认情况下,innodb使用next-key locks来锁定记录。

      但当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。

  12. 联合索引

    • 最左前缀匹配:联合索引(a, b, c)相当于建立了(a), (a, b), (a, b, c)三个索引
    • 检索时,索引字段的顺序是任意的
    • 联合索引实际上是一棵B+树,只有最后一个索引字段可以是大于或者小于
  13. explain

    • id
    • select_type: simple, [primary, subquery], [primary, derived (在from子句里)], [primary, union, union result]
    • type: eq_ref, ref, range, index, all
    • extra: using index, using where, using temporary

    一般MySQL能够使用如下三种方式应用WHERE条件,从好到坏依次为:

    • 在索引中使用WHERE条件来过滤不匹配的记录。
    • 使用索引覆盖扫描(在Extra列中出现了Using index)来返回记录,直接从索引中过滤不需要的记录并返回命重的结果。
    • 从数据表中返回数据,然后过滤不满足条件的记录(在Extra列中出现Using Where)。
  14. 大表优化

    • 限定数据范围
    • 读写分离:主库负责写,从库负责读。
    • 垂直分区:数据表列的拆分,把一张列比较多的表拆分为多张表。
    • 水平分区:数据表行的拆分。分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以水平拆分最好分库 。
  15. 数据库连接池:在连接池中,创建连接后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。

  16. 覆盖索引

    • 什么是覆盖索引

      如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子结点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是要查询出的列和索引是对应的,不做回表操作。

    • 覆盖索引使用实例

      现在我创建了索引(username, age),在查询数据的时候:select username, age from user where username = 'Java' and age = 22,要查询出的列在叶子结点都存在,所以就不用回表。

  17. 索引优化

    • where, order by, group by, join加索引
    • 有null的列不要加索引
    • 联合索引:区分度高的索引放左边,长度短的索引放左边,运行频率高的索引放左边
    • 主键建议使用自增 ID 值,分库分表要生成全局 id
    • 对于频繁的查询考虑使用覆盖索引
  18. SQL优化

    • *避免使用
    • limit
    • 小表join大表
    • 连接好于子查询
    • explain
  19. 索引失效

    • 联合索引未用左列索引
    • like以%开头
    • 需要类型转换
    • where中索引列使用了函数
  20. MySQL索引为什么用B+树不用B树或者红黑树

    • B+树做索引而不用B-树:B+树所有的Data域在叶子节点,并且所有叶子节点之间都有一个链指针。这样遍历叶子节点就能获得全部数据,这样就能进行区间访问啦。
    • B+树做索引而不用红黑树:大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率低下的情况。
  21. 数据库主从一致性

    半同步复制

    (1)系统先对DB-master进行了一个写操作,写主库

    (2)等主从同步完成,写主库的请求才返回

    (3)读从库,读到最新的数据(如果读请求先完成,写请求后完成,读取到的是“当时”最新的数据)

    数据库中间件

    (1)所有的读写都走数据库中间件,通常情况下,写请求路由到主库,读请求路由到从库

    (2)记录所有路由到写库的key,在经验主从同步时间窗口内(假设是500ms),如果有读请求访问中间件,此时有可能从库还是旧数据,就把这个key上的读请求路由到主库

    (3)经验主从同步时间过完后,对应key的读请求继续路由到从库

    缓存记录写key法

    当写请求发生的时候:

    (1)将某个库上的某个key要发生写操作,记录在cache里,并设置“经验主从同步时间”的cache超时时间,例如500ms

    (2)修改数据库

    而读请求发生的时候:

    (1)先到cache里查看,对应库的对应key有没有相关数据

    (2)如果cache hit,有相关数据,说明这个key上刚发生过写操作,此时需要将请求路由到主库读最新的数据

    (3)如果cache miss,说明这个key上近期没有发生过写操作,此时将请求路由到从库,继续读写分离

  22. 如何设计数据库表

    数据库设计的初级⽅方法论:罗列实体全部属性->拆解属性->建⽴表关系

    E-R图:实体、关系

    一对一(外键)、一对多(多的一方加外键)、多对多(中间表,两列作为联合主键)

    三大范式

    • 第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项。

    • 第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)

      第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。

    • 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)

      第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

  23. MySQL三层体系结构

    1. 应用
    2. 连接层:通信协议处理理、线程处理理、账号认证、安全检查等
    3. SQL层:权限判断、查询解析、优化器&缓存、查询执⾏&返回
    4. 存储层:存储引擎、错误日志、二进制⽇日志、中继日志
  24. MySQL数据库常⽤用操作

    • DDL:数据定义语⾔言DDL(Data Definition Language)Create、 Alter
      • 创建数据库 CREATE DATABASE 数据库名;
      • 删除数据库 DROP DATABASE 数据库名;
      • 创建表 CREATE TABLE table_name (column_name column_type);
      • 新增列 ALTER TABLE testalter_tbl ADD i INT;
      • 新增索引 ALTER TABLE tableName ADD INDEX indexName(columnName)
    • DML:数据操纵语⾔言DML(Data Manipulation Language)Insert、 Update、 Delete
      • 插入数据 INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN );
      • 更新数据 UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]
      • 删除数据 DELETE FROM table_name [WHERE Clause]
    • DQL:数据查询语⾔言DQL(Data Query Language)Select
    • DCL:数据控制语⾔言DCL(Data Control Language)Grant、 Commit、 Rollback
  25. MySQL重要的数据类型

    • 数值类型
      • INT类型
        • TINYINT 1 byte
        • SMALLINT 2 byte
        • MEDIUMINT 3 byte
        • INT 4 byte
        • BIGINT 8 byte
      • 精度类型
        • FLOAT 4 byte
        • DOUBLE 8 byte
        • DECIMAL 变⻓
    • 字符串类型
      • CHAR(N) 定长字符
      • VARCHAR(N) 变长字符
      • TEXT 大对象
      • 字符集选择: UTF8-MB4
      • 字符排序规则:
        • utf8_general_ci 不区分大小写,这个你在注册用户名和邮箱的时候就要使用
        • utf8_general_cs 区分⼤大⼩小写,如果用户名和邮箱用这个就会照成不良后果,A和a不一样
    • 时间类型
      • 推荐使用: DATETIME
      • DATE:日期
      • TIMESTAMP:1970-2038年
    • JSON类型

网络

网络

  1. OSI七层模型:物理层、数据链路层、网络层、传输层、会话层(会话管理)、表示层(数据格式转换)、应用层

  2. ARP协议(网络层):IP地址->MAC地址

    每个主机都设有一个ARP高速缓存,先查ARP表,如果没有就通过使用目的MAC地址为FF-FF-FF-FF-FF-FF的帧来封装并广播ARP请求分组。

  3. 报文结构

    • IP首部字段:版本、首部长度、总长度、标识、标志、片偏移、首部检验和、生存时间TTL、协议、源地址字段目的地址字段
    • UDP的首部字段:源端口目的端口、长度、校验和
    • TCP的首部字段:源端口和目的端口字段序号字段 (seq)确认号字段 (ack)、数据偏移(即首部长度)、保留字段、紧急位URG、确认位ACK、推送位PSH、复位位RST、同步位SYN、终止位FIN、窗口字段(允许对方发送的数据量)、校验和、紧急指针字段、选项字段 (最大报文段长度MSS)、填充字段
  4. TCP和UDP

    TCP提供可靠的面向连接的服务,增加了开销,用于文件传输、发送和接收邮件、远程登录等场景。

    UDP不建立连接,不提供可靠服务,用于语音、视频。

  5. TCP为什么可靠一些

    TCP连接管理:三次握手、四次挥手

    TCP可靠传输:累计确认、超时和冗余ACK导致重传

    TCP流量控制:发送窗口的实际大小是接受窗口和拥塞窗口的最小值

    TCP拥塞控制:慢开始(指数规模增长)、拥塞避免(加法增大)、快恢复(乘法减小)

  6. 滑动窗口的作用

    • 滑动窗口实现面向流的可靠性,只有在收到ACK确认的情况下移动左边界

    • 滑动窗口的流控特性

  7. TCP连接和释放过程

    三次握手

    1. 客户机到服务器:SYN
    2. 服务器到客户机:SYN/ACK
    3. 客户机到服务器:ACK

    四次挥手

    1. 客户机到服务器:FIN
    2. 服务器到客户机:ACK

      CLOSE_WAIT

    3. 服务器到客户机:FIN/ACK

      TIME_WAIT

    4. 客户机到服务器:ACK
    • 为什么A还要发送一次确认呢?

      防止已经失效的连接请求报文段突然又传到了B,因而产生错误,浪费B的资源。

    • CLOSE_WAIT:半关闭状态,即A已经没有数据要发送了,但B若发送数据,A仍要接受。

    • TIME_WAIT:为什么A在TIME_WAIT状态必须等待2MSL(最长报文段寿命,建议为2min)?

      1. 为了保证A发送的最后一个ACK报文段能够到达B
      2. 防止已失效的连接请求报文段出现在本连接中
  8. DNS的寻址过程

    1. 浏览器缓存、DNS缓存
    2. hosts文件
    3. 本地域名服务器分别请求根域名服务器、顶级域名服务器、权限域名服务器

      递归查询(比较少用)、迭代查询

  9. 在浏览器输入url到显示主页的过程:DNS解析、TCP连接、发送HTTP请求、服务器解析渲染页面

  10. 状态码

    1XX:信息性状态码

    2XX:成功状态码

    3XX:重定向状态码

    4XX:客户端错误状态码

    5XX:服务端错误状态码

    • 200 OK:表示从客户端发来的请求在服务端被正常处理了。
    • 201 Created:请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回。
    • 202 Accepted:服务器已接受请求,但尚未处理。正如它可能被拒绝一样,最终该请求可能会也可能不会被执行。在异步操作的场合下,没有比发送这个状态码更方便的做法了。
    • 204 No Content:代表服务器接收的请求已成功处理,但在返回的响应报文中不含实体的主体部分。
    • 206 Partial Content:表示客户端进行了范围请求,而服务器成功执行了这部分的GET请求。

    • 301 Moved Permanently:永久性重定向。表示请求的资源已被分配了新的URL,以后应使用资源现在所指的URL。
    • 302 Found:临时性重定向。表示请求的资源已被分配了新的URL,希望用户能使用新的URL访问。和301状态码相似,但302状态码代表资源不是被永久移动,只是临时性质的。
    • 304 Not Modified:表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304.

    • 400 Bad Request:表示请求报文中存在语法错误。
    • 401 Unauthorized:表示发送的请求需要有通过HTTP认证的认证信息。
    • 403 Forbidden:对请求资源的访问被服务器拒绝了。
    • 404 Not Found:表明服务器上无法找到请求的资源。

    • 500 Internal Server Error:表示服务器端在执行请求时发生了错误。
    • 502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
    • 503 Service Unavailable:表明服务器暂时处于超负荷或正在进行停机维护,现在无法处理请求。
  11. HTTP/1.1默认使用长连接,在响应头加入Connection: keep-alive。在使用长连接的情况下,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭。

  12. HTTPS过程

    客户端向服务端发送HTTPS请求,服务端将自己的公钥发送给客户端,客户端利用公钥加密密钥,发起第二个HTTPS请求,将加密之后的密钥发送给服务端,服务端用私钥解密密钥,将加密后的密文发送给客户端,客户端利用密钥解密。

  13. HTTP 2.0

    多个请求可同时在一个连接上并行执行。

  14. HTTPS与HTTP的区别

    • 传输信息安全性不同
    • 连接方式不同:HTTPS由SSL+HTTP协议构建
    • 端口不同:HTTP 80, HTTPS 443
    • 证书申请方式不同:HTTPS需要到CA申请证书
  15. 请求报文的结构

    • 请求行:请求方法GET/POST、URL、协议版本HTTP1.0/HTTP1.1
    • 请求首部:图解HTTP P80
    • 请求主体

    通用首部字段

    • Cache-Control:控制缓存的行为
    • Connection:逐跳首部、连接的管理

    请求首部字段

    • Accept:用户代理可处理的媒体类型
    • Accept-Charset:优先的字符集
    • Accept-Encoding:优先的内容编码
    • Accept-Language:优先的语言(自然语言)
    • Host:请求资源所在服务器
    • Range:实体的字节范围请求
    • Referer:对请求中URL的原始获取方
    • User-Agent:HTTP客户端程序的信息

    响应首部字段

    实体首部字段

    • Content-Encoding:实体主体适用的编码方式
    • Content-Language:实体主体的自然语言
    • Content-Length:实体主体的大小
    • Content-Type:实体主体的媒体类型

    非HTTP/1.1首部字段

    • Cookie
    • Set-Cookie
  16. GET请求和POST请求

    • GET请求参数在URL中,POST请求参数在请求主体中
    • GET请求具有幂等性,多次调用和一次调用是一样的,没有副作用

设计模式

大话设计模式

设计模式

  1. 面对对象

    • 封装:对象包含它能操作所需的所有信息,包括变量和方法,好处是减少耦合,内部可以自由修改,具有清晰的对外接口。
    • 继承:is-a的关系,父类变子类不得不变,强耦合。
    • 多态:子类以父类的身份出现,调用父类的方法,使用的是子类的实现。
  2. 原则

    • 单一职责原则:就一个类而言,应该仅有一个引起它变化的原因。
    • 开放封闭原则:软件实体对于扩展是开放的,对于更改是封闭的。
    • 依赖倒转原则:A.高层模块不应该依赖低层模块,两个都应该依赖抽象。B. 抽象不应该依赖细节,细节应该依赖抽象。
    • 里氏替换原则:子类型必须能够替换掉它们的父类型。
    • 合成/聚合复用原则:尽量使用合成/聚合,尽量不要使用继承。
  3. 设计模式

    • 简单工厂模式
    • 工厂方法模式:产品接口、工厂接口、具体产品、具体工厂,一个具体工厂生产一个具体产品。
    • 抽象工厂模式:一个具体工厂创建一组具体产品。
    • 策略模式:上下文类(维护一个对策略的引用),策略类,具体策略类
    • 代理模式:主体类(定义真是主体类和代理类的共同接口),真实主体类,代理类(保存一个引用是的代理可以访问真实主体)
    • 观察者模式(发布-订阅模式):主体类(可以增加和删除观察者对象)、观察者(在得到主体的通知时更新自己)、具体主体、具体观察者
    • 适配器模式:将一个类的接口转换成客户希望的另一个接口
    • 桥接模式:实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。
    • 单例模式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      public class Singleton {
          private volatile static Sinleton uniqueInstance;
          private Singleton() {}
          public static SIngleton getInstance() {
              if (uniqueInstance == null) {
                  synchronized (Singleton.class) {
                      if (uniqueInstance == null) {
                          uniqueInstance = new Singleton();
                      }
                  }
              }
          }
      }
      
  4. 领域驱动设计(DDD)的六边形架构

    六边形每条不同的边代表了不同类型的端口,通过“端口”跟外部进行交互。通过适配器调用应用程序和领域模型。

    领域模型中的实体类可细分为4种类型:VO、DTO、DO、PO。

    • VO(View Object):视图对象,用于展示层视图状态对应的对象
    • DTO(Data Transfer Object):数据传输对象,原来的目的是为EJB的分布式应用提供粗粒度的数据实体,以降低分布式调用的次数,提高分布式调用的性能,后来一般泛指用于展示层与服务层之间的数据传输对象,因此可以将DTO看成一个组合版的DO
    • DO(Domain Object):领域对象,即业务实体对象
    • PO(Persistent Object):持久化对象,表示持久层的数据结构(如数据库表)

Java

Java

  1. 简单类型8种:short, int, long, float, double, boolean, byte, char

  2. NIO:非阻塞IO,调用者不用一直等着结果返回。线程控制选择器,选择不同的通道来读取缓存区。

  3. Linux的5中IO模型:阻塞、非阻塞、异步、IO复用、信号驱动IO

  4. select, poll, epoll的区别

    • select需要把文件描述符(fd)集合从用户态拷贝到内核态,并在内核态遍历文件描述符。
    • poll与select类似,文件描述符集合的描述结构不同。
    • epoll会在注册时把所有文件描述符拷贝进内核,每个文件描述符只会拷贝一次,挂到等待队列上。
  5. Object类方法:hashCode(), equals(), notify(), wait(), toString(), clone(), getClass(), finalize()

  6. 接口和抽象类的区别:方法在接口中不能有实现;一个类可以实现多个接口,但只能继承一个抽象类。

  7. String, StringBuffer, StringBuilder

    • String:不可变
    • StringBuffer:可变,线程安全synchronized
    • StringBuilder:可变,线程不安全

    String为什么不可变?

    • 用不可变类型做HashMap和HashSet键值。
    • 不可变对象不能被写,所以线程安全。
    • String one = "someString"; 使用字符串常量池,在大量使用字符串的情况下,可以节省内存空间,提高效率。
  8. final

    • 修饰变量:值在初始化后不能更改
    • 修饰类:类不能被继承,所有成员方法都会被隐式指定为final
    • 修饰方法:方法不能被重写
  9. static

    • 修饰成员变量和方法
    • 静态代码块
    • 静态内部类(不能引用外部非static成员变量和方法)
  10. 通配符?用于实例化泛型对象,T用于定义泛型类

  11. 类型擦除:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉

  12. List, Set, Map

    • List: ArrayList, Vector, LinkedList
    • Set, Map: HashMap, HashTable, LinkedHashMap, TreeMap
  13. HashMap

    • hash = h ^ (h »> 16),高16位和低16位都反映到低位上,使hash更均匀
    • 到table[(n-1)&hash]取值(n为2的幂,相当于取hash的低位)
    • 扩容:新数组newTable为原数组的2倍;如果节点是TreeNode,分成2棵树;如果是节点,hash&oldCap==0放在原索引,否则原索引+oldCap.
    • JDK 8前,并发扩容产生循环链表,get时死循环。JDK 8不会死循环,但仍然会产生数据丢失。
  14. ==和equals()的区别

    • ==判断2个对象的地址是否相等
    • equals()没有重写时等价于==,可以重写
  15. hashCode()和equals()

    • HashMap比较key是否相同时,先判断hashCode是否相同,再比较equals()是否相同。
    • 2个对象相等,散列码相同。
    • equals()重写过,hashCode()也要重写,否则有可能2个对象相同散列码不同,散列到不同的散列桶中找不到key
  16. 重写equals()的约定

    (1) 使用==操作符检查参数是否时这个对象的引用

    (2) 使用instance检查是否为正确的类型

    (3) 把参数转换成正确的类型

    (4) 比较每个关键域是否匹配

    (5) 思考equals()方法是否满足自反性、对称性、传递性、一致性、非空性

  17. 重写hashCode

    (1) int result = 17;

    (2) 对于每个关键域:

    a. 计算关键域int类型的散列码c,float, double转成int, long, long: (int) (f^(f»>32))

    b. result=31*result+c

  18. TreeMap的底层实现是红黑树,AVL树是严格平衡,红黑树是弱平衡

  19. 红黑树性质

    (1) 每个节点要么是红色,要么是黑色

    (2) 根节点永远是黑色的

    (3) 叶结点是空节点,并且是黑色的

    (4) 每个红色节点的2个子节点都是黑色的

    (5)从任一节点到其子树的每个叶结点都包含相同数量的黑色节点

  20. Java 8

    • lambda表达式
    • 方法引用
    • 接口的默认方法
    • stream()
    • Optional
  21. 多态的实现原理

    • Java 的方法调用方式

      Java 的方法调用有两类,动态方法调用与静态方法调用。

      静态方法调用是指对于类的静态方法的调用方式,是静态绑定的;而动态方法调用需要有方法调用所作用的对象,是动态绑定的。

      类调用 (invokestatic) 是在编译时就已经确定好具体调用方法的情况。

      实例调用 (invokevirtual)则是在调用的时候才确定具体的调用方法,这就是动态绑定,也是多态要解决的核心问题。

    • 方法重写后的动态绑定

      方法表是实现动态调用的核心。为了优化对象调用方法的速度,方法区的类型信息会增加一个指针,该指针指向记录该类方法的方法表,方法表中的每一个项都是对应方法的指针。这些方法中包括从父类继承的所有方法以及自身重写(override)的方法。

      多态允许具体访问时实现方法的动态绑定。Java对于动态绑定的实现主要依赖于方法表,通过继承和接口的多态实现有所不同。

      继承:在执行某个方法时,在方法区中找到该类的方法表,再确认该方法在方法表中的偏移量,找到该方法后如果被重写则直接调用,否则认为没有重写父类该方法,这时会按照继承关系搜索父类的方法表中该偏移量对应的方法。

      接口:Java 允许一个类实现多个接口,从某种意义上来说相当于多继承,这样同一个接口的的方法在不同类方法表中的位置就可能不一样了。所以不能通过偏移量的方法,而是通过搜索完整的方法表。

并发

Java并发编程的艺术

并发

  1. 并发执行有可能比串行慢,因为线程有创建和上下文切换的开销

  2. 使用Lmbench测量上下文切换时长,vmstat测量上下文切换的次数

  3. 如何减少上下文切换

    • CAS算法,不用加锁
    • 使用最少线程
    • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
  4. synchronized

    • 3种形式

      • 对于普通的同步方法,锁的是当前实例对象
      • 对于静态同步方法,锁的是当前类的Class对象
      • 对于同步方法块,锁的是synchronized括号里配置的对象
    • JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步时使用ACC_SYNCHRONIZED标志位实现的。

  5. Java对象头的Mark Word中存储对象的hashCode、分代年龄和锁标记位。

    • 偏向锁:Mark Word中存储指向当前线程的偏向锁。
    • 轻量级锁:线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。
    • 重量级锁
  6. CAS的三大问题

    (1) ABA问题 (2) 循环时间长开销大 (3) 只能保证一个共享变量的原子操作

  7. JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

    如果线程A要和B通信,必须要经历下面2个步骤:

    (1) 线程A把本地内存A中更新过的共享变量刷新到主内存中去。 (2) 线程B到主内存中去读取线程A之前已经更新过的共享变量。

  8. 为什么要使用多线程(主要原因是IO阻塞和多CPU)

    • 单核时代提高CPU和IO的综合利用率
    • 多核时代提高CPU利用率
    • 业务更快的响应时间
  9. 设置线程优先级时,针对频繁阻塞的线程需设置较高的优先级,而偏重计算的线程设置较低的优先级,确保处理器不会被独占。

  10. 线程的状态

    • NEW
    • TERMINATED
    • RUNNABLE:运行中,包括就绪和运行
    • BLOCKED:阻塞于锁
    • WAITING:等待其他线程通知
    • TIME_WAITING:超时等待,可以在指定时间自行返回
  11. 当一个Java虚拟机中不存在非Daemon线程时,Java虚拟机将会退出。

  12. 线程通过方法isInterrupted()来判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标志位进行复位。

  13. 除了中断以外,还可以利用一个boolean变量来控制是否需要停止任务并终止该线程。

  14. suspend()调用后不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态。

    stop()终结一个线程时不会保证线程资源正常释放

    yield()方法会临时暂停正在执行的线程,来让有同样优先级的线程有机会执行。yield()方法不保证当前的线程会暂停或停止,但是可以保证当前线程调用yield方法时会放弃CPU。

  15. 线程间通信

    • wait/notify
    • volatile/synchronized
    • join
    • countdownlatch/cyclicbarrier
    • semaphore
  16. Fork/Join框架

    Fork就是把一个大任务切分成若干子任务,Join就是合并这些子任务的执行结果。

    工作窃取算法是指某个线程从其他队列中窃取任务来执行。充分利用线程进行并行计算,减少了线程的竞争。

  17. AtomicInteger

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next)) {
                return current;
            }
        }
    }
      
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    
  18. 线程池的好处

    • 降低资源消耗
    • 提高响应速度
    • 提高线程的可管理性
  19. 线程池的参数

    (1) corePoolSize:核心线程数

    (2) runnableTaskQueue:任务队列

    ArrayBockingQueue:有界

    LinkedBlockingQueue:有界,默认Integer.MAX_VALUE

    SynchronousQueue:不存储元素

    PriorityBlockingQueue:具有优先级、无限

    (3) maxPoolSize:线程池的最大数量

    (4) ThreadFactory:创建线程的工厂

    (5) RejectExcutionHandler 饱和策略

    AbortPolicy 直接抛出异常

    CallerRunPolicy 调用者所用线程

    DiscardOldestPolicy 丢弃最早

    DiscardPolicy 丢弃

    (6) keepAliveTime 线程池工作线程空闲后,保持存活的时间

  20. 线程池提交任务

    execute()提交不需要返回值的任务,输入Runnable类实例

    submit()提交需要返回值的任务,输入Runnale或Callable,返回一个future对象,future的get()方法会阻塞到任务完成并返回返回值

  21. 关闭线程池

    RUNNING

    SHUTDOWN(shutdown) 继续处理等待队列

    STOP(shutdownNow) 不再处理等待队列,中断正在执行的线程

  22. CPU密集型任务应配置尽可能小的线程,如N_CPU+1; IO密集型任务线程并不是一直在执行任务,应配置尽可能多的线程,如2N_CPU.

  23. Executor

    (1) FixedThreadPool

    (2) SingleThreadExecutor

    (3) CachedThreadExecutor

    (4) ScheduledThreadExecutor

  24. Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。

  25. Runnable接口不会返回结果,而Callable接口可以返回结果。

  26. CopyOnWriteArrayList:修改时,复制原有数据,将修改内容写入副本。写完后再去替换原来的数据。

  27. java.util.comncurrent并发包

    并发容器 concurrentHashMap, copyOnWriteArrayList

    原子变量 AtomicInteger

    显式锁 lock

    同步工具 semaphore, countdownlatch, cyclicbarrier

    线程池

  28. 可重入锁独有的功能

    • 指定公平锁还是非公平锁
    • Condition条件类,分组唤醒需要唤醒的线程
    • 中断等待锁
  29. ThreadLocal内存泄漏

    ThreadLocalMap的 key 就是 ThreadLocal对象,value 就是 ThreadLocal 对象调用set方法设置的值。

    内存泄漏:key为弱引用,value为强引用,key被清理掉,value未被清理。

  30. 引用

    强引用:不会被清理

    弱引用:生存到下一次垃圾收集发生之前

    软引用:系统将要内存溢出异常前回收

    虚引用:唯一目的就是被回收前收到一个系统通知

  31. CountDownLatch, CyclicBarrier

    CountDownLatch: countDown()计数减1,计数为0时释放等待线程,一等多

    CyclicBarrier: await()计数减1,计数为0时释放等待线程,多个线程互相等待

  32. 创建线程的方法

    • 继承Thread类
    • 实现Runnable接口,传进Thread
    • 实现Callable接口,传进Thread
    • 线程池
  33. 线程同步的方法

    • synchronized
    • volatile
    • 重入锁
    • ThreadLocal
    • 阻塞队列
    • 原子变量
  34. 重入:获取锁的操作粒度是线程而不是调用,一个线程如果获取了锁之后那么也可以再次获取这个锁。

  35. AQS

    AQS

    AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

    CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

    AQS 使用一个 int 成员变量来表示同步状态,通过内置的 FIFO 队列来完成获取资源线程的排队工作。AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。

    AQS 定义两种资源共享方式

    1)Exclusive(独占)

    只有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁,ReentrantLock 同时支持两种锁,下面以 ReentrantLock 对这两种锁的定义做介绍:

    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
    • 非公平锁:当线程要获取锁时,先通过两次 CAS 操作去抢锁,如果没抢到,当前线程再加入到队列中等待唤醒。

    2)Share(共享)

    多个线程可同时执行,如 Semaphore/CountDownLatch。

    AQS 底层使用了模板方法模式

    同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):

    使用者继承 AbstractQueuedSynchronizer 并重写指定的方法。(这些重写方法很简单,无非是对于共享资源 state 的获取和释放)

    将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

    这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。

    模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票buyTicket()->安检securityCheck()->乘坐某某工具回家ride()->到达目的地arrive()。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了ride()方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 ride()方法。

JVM

JVM

  1. 双亲委派模型

    如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器完成。只有当父类加载器自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子类加载器才会尝试自己去加载。

  2. 三种系统提供的类加载器

    • 启动类加载器
    • 扩展类加载器
    • 应用程序类加载器
  3. 破坏双亲委派模型

    • ClassLoader.loadClass()方法
    • 线程上下文加载器
    • 用户对程序的动态性要求
  4. 双亲委派模型的好处

    • 避免类的重复加载
    • Java的核心API不被篡改
  5. 线程的实现

    • 内核线程,一对一线程模型
    • 用户线程,一对多线程模型
  6. Amdahl定律:并行化来压榨计算机运算能力

  7. 类加载的过程

    • 加载(class文件)
    • 连接:验证(载入的class文件数据的正确性)、准备(为类的静态变量分配存储空间)、解析(将符号引用转换成直接引用)
    • 初始化(静态变量、静态代码块)
  8. volatile

    • 第一个语义:保证此变量对所有线程的可见性
    • volatile变量的运算在并发下一样是不安全的
    • 第二个语义:禁止指令重排序优化
  9. happends-before先行发生

    happens-before是Java内存模型中定义的2项操作之间的偏序关系。如果说操作A先行发生于操作B,其实就是说发生操作B之前,操作A产生的影响能被B观察到。

    • 程序次序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
    • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
    • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
    • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C.
  10. JVM运行时数据区域

    线程私有的:程序计数器、虚拟机栈、本地方法栈

    线程共享的:堆、方法区(存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据)

    JDK 8中用元数据去代替永久代实现方法区,并把字符串常量池和类静态变量迁移到堆中存放。

    为什么要用元数据区代替永久代?

    (1) 字符串存在永久代,容易出现性能问题

    (2) 类信息比较难确定大小,永久代空间分配困难

  11. 垃圾回收GC

    • 根搜索算法:GC Roots向下搜索,判断是否可达

      GC Roots包括下面几种:

      1. 虚拟机栈中引用的对象
      2. 本地方法栈中引用的对象
      3. 方法区中的的常量引用的对象
      4. 方法区中的类静态变量引用的对象
    • 分代收集算法

      Eden: From Survivor: To Survivor = 8:1:1

      Eden区和From Survivor区中还存活的对象移动到To Survivor区,对象优先在Eden区分配,大对象直接进入老年代,长期存活的对象将进入老年代(15岁),老年代分配担保

    • Minor GC的触发条件:Eden区满

    • Full GC的触发条件

      1. 调用System.gc时建议执行Full GC
      2. 老年代空间不足
      3. 方法区空间不足
      4. 老年代担保空间不足
    • 新生代的收集器:Serial(串行)、ParNew(并行)、Parallel(高吞吐量),使用复制算法

    • 老年代的收集器有Serial Old、Parallel Old,使用标记-整理算法

    • CMS收集器基于标记-清除算法,步骤分为初始标记、并发标记、重新标记、并发清除

    • G1收集器将Java堆划分为多个大小相等的独立区域(Region),相关引用记录在Region对应的Remembered Set中。从整体看基于标记-整理算法,从局部(2个Region之间)看基于复制算法。步骤分为初始标记、并发标记、最终标记、筛选回收。

  12. OOM、CPU占用过高排查

    • jsp:列出正在运行的虚拟机进程
    • jstat:统计信息,包括分区占用情况
    • jmap:内存映像
    • jstack:堆栈跟踪
    • VisualVM:生成浏览堆转储快照、分析CPU、内存性能
    • top ps

Spring

Spring揭秘

Spring

  1. Spring模块

    • Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
    • Spring Aspects : 该模块为与AspectJ的集成提供支持。
    • Spring AOP :提供了面向切面的编程实现。
    • Spring JDBC : Java数据库连接。
    • Spring JMS :Java消息服务。
    • Spring ORM : 用于支持Hibernate等ORM工具。
    • Spring Web : 为创建Web应用程序提供支持。
    • Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
  2. Spring IOC & AOP

    • IOC:IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。 IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
    • DI:依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念。创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。依赖注入可以通过setter方法注入、构造器注入和接口注入三种方式来实现,Spring支持setter注入和构造器注入。
    • AOP:AOP(Aspect-Oriented Programming:面向切面编程)能够分离应用的业务逻辑和系统级服务。将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
  3. IoC容器初始化流程

    Spring的IoC容器所起的作用,它会以某种方式加载Configuration Metadata(通常也就是XML格式的配置信息),然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。

    Spring的IoC容器实现以上功能的过程,基本上可以按照类似的流程划分为两个阶段,即容器启动阶段和Bean实例化阶段。

    1. 容器启动阶段

      容器启动伊始,首先会通过某种途径加载Configuration Metadata. 除了代码方式比较直接,在大部分情况下,容器需要依赖某些工具类对加载的Configuration Metadata进行解析和分析,并将分析后的信息编组为响应的BeanDefinition,最后把这些保存了bean定义必要信息的BeanDefinition,注册到相应的BeanDefinitionRegistry,这样容器启动工作就完成了。

      总的来说,该阶段所作的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集。当然,一些验证性或者辅助性的工作也可以在这个阶段完成。

    2. Bean实例化阶段

      经过第一阶段,现在所有的bean定义信息都通过BeanDefinition的方式注册到了Bean DefinitionRegistry中。当某个请求方法通过容器的getBean方法明确地请求某个对象,或者音依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段的活动。

      该阶段,容器会首先检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。如果说第一阶段只是根据图纸装配生产线的话,那么第二阶段就是使用装配好的生产线来生产具体的产品了。

  4. Spring 中的 bean 的作用域有哪些?

    • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
    • prototype : 每次请求都会创建一个新的 bean 实例。

    Spring 2.x中针对WebApplicationContext新增了3个作用域

    • request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
    • session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
    • global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
  5. Spring 中的单例 bean 的线程安全问题了解吗?

    单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。

    常见的有两种解决办法:

    1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)。

    2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

  6. Spring中自动装配的方式

    • no:不进行自动装配,手动设置Bean的依赖关系。
    • byName:根据Bean的名字进行自动装配。
    • byType:根据Bean的类型进行自动装配。
    • constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。
    • autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。
  7. Spring 中的 bean 生命周期?

    实例化bean、设置属性(依赖注入)、注入aware接口、执行postProcessBeforeInitialization() 方法、调用初始化方法、执行postProcessAfterInitialization() 方法、调用销毁方法

  8. Spring提供了两种容器类型:BeanFactory和ApplicationContext

    • BeanFactory: 默认采用延迟初始化策略。容器启动初期速度较快,所需要的资源有限。
    • ApplicationContext: 除了拥有BeanFactory的所有指出,ApplicationContext还提供了其他高级特性,比如事件发布、国际化信息支持等。默认初始化并绑定完成,要求更多的系统资源。
  9. Spring如何解决循环依赖(三级缓存)

    • 第一级缓存:单例缓存池singletonObjects
    • 第二级缓存:早期提前暴露的对象缓存earlySingletonObjects。(属性还没有值,对象也没有被初始化
    • 第三级缓存:singletonFactories单例对象工厂缓存。
  10. 注解

    • @RestController = @Controller + @ResponseBody

    • 组件类注解

      • @Component :标准一个普通的spring Bean类。
      • @Repository:标注一个DAO组件类。
      • @Service:标注一个业务逻辑组件类。
      • @Controller:标注一个控制器组件类。
      • @Component可以代替@Repository、@Service、@Controller,因为这三个注解是被@Component标注的。
    • @Configuration, @Component, @Bean

      • @Configuration 来注解类表示类可以被 Spring 的 IoC 容器所使用,作为 bean 定义的资源。
      • @Component可以替代 @Configuration注解。
      • @Bean注解主要用于方法上,有点类似于工厂方法。
    • @Scope:声明 Spring Bean 的作用域

    • @Autowired, @Resource

      • @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。
      • @Resource默认按照ByName自动注入,@Resource有两个重要的属性:name和type,Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
    • 处理常见的 HTTP 请求类型

      • @GetMapping(“users”) 等价于@RequestMapping(value=”/users”,method=RequestMethod.GET)
      • @PostMapping(“users”) 等价于@RequestMapping(value=”/users”,method=RequestMethod.POST)
    • 前后端传值

      • @PathVariable用于获取路径参数,@RequestParam用于获取查询参数。
      • @RequestBody
  11. Spring AOP 和 AspectJ AOP 有什么区别?

    Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。

    Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。

  12. AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)

    a. 连接点(Joinpoint):程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。Spring仅支持方法的连接点。

    b. 切点(Pointcut):如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。

    c. 增强(Advice):增强是织入到目标类连接点上的一段程序代码。Spring提供的增强接口都是带方位名的,如:@Before, @AfterReturning, @AfterThrowing, @After (After finally), @Around。

    d. 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过引介功能,可以动态的为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

    e. 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式:①编译期织入:需要特殊的Java编译期(例如AspectJ的ajc);②装载期织入:要求使用特殊的类加载器,在装载类的时候对类进行增强;③运行时织入:在运行时为目标类生成代理实现增强。Spring采用了动态代理的方式实现了运行时织入,而AspectJ采用了编译期织入和装载期织入的方式。

    f. 切面(Aspect):切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。

  13. AOP的原理是什么?

    Spring 提供了两种方式来生成代理对象: JDKProxy 和Cglib,具体使用哪种方式生成由AopProxyFactory 根据AdvisedSupport 对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK 动态代理技术,否则使用Cglib 来生成代理。

    • JDK动态代理

      JDK 动态代理主要涉及到java.lang.reflect 包中的两个类:Proxy 和InvocationHandler。InvocationHandler 是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。Proxy 利用InvocationHandler 动态创建一个符合某一接口的实例,生成目标类的代理对象。

      动态代理机制可以在运行期间,为响应的接口动态生成对应的代理对象。可以将横切关注点逻辑封装到动态代理的InvocationHandler中。

      这种方式实现的唯一缺点或者优点就是,所有需要织入横切关注点逻辑的模块类都得实现响应的接口,因为动态代理机制只针对接口有效。

      JDK动态代理为什么必须针对接口?

      由于java的单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。

    • CGLib 动态字节码增强

      CGLib 全称为Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java 类与实现Java 接口,CGLib 封装了asm,可以在运行期动态生成新的class。和JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib 创建动态代理。

      可以使用ASM或者CGLIB等Java工具库,在程序运行期间,动态构建字节码的class文件。

      可以为需要织入横切逻辑的模块类在运行期间,通过动态字节码增强技术,为这些系统模块类生成相应的子类,而将横切逻辑家都这些子类中,让应用程序在执行期间使用的是这些动态生成的子类,从而达到将横切逻辑织入系统的目的。

      使用动态字节码增强技术,即使模块类没有实现相应的接口,我们依然可以对其进行扩展,而不用像动态代理那样受限于接口。

  14. SpringAOP的在实际应用中场景

    1. Authentication 权限
    2. Caching 缓存
    3. Context passing 内容传递
    4. Error handling 错误处理
    5. Lazy loading 懒加载
    6. Debugging 调试
    7. logging,tracing,profiling and monitoring 记录跟踪 优化 校准
    8. Performance optimization 性能优化
    9. Persistence 持久化
    10. Resource pooling 资源池
    11. Synchronization 同步
    12. Transactions 事务
    13. Logging 日志
  15. Order的作用

    Spring在处理同一Jointpoint处的多个Advisor时,实际上会按照指定的顺序和优先级来执行它们,顺序号决定优先级,顺序号越小,优先级越高,优先级排在前面的,将被优先执行。我们可以从0或者1开始,因为小于0的顺序号原则上由Spring AOP框架内部使用。默认情况下,如果我们不明确指定各个Advisor的执行顺序,那么Spring会按照它们的声明顺序来应用它们,最先声明的顺序号最小但优先级最大,其次次之。

  16. 迈向Spring MVC的旅程

    • Sevlet

      Servlet运行于Web容器之内,提供了Session和对象声明周期管理等功能。

      弊病式Magic Servlet,开发人员会将各种逻辑混杂于一处,包括流程控制逻辑、视图显示逻辑、业务逻辑、数据访问逻辑等,这就在成了后去系统能以维护等一系列问题。

    • JSP

      为了能够将Servlet中的输入显然逻辑以独立的单元抽取出来,我们通常使用模板化的方法。JSP的提出,成为Java平台上开发Web应用程序事实上的模板化视图标准。

      使用Servlet处理Web请求,我们需要在web.xml文件中,注册相应的请求URL与具体的处理Servlet之间的一个映射关系。

  17. Servlet生命周期

    • 加载Servlet。当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例
    • 初始化。当Servlet被实例化后,Tomcat会调用init()方法初始化这个对象
    • 处理服务。当浏览器访问Servlet的时候,Servlet 会调用service()方法处理请求
    • 销毁。当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会自动调用destroy()方法,让该实例释放掉所占的资源。一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁
    • 卸载。当Servlet调用完destroy()方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()方法进行初始化操作。
  18. SpringMVC 工作原理

    浏览器请求DispatcherServlet,DispatcherServlet根据请求调用HandlerMapping,解析到对应的Handler。HandlerAdapter根据Handler调用controller,返回ModelAndView,包括数据对象Model和逻辑View。ViewResolver根据逻辑View查找实际的View。DispatcherServlet把Model传给View渲染,并返回给浏览器。

  19. Spring 管理事务的方式有几种?

    1. 编程式事务,在代码中硬编码。(不推荐使用)
    2. 声明式事务,在配置文件中配置(推荐使用)

    声明式事务又分为两种:

    1. 基于XML的声明式事务
    2. 基于注解的声明式事务
  20. Spring 事务中的隔离级别

    • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
    • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
    • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
    • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
    • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
  21. 事务的传播特性

    • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
    • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
    • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
    • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
    • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
    • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
  22. @Transactional(rollbackFor = Exception.class)注解

    在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。

  23. #{}和${}的区别是什么?

    ${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为com.mysql.jdbc.Driver。

    #{}是 sql 的参数占位符,Mybatis 会将 sql 中的#{}替换为?号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的?号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值,相当于 param.getItem().getName()。

  24. mybatis缓存

    mybatis提供了缓存机制减轻数据库压力,提高数据库性能

    mybatis的缓存分为两级:一级缓存、二级缓存

    一级缓存是SqlSession级别的缓存,缓存的数据只在SqlSession内有效

    mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。

    二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的

    二级缓存是mapper级别的缓存,也就是同一个namespace的mapper.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域

  25. Xml 映射文件中,除了常见的 select|insert|update|delete 标签之外,还有哪些标签?

    还有很多其他的标签,<resultMap><parameterMap><sql><include><selectKey>,加上动态 sql 的 9 个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中为 sql 片段标签,通过<include>标签引入 sql 片段,<selectKey>为不支持自增的主键生成策略标签。

  26. 最佳实践中,通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?

    Dao 接口,就是人们常说的 Mapper接口,接口的全限名,就是映射文件中的 namespace 的值,接口的方法名,就是映射文件中MappedStatement的 id 值,接口方法内的参数,就是传递给 sql 的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在 Mybatis 中,每一个<select><insert><update><delete>标签,都会被解析为一个MappedStatement对象。

    Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。

    Dao 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象,代理对象 proxy 会拦截接口方法,转而执行MappedStatement所代表的 sql,然后将 sql 执行结果返回。

  27. mybatis和hibernate, jdbc的区别;mybatis解决了什么问题,mybatis有什么缺点

    JDBC

    1.使用JDBC编程需要链接数据库,注册驱动和数据库信息。

    2.操作Connection,打开Statement对象。

    3.通过Statement执行SQL语句,返回结果放到ResultSet对象。

    4.使用ResultSet读取数据。

    5.关闭数据库相关的资源。

    JDBC缺点:

    工作量比较大,需要连接,然后处理jdbc底层事务,处理数据类型,还需要操作Connection,Statement对象和ResultSet对象去拿数据并关闭他们

    没有使用框架的时候 sql语句是和java语句一起写在dao层 耦合度高,维护不易而且实际开发中sql是会变的,需要频繁修改 当你要替换某个sql代码的时候,需要对整个项目 进行操作,极不方便。

    JDBC优点:

    接近底层,理论上效率最高

    MyBatis

    半自动化的持久层框架 半自动 轻量级

    1.SQLSessionFactoryBuilder(构造器):它会根据配置信息或者代码生成SqlSessionFactory。

    2.SqlSessionFactory(工厂接口):依靠工厂生成SqlSession。

    3.SqlSession(会话):是一个既可以发送SQL去执行并且返回结果,也可以获取Mapper接口。

    4.SQL Mapper:是由一个JAVA接口和XML文件(或注解)构成,需要给出对应的SQL和映射规则。SQL是由Mapper发送出去,并且返回结果。

    Mybatis的优点:

    1、易于上手和掌握,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。

    2、sql写在xml里,便于统一管理和优化, 解除sql与程序代码的耦合。

    3、提供映射标签,支持对象与数据库的orm字段关系映射

    4、 提供对象关系映射标签,支持对象关系组建维护

    5、提供xml标签,支持编写动态sql。

    6、速度相对于Hibernate的速度较快

    Mybatis的缺点:

    1、关联表多时,字段多的时候,sql工作量很大。

    2、sql依赖于数据库,导致数据库移植性差。

    3、由于xml里标签id必须唯一,导致DAO中方法不支持方法重载。

    4、对象关系映射标签和字段映射标签仅仅是对映射关系的描述,具体实现仍然依赖于sql。

    5、DAO层过于简单,对象组装的工作量较大。

    6、不支持级联更新、级联删除。

    7、Mybatis的日志除了基本记录功能外,其它功能薄弱很多。

    8、编写动态sql时,不方便调试,尤其逻辑复杂时。

    9、提供的写动态sql的xml标签功能简单,编写动态sql仍然受限,且可读性低。

    Hibernate

    Hibernate的优点:

    1、hibernate是全自动,hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。

    2、功能强大,数据库无关性好,O/R映射能力强,需要写的代码很少,开发速度很快。

    3、有更好的二级缓存机制,可以使用第三方缓存。

    4、数据库移植性良好。

    5、hibernate拥有完整的日志系统,hibernate日志系统非常健全,涉及广泛,包括sql记录、关系异常、优化警告、缓存提示、脏数据警告等

    Hibernate的缺点:

    1、学习门槛高,精通门槛更高,程序员如何设计O/R映射,在性能和对象模型之间如何取得平衡,以及怎样用好Hibernate方面需要的经验和能力都很强才行

    2、hibernate的sql很多都是自动生成的,无法直接维护sql;虽然有hql查询,但功能还是不及sql强大,见到报表等变态需求时,hql查询要虚,也就是说hql查询是有局限的;hibernate虽然也支持原生sql查询,但开发模式上却与orm不同,需要转换思维,因此使用上有些不方便。总之写sql的灵活度上hibernate不及mybatis。

  28. 反射

    反射可以实现在运行时可以知道任意一个类的属性和方法。

    理解Class类和类类型

    类是java.lang.Class类的实例对象,而Class是所有类的类。

    1
    2
    3
    4
    5
    6
    
    Class c1 = Code.class;
    //这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class得到的
    Class c2 = code1.getClass();
    //code1是Code的一个对象,这种方式是通过一个类的对象的getClass()方法获得的
    Class c3 = Class.forName("com.trigl.reflect.Code");
    //这种方法是Class类调用forName方法,通过一个类的全量限定名获得
    

    这里,c1、c2、c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Code的类类型(class type)。

    Java反射相关操作

    获取成员方法Method

    1
    2
    
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的
    public Method getMethod(String name, Class<?>... parameterTypes) // 得到该类所有的public方法,包括父类的
    

    获取成员变量Field

    1
    2
    
    public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
    public Field getField(String name) // 获得该类自所有的public成员变量,包括其父类变量
    

    获取构造函数Constructor

    1
    2
    
    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) //  获得该类所有的构造器,不包括其父类的构造器
    public Constructor<T> getConstructor(Class<?>... parameterTypes) // 获得该类所以public构造器,包括父类
    
  29. 什么是 Spring Boot?

    首先,重要的是要理解 Spring Boot 并不是一个框架,它是一种创建独立应用程序的更简单方法,只需要很少或没有配置(相比于 Spring 来说)。Spring Boot最好的特性之一是它利用现有的 Spring 项目和第三方项目来开发适合生产的应用程序。

    说出使用Spring Boot的主要优点

    1. 开发基于 Spring 的应用程序很容易。
    2. Spring Boot 项目所需的开发或工程时间明显减少,通常会提高整体生产力。
    3. Spring Boot不需要编写大量样板代码、XML配置和注释。
    4. Spring引导应用程序可以很容易地与Spring生态系统集成,如Spring JDBC、Spring ORM、Spring Data、Spring Security等。
    5. Spring Boot遵循“固执己见的默认配置”,以减少开发工作(默认配置可以修改)。
    6. Spring Boot 应用程序提供嵌入式HTTP服务器,如Tomcat和Jetty,可以轻松地开发和测试web应用程序。(这点很赞!普通运行Java程序的方式就能运行基于Spring Boot web 项目,省事很多)
    7. Spring Boot提供命令行接口(CLI)工具,用于开发和测试Spring Boot应用程序,如Java或Groovy。
    8. Spring Boot提供了多种插件,可以使用内置工具(如Maven和Gradle)开发和测试Spring Boot应用程序。

    什么是 Spring Boot Starters?

    Spring Boot Starters 是一系列依赖关系的集合,因为它的存在,项目的依赖之间的关系对我们来说变的更加简单了。举个例子:在没有Spring Boot Starters之前,我们开发REST服务或Web应用程序时; 我们需要使用像Spring MVC,Tomcat和Jackson这样的库,这些依赖我们需要手动一个一个添加。但是,有了 Spring Boot Starters 我们只需要一个只需添加一个spring-boot-starter-web一个依赖就可以了,这个依赖包含的字依赖中包含了我们开发REST 服务需要的所有依赖。

    1
    2
    3
    4
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    

    如何在Spring Boot应用程序中使用Jetty而不是Tomcat?

    Spring Boot Web starter使用Tomcat作为默认的嵌入式servlet容器, 如果你想使用 Jetty 的话只需要修改pom.xml(Maven)或者build.gradle(Gradle)就可以了。

    介绍一下@SpringBootApplication注解

    大概可以把 @SpringBootApplication 看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合。根据 SpringBoot官网,这三个注解的作用分别是:

    • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
    • @ComponentScan扫描被@Component (@Service,@Controller)注解的bean,注解默认会扫描该类所在的包下所有的类。
    • @Configuration:允许在上下文中注册额外的bean或导入其他配置类

    (重要)Spring Boot 的自动配置是如何实现的?

    这个是因为@SpringBootApplication 注解的原因。

    Spring Boot支持哪些嵌入式web容器?

    Spring Boot支持以下嵌入式servlet容器:

    Name Servlet Version
    Tomcat 9.0 4.0
    Jetty 9.4 3.1
    Undertow 2.0 4.0

    您还可以将Spring引导应用程序部署到任何Servlet 3.1+兼容的 Web 容器中。

    这就是你为什么可以通过直接像运行 普通 Java 项目一样运行 SpringBoot 项目。这样的确省事了很多,方便了我们进行开发,降低了学习难度。

操作系统

操作系统

操作系统

  1. 系统调用

    进程在系统上的运行分为两个级别:

    用户态(user mode) : 用户态运行的进程或可以直接读取用户程序的数据。

    系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。

    我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能,就需要系统调用了!

    也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。

  2. 进程间的通信方式

    • 管道
    • 消息队列
    • 信号量
    • 共享内存
    • 套接字
  3. 线程间的同步的方式

    • 互斥量
    • 信号量
    • 事件
  4. 进程的调度算法

    • 先到先服务(FCFS)调度算法
    • 短作业优先(SJF)的调度算法
    • 时间片轮转调度算法
    • 多级反馈队列调度算法 :既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。
    • 优先级调度
  5. 死锁

    • 死锁预防:互斥、不剥夺、请求保持(一次申请完所需的全部资源)、循环等待(顺序资源分配法)
    • 死锁避免:银行家算法
    • 死锁检测与解除:资源分配图、死锁定理(S为死锁的条件是当且仅当S状态的资源分配图是不可完全简化的)
  6. 页式存储

    页号根据页表查到块号,与页内偏移量拼接,得到物理地址

    连续的逻辑地址->不连续的物理地址

  7. 段式存储

    段号根据段表查到基址,加上段内偏移量,得到物理地址。

    页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。

  8. 段页式

    段号根据段表查到页表的起始地址。页号根据页表查到块号,与页内偏移量拼接,得到物理地址。

  9. 快表

    我们可以把块表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。

  10. 多级页表

    引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。

  11. 虚拟内存

    通过 虚拟内存 可以让程序可以拥有超过系统物理内存大小的可用内存空间。

  12. 局部性原理

    • 时间局部性 :如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
    • 空间局部性 :一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
  13. 缺页中断

    在请求分页系统中,每当所要访问的页面不在内存时,便产生一个缺页中断,请求操作系统将所缺页调入内存。

  14. 页面置换算法

    • 最佳(OPT)置换算法

      最佳置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。

    • 先进先出(FIFO)置换算法

      优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。

      FIFO算法会产生当所分配的物理块数增大而页故障数不增反减的异常现象,称为Belady异常。只有FIFO算法可能出现Belady异常,而LRU和OPT算法永远不会出现Belady异常。

    • 最近最久未使用(LRU)置换算法

      它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法未每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰。

    • 时钟(CLOCK)置换算法

      又称为最近未用(NRU)算法。给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置为1;当该页随后再被访问到时,它的使用位也被置为1。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。当遇到一个使用位为1的帧时,操作系统就将该位重新置为0。

数据结构

数据结构

leetcode整理

  1. 散列表

    • 散列函数

      • 除法:$h(k)=k \mod m$
      • 乘法:将模块轮旋转k个A
      • 加法:位运算、查表、混合
    • 一个好的哈希函数应该具备以下三点:

      • 抗碰撞性,尽量避免冲突。
      • 抗篡改性,只要改动一个字节,其哈希值也会很大不同。
      • 查找效率。
    • 处理冲突

      • 拉链法
      • 开放定址法:线性探查法、平方探查法、再散列法、伪随机序列法
  2. B树和B+树的区别

    • B树n个关键字对应n+1棵子树
    • B+树n个关键字对应n棵子树
    • B+树叶节点包含信息,提供数据库的索引范围查询和遍历的功能
  3. 布隆过滤器

    • 将输入经过多个hash函数,数字对应下标置1.
    • 布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不存在,一定不存在。
    • 作用:判断给定数据是否存在(防止缓存穿透)、去重
  4. 海量数据TopK问题

    • 找重复最多的TopK:散列成多个小数据再统计
    • 找最大TopK:外排、优先队列、分治找最大
    • 去重:布隆过滤器
  5. 主方法

    对于形式$T(n)=aT(n/b)+f(n)$的递归,分为3种情况:

    1. $f(n)=O(n^{\log_ba-\varepsilon})$

      $f(n)$多项式地慢于$n^{\log_ba}$增长(相差$n^\varepsilon$)

      结论:$T(n)=\Theta(n^{\log_ba})$

    2. $f(n)=\Theta(n^{\log_ba}\log^kn)$

      $f(n)$与$n^{\log_ba}$以相似速率增长

      结论:$T(n)=\Theta(n^{\log_ba}\log^{k+1}n)$

    3. $f(n)=\Omega(n^{\log_ba+\varepsilon})$

      $f(n)$多项式地快于$n^{\log_ba}$增长(相差$n^\varepsilon$)

      结论:$T(n)=\Theta(f(n))$

参考

JavaGuide

学妹推荐的笔记

后端架构师技术图谱