暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

基础概念及问答(二)

萌小璐 2021-10-23
393
1: 请简要介绍一下你了解Mysql的哪些锁
不同的存储引擎,默认哪种锁
MyISAM和MEMORY采用表级锁(table-level locking)
BDB采用页面锁(page-level locking)或表级锁,默认为页面锁
InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁
大致分类:
按照按锁粒度分类-行级锁&表级锁&页级锁
1 行级锁
(1) 描述
行级锁是mysql中锁定粒度最细的一种锁。表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突,其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁和排他锁
(2)特点
开销大,加锁慢,会出现死锁。发生锁冲突的概率最低,并发度也最高。
2 表级锁
(1) 描述
表级锁是mysql中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分mysql引擎支持。最常使用的MyISAM与InnoDB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)
(2)特点
开销小,加锁快,不会出现死锁。发生锁冲突的概率最高,并发度也最低。
3 页级锁
(1) 描述
页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。因此,采取了折衷的页级锁,一次锁定相邻的一组记录。BDB 支持页级锁。
(2)特点
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
 锁级别分类 - 共享锁 & 排他锁 & 意向锁
1 共享锁(Share Lock)
共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
用法
SELECT … LOCK IN SHARE MODE;
在查询语句后面增加LOCK IN SHARE MODE,MySQL 就会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。
2 排他锁(Exclusive Lock)
排他锁又称写锁、独占锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
用法
SELECT … FOR UPDATE;
在查询语句后面增加FOR UPDATE,MySQL 就会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
3 意向锁(Intention Lock)
意向锁是表级锁,其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型。InnoDB 中的两个表锁:
意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁;
意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。
意向锁是 InnoDB 自动加的,不需要用户干预。
对于INSERT、UPDATE和DELETE,InnoDB 会自动给涉及的数据加排他锁;对于一般的SELECT语句,InnoDB 不会加任何锁,事务可以通过以下语句显式加共享锁或排他锁。
共享锁:SELECT … LOCK IN SHARE MODE;
排他锁:SELECT … FOR UPDATE;
1、表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2、行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
3、页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

2: Spring 、Spring Boot 和 Spring Cloud 的关系?
Spring 最初最核心的两大核心功能 Spring Ioc 和 Spring Aop 成就了 Spring,Spring 在这两大核心的功能上不断的发展,才有了 Spring 事务、Spring Mvc 等一系列伟大的产品,最终成就了 Spring 帝国,到了后期 Spring 几乎可以解决企业开发中的所有问题。microservice架构风格是一种将单个应用程序开发为一组小服务的方法,每个服务都在自己的进程中运行,并与轻量级机制(通常是HTTP资源API)进行通信。这些服务是围绕业务能力构建的,可以通过全自动部署机制独立部署。对这些服务的集中管理非常有限,这些服务可以用不同的编程语言编写,并使用不同的数据存储技术。
Spring Boot 是在强大的 Spring 帝国生态基础上面发展而来,发明 Spring Boot 不是为了取代 Spring ,是为了让人们更容易的使用 Spring 。
Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。
Spring Cloud 是为了解决微服务架构中服务治理而提供的一系列功能的开发框架,并且 Spring Cloud 是完全基于 Spring Boot 而开发,Spring Cloud 利用 Spring Boot 特性整合了开源行业中优秀的组件,整体对外提供了一套在微服务架构中服务治理的解决方案

3: 消息队列有什么优缺点;RabbitMQ有什么优缺点
优点:解耦、异步、削峰。
缺点:
  • 系统可用性降低


本来系统运行好好的,现在你非要加入个消息队列进去,那消息队列挂了,你的 系统不是呵呵了。因此,系统可用性会降低;
  • 系统复杂度提高


加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息 不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂 性增大。
  • 一致性问题


A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是, 要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了, 咋整?你这数据就不一致了。
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对 它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈 呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用, 还是得用的。

4: Spring容器启动过程有哪些步骤
  • 初始化一个ReaderScanner,Reader可以用来注册单个BeanDefinition,Scanner用来扫描得到BeanDefinition

  • 通过Reader把配置类注册为一个BeanDefinition

  • 调用refresh方法,开始启动Spring容器

  • 先获取一个Bean工厂

  • 预先往Bean工厂中添加一些Bean后置处理器,和一些单例bean,和一些其他的配置

  • 执行Bean工厂的后置处理器,这里会进行扫描,扫描bean和bean的后置处理器

  • 实例化bean的后置处理器并且排序,然后添加到Bean工厂中去

  • 初始化用来进行国际化的MessageSource

  • 初始化事件广播器

  • 注册事件监听器

  • 开始实例化非懒加载的单例bean

  • 发布ContextRefreshedEvent事件














5: Ribbon的负载均衡原理

  • Ribbon是Spring Cloud家族的开源项目,主要功能是提供客户端的软件负载均衡算法,实现服务高可用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法,将请求平摊的分配到多个服务上,从而达到系统的HA。
  • 需要注意的是:Ribbon并非直接通过DiscoveryClient从注册中心获取服务的可用提供者,而是通过ServerList<Server>从注册中心获取服务提供者,ServerList与DiscoveryClient不一样,ServerList不是Spring Cloud定义的接口,而是Ribbon定义的接口。
  • Ribbon自身的负载均衡算法
  • RoundRobinRule(轮询算法)
  • RandomRule(随机算法)
  • AvailabilityFilteringRule():会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
  • WeightedResponseTimeRule():根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule
  • RetryRule():先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
  • BestAviableRule():会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
  • ZoneAvoidanceRule():默认规则,符合判断server所在区域的性能和server的可用性选择服务器
  • 自定义负载均衡算法需要爱继承AbstractLoadBalanceRule类进行自定义算法实现。

    6: 为什么使用MQ;MQ的优点
  • 异步处理 - 相比于传统的串行、并行方式,提高了系统吞吐量。
  • 应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。
  • 流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请 求。
  • 日志处理 - 解决大量日志传输。
  • 消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通 讯。比如实现点对点消息队列,或者聊天室等。
    详答
  • 主要是:解耦、异步、削峰。
  • 解耦:A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要 这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃…A 系统 跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系 统都需要 A 系统将这个数据发送过来。如果使用 MQ,A 系统产生一条数据, 发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要 数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数 据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复 杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦。
  • 异步:A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、 200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户 感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求。如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从 接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms。
  • 削峰:减少高峰时期对服务器压力。


    7: B树定义
    B树和平衡二叉树稍有不同的是B树属于多叉树又名平衡多路查找树(查找路径 不只两个),不属于二叉搜索树的范畴,因为它不止两路,存在多路。
    B树满足的条件:
  • 树种的每个节点最多拥有m个子节点且m>=2,空树除外(注:m阶代表一个树节点最多有多少个查找路径,m阶=m路,当m=2则是2叉树,m=3则是3叉);
  • 除根节点外每个节点的关键字数量大于等于ceil(m/2)-1个小于等于m-1个,非根 节点关键字数必须>=2;(注:ceil()是个朝正无穷方向取整的函数 如ceil(1.1)结果为2)
  • 所有叶子节点均在同一层、叶子节点除了包含了关键字和关键字记录的指针外也 有指向其子节点的指针只不过其指针地址都为null对应下图后一层节点的空格子
  • 如果一个非叶节点有N个子节点,则该节点的关键字数等于N-1;
  • 所有节点关键字是按递增次序排列,并遵循左小右大原则;


  • B树的应用场景:构造一个多阶的B类树,然后在尽量多的在结点上存储相关的信息, 保证层数尽量的少,以便后面我们可以更快的找到信息,磁盘的I/O操作也少一些,而 且B类树是平衡树,每个结点到叶子结点的高度都是相同,这也保证了每个查询是稳定 的。

    8: Mybatis都有哪些Executor执行器?它们之间的区别是什么
  • Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、 BatchExecutor
  • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使 用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。简言之,就是重复使用Statement对象。
  • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行 executeBatch()批处理。与JDBC批处理相同。
  • 作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

    9: 什么是 java 序列化?什么情况下需要序列化
    简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
    什么情况下需要序列化
    a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
    b)当你想用套接字在网络上传送对象的时候;
    c)当你想通过RMI传输对象的时候;

    10: Spring Boot 中如何实现定时任务
    定时任务也是一个常见的需求,Spring Boot 中对于定时任务的支持主要还是来自 Spring 框架。
    在 Spring Boot 中使用定时任务主要有两种不同的方式,一个就是使用 Spring 中的 @Scheduled 注解,另一个则是使用第三方框架 Quartz
    使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现。
    使用 Quartz ,则按照 Quartz 的方式,定义 Job 和 Trigger 即可。

    11: JDK7中的ConcurrentHashMap是如何扩容的
    JDK7中的ConcurrentHashMap和JDK7的HashMap的扩容是不太一样的,首先JDK7中也是支持多线程扩容的,原因是,JDK7中的ConcurrentHashMap分段了,每一段叫做Segment对象,每个Segment对象相当于一个HashMap,分段之后,对于ConcurrentHashMap而言,能同时支持多个线程进行操作,前提是这些操作的是不同的Segment,而ConcurrentHashMap中的扩容是仅限于本Segment,也就是对应的小型HashMap进行扩容,所以是可以多线程扩容的。
    每个Segment内部的扩容逻辑和HashMap中一样。

    12: Spring Boot 有哪些优点
    Spring Boot 主要有如下优点:
    容易上手,提升开发效率,为 Spring 开发提供一个更快、更广泛的入门体验。
    开箱即用,远离繁琐的配置。
    提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。
    没有代码生成,也不需要XML配置。
    避免大量的 Maven 导入和各种版本冲突。

    13: MyBatis编程步骤是什么样的
    创建SqlSessionFactory
    通过SqlSessionFactory创建SqlSession
    通过sqlsession执行数据库操作
    调用session.commit()提交事务
    调用session.close()关闭会话

    14: 我们如何监视所有 Spring Boot 微服务
    Spring Boot 提供监视器端点以监控各个微服务的度量。这些端点对于获取有关应用程序的信息(如它们是否已启动)以及它们的组件(如数据库等)是否正常运行很有帮助。但是,使用监视器的一个主要缺点或困难是,我们必须单独打开应用程序的知识点以了解其状态或健康状况。想象一下涉及 50 个应用程序的微服务,管理员将不得不击中所有 50 个应用程序的执行终端。为了帮助我们处理这种情况,我们将使用位于的开源项目。它建立在 Spring Boot Actuator 之上,它提供了一个 Web UI,使我们能够可视化多个应用程序的度量。

    15: 如何处理缓存数据不一致和并发竞争
    1)数据不一致
    问题描述:
    同一份数据,可能会同时存在 DB 和缓存之中。那就有可能发生,DB 和缓存的数据不一致。如果缓存有多个副本,多个缓存副本里的数据也可能会发生不一致现象。
    业务场景:
    在缓存机器的带宽被打满,或者机房网络出现波动时,缓存更新失败,新数据没有写入缓存,就会导致缓存和 DB 的数据不一致。缓存 rehash 时,某个缓存机器反复异常,多次上下线,更新请求多次 rehash。这样,一份数据存在多个节点,且每次 rehash 只更新某个节点,导致一些缓存节点产生脏数据。
    解决方案:
    1.cache 更新失败后,可以进行重试,如果重试失败,则将失败的 key 写入队列机服务,待缓存访问恢复后,将这些 key 从缓存删除。这些 key 在再次被查询时,重新从 DB 加载,从而保证数据的一致性。
    2.缓存时间适当调短,让缓存数据及早过期后,然后从 DB 重新加载,确保数据的最终一致性。
    3.不采用 rehash 策略,而采用缓存分层策略,尽量避免脏数据产生。


    2)数据并发竞争

    问题描述:
    数据并发竞争,是指在高并发访问场景,一旦缓存访问没有找到数据,大量请求就会并发查询 DB,导致 DB 压力大增的现象。
    业务场景:
    购票系统,如果某个火车车次缓存信息过期,但仍然有大量用户在查询该车次信息。微博系统中,如果某条微博正好被缓存淘汰,但这条微博仍然有大量的转发、评论、赞。
    解决方案:
    1.使用全局锁。当缓存请求 miss 后,先尝试加全局锁,只有加全局锁成功的线程,才可以到 DB 去加载数据。其他进程/线程在读取缓存数据 miss 时,如果发现这个 key 有全局锁,就进行等待,待之前的线程将数据从 DB 回种到缓存后,再从缓存获取。
    2.对缓存数据保持多个备份,即便其中一个备份中的数据过期或被剔除了,还可以访问其他备份,从而减少数据并发竞争的情况。

    16: 微服务优缺点
    优点:
    每个微服务都很小,这样能聚焦一个指定的业务功能或业务需求。
    微服务能够被小团队单独开发。
    微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的。
    微服务能使用不同的语言开发。
    微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果。无需通过合作才能体现价值。
    微服务只是业务逻辑的代码,不会和HTML,CSS 或其他界面组件混合。
    缺点
    运维要求较高
    分布式的复杂性
    接口调整成本高

    17: 如何处理缓存失效、缓存穿透与缓存雪崩

    缓存失效

    问题描述:
    业务访问时,如果大量的 key 同时过期,很多缓存数据访问都会 miss,进而穿透到 DB,DB 的压力就会明显上升,由于 DB 的性能较差,只在缓存的 1%~2% 以下,这样请求的慢查率会明显上升。
    业务场景:
    同一批火车票售卖时,系统会一次性加载到缓存,如果缓存写入时,过期时间按照预先设置的过期值,那过期时间到期后,系统就会因缓存失效出现变慢的问题。新业务上线时,会进行缓存预热,也会一次性加载大批热数据。
    解决方案:
    设计缓存的过期时间时,使用公式:过期时间=base 时间+随机时间,让数据在未来一段时间内慢慢过期,避免瞬时全部过期,对 DB 造成过大压力


    缓存穿透

    问题描述:
    有特殊访客在查询一个不存在的 key,导致每次查询都会穿透到 DB,如果这个特殊访客再控制一批肉机,持续访问系统里不存在的 key,就会对 DB 产生很大的压力,从而影响正常服务。
    业务场景:
    通过不存在的 UID 访问用户,通过不存在的车次 ID 查看购票信息 
    解决方案:
    1.查询这些不存在的数据时,第一次查 DB,虽然没查到结果返回 NULL,仍然记录这个 key 到缓存,只是这个 key 对应的 value 是一个特殊设置的值。                                                   改进:设置很短的过期时间,将这些不存在的 key 存在一个独立的公共缓存。
    2.构建一个 BloomFilter 缓存过滤器,记录全量数据,这样访问数据时,可以直接通过 BloomFilter 判断这个 key 是否存在,如果不存在直接返回即可,根本无需查缓存和 DB。


    缓存雪崩

    问题描述:
    缓存雪崩是指部分缓存节点不可,导致整个缓存体系甚至服务系统不可用的情况。
    按照缓存是否 rehash分两种情况
    缓存不支持 rehash 导致的系统雪崩不可用
    缓存支持 rehash 导致的缓存雪崩不可用
    业务场景:
    微博、Twitter 在突发流量洪峰时crash。机架断电,导致业务缓存多个节点宕机,大量请求直接打到 DB,也导致 DB 过载而阻塞,整个系统异常。
    解决方案:
    1.对业务 DB 的访问增加读写开关,当发现 DB 请求变慢、阻塞,慢请求超过阀值时,就会关闭读开关,部分或所有读 DB 的请求进行 failfast 立即返回,待 DB 恢复后再打开读开关。(优先保证写,同时支持部分读)
    2.对缓存增加多个副本,缓存异常或请求 miss 后,再读取其他缓存副本,而且多个缓存副本尽量部署在不同机架,从而确保在任何情况下,缓存系统都会正常对外提供服务。
    3.对缓存体系进行实时监控,当请求访问的慢速比超过阀值时,及时报警,通过机器替换、服务替换进行及时恢复;也可以通过各种自动故障转移策略,自动关闭异常接口、停止边缘服务、停止部分非核心功能措施,确保在极端场景下,核心功能的正常运行。

    18: 如何使用 Spring Boot 实现分页和排序
    使用 Spring Boot 实现分页非常简单。使用 Spring Data-JPA 可以实现将可分页的传递给存储库方法。

    19:微服务中如何实现 session 共享 ?
    在微服务中,一个完整的项目被拆分成多个不相同的独立的服务,各个服务独立部署在不同的服务器上,各自的 session 被从物理空间上隔离开了,但是经常,我们需要在不同微服务之间共享 session ,常见的方案就是 SpringSession + Redis 来实现 session 共享。将所有微服务的 session 统一保存在 Redis 上,当各个微服务对 session 有相关的读写操作时,都去操作 Redis 上的 session 。这样就实现了 session 共享,Spring Session 基于 Spring 中的代理过滤器实现,使得 session 的同步操作对开发人员而言是透明的,非常简便。




    20: Hystrix断路器的实现原理
    Hystix是Spring Cloud家族产品组件之一,主要用来熔断降级以及资源隔离来保护系统免受级联故障影响。


  • Hystrix设计目标:
  1. 对来自依赖的延迟和故障进行防护和控制——这些依赖通常都是通过网络访问的
  2. 阻止故障的连锁反应
  3. 快速失败并迅速恢复
  4. 回退并优雅降级
  5. 提供近实时的监控与告警

  • Hystrix遵循的设计原则:
  1. 防止任何单独的依赖耗尽资源(线程)
  2. 过载立即切断并快速失败,防止排队
  3. 尽可能提供回退以保护用户免受故障
  4. 使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一个依赖的影响
  5. 通过近实时的指标,监控和告警,确保故障被及时发现
  6. 通过动态修改配置属性,确保故障及时恢复
  7. 防止整个依赖客户端执行失败,而不仅仅是网络通信

  • Hystrix如何实现这些设计目标?
  1. 使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行;
  2. 每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
  3. 记录请求成功,失败,超时和线程拒绝。
  4. 服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
  5. 请求失败,被拒绝,超时或熔断时执行降级逻辑。
  6. 近实时地监控指标和配置的修改。

Hystrix工作流程
1、创建HystrixCommand 或者 HystrixObservableCommand 对象
2、执行命令execute()、queue()、observe()、toObservable()
3、如果请求结果缓存这个特性被启用,并且缓存命中,则缓存的回应会立即通过一个Observable对象的形式返回
4、检查熔断器状态,确定请求线路是否是开路,如果请求线路是开路,Hystrix将不会执行这个命令,而是直接执行getFallback
5、如果和当前需要执行的命令相关联的线程池和请求队列,Hystrix将不会执行这个命令,而是直接执行getFallback
6、执行HystrixCommand.run()或HystrixObservableCommand.construct(),如果这两个方法执行超时或者执行失败,则执行getFallback()
7、Hystrix 会将请求成功,失败,被拒绝或超时信息报告给熔断器,熔断器维护一些用于统计数据用的计数器。
这些计数器产生的统计数据使得熔断器在特定的时刻,能短路某个依赖服务的后续请求,直到恢复期结束,若恢复期结束根据统计数据熔断器判定线路仍然未恢复健康,熔断器会再次关闭线路。

Hystrix断路器工作原理:
断路器开启或者关闭的条件:
     1、 当满足一定的阀值时候 (默认 10 秒内 超过 20 个请求 次数 )
     2、 当失败率达到一定的时候( 默认 10 秒内超过 秒内超过 50%的请求失败 )
     3、 到达以上阀值 ,断路器将会开启
     4、 当开启的时候 ,所有请求 都不会进行转发
     5、 一段时间之后( 默认是 5秒),这个时候断路器是半开状态, 会让其中一请求进行转发。如果成功断路器会关闭,若失败继续开启。重复 4和 5。

Hystrix特性
  • 请求熔断:当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN).这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
  • 服务降级:Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.告知后面的请求服务不可用了,不要再来了。
  • 依赖隔离(采用舱壁模式,Docker就是舱壁模式的一种):在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池.比如说,一个服务调用两外两个服务,你如果调用两个服务都用一个线程池,那么如果一个服务卡在哪里,资源没被释放后面的请求又来了,导致后面的请求都卡在哪里等待,导致你依赖的A服务把你卡在哪里,耗尽了资源,也导致了你另外一个B服务也不可用了。这时如果依赖隔离,某一个服务调用A B两个服务,如果这时我有100个线程可用,我给A服务分配50个,给B服务分配50个,这样就算A服务挂了,我的B服务依然可以用。
  • 请求缓存:比如一个请求过来请求我userId=1的数据,你后面的请求也过来请求同样的数据,这时我不会继续走原来的那条请求链路了,而是把第一次请求缓存过了,把第一次的请求结果返回给后面的请求。
  • 请求合并:我依赖于某一个服务,我要调用N次,比如说查数据库的时候,我发了N条请求发了N条SQL然后拿到一堆结果,这时候我们可以把多个请求合并成一个请求,发送一个查询多条数据的SQL的请求,这样我们只需查询一次数据库,提升了效率。

Hystix解决了什么问题?
复杂分布式体系结构中的应用程序通常有几十个依赖项,每个依赖项都不可避免地在某个时刻失败。如果主机应用程序没有与这些外部故障隔离开来,那么出现风险就有可能让整个集群服务崩溃
例如,对于一个依赖于30个服务的应用程序,其中每个服务的正常运行时间为99.99%,您可以期望:
99.9930=99.7%正常运行时间
10亿个请求中的0.3%=3000000个失败
即使所有依赖项都有很好的正常运行时间,每月停机2小时以上。
现实通常比这个情况更糟。
即使所有的依赖性都表现良好,如果您不设计整个系统的弹性,那么即使是0.01%的停机时间对几十个服务中的每一个服务的总影响也相当于一个月可能有几个小时的停机时间

熔断器有以下几个主要配置

21:说说你对 Java 注解的理解
注解是通过@interface 关键字来进行定义的,形式和接口差不多,只是前面多了一个@,比如:

public @interface TestAnnotation {

}

使用时@TestAnnotation来引用,要使注解能正常工作,还需要使用元注解,它是可以注解到注解上的注解。元标签有@Retention @Documented @Target @Inherited @Repeatable 五种

@Retention: 说明注解的存活时间,取值有 


RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时被丢弃;RetentionPolicy.CLASS 注解只保留到编译进行的时候,并不会 被 加 载 到 J V M 中 。R e t e n t i o n P o l i c y . R U N T I M E 可 以 留 到程 序 运 行 的 时 候 ,它 会 被 加 载 进 入 到 J V M 中,所以在程序运行时可以获取到它们。

@Documented:注解中的元素包含到 javadoc 中去

@Target:限定注解的应用场景

ElementType.FIELD 给属性进行注解; 

ElementType.LOCAL_VARIABLE 可以给局部变量进行注解;

ElementType.METHOD 可以给方法进行注解;

ElementType.PACKAGE 可以给一个包进行注解 

ElementType.TYPE 可以给一个类型进行注解,如类、接口、枚举

@Inherited 若一个超类被@Inherited注解过的注解进行注解,它的子类没有被任何注解应用的话,该子类就可继承超类的注解;

注解的作用:
1)提供信息给编译器:编译器可利用注解来探测错误和警告信息 

2)编译阶段:软件工具可以利用注解信息来生成代码、html 文档或做其它相应处理; 

3)运行阶段:程序运行时可利用注解提取代码

注解是通过反射获取的,可以通过 Class 对象的 isAnnotationPresent()方法判断它是否应用了某个注解,再通过 getAnnotation()方法获取 Annotation 对象


22:说一下泛型原理,并举例说明

泛型就是将类型变成参数传入,使得可以使用的类型多样化,从而实现解耦。

Java 泛型是在 Java1.5 以后出现的,为保持对以前版本的兼容,使用了擦除的方法实现泛型。擦除是指在一定程度无视类型参数T,直接从 T 所在的类开始向上T的父类去擦除,如调用泛型方法,传入类型参数 T 进入方法内部,若没在声明时做类似 

public T methodName(T extends Father t){

},Java就进行了向上类型的擦除,直接把参数t当做Object类来处理,而不是传进去的T。即在有泛型的任何类和方法内部,它都无法知道自己的泛型参数,擦除和转型都是在边界上发生,即传进去的参在进入类或方法时被擦除掉,但传出来的时候又被转成了我们设置的 T。在泛型类或方法内,任何涉及到具体类型(即擦除后的类型的子类)操作都不能进行,如 new T(),或者 T.play()(play 为某子类的方法而不是擦除后的类的方法)


23:Java 中 String 的了解
1)String 类是 final 型,固 String 类不能被继承,它的成员方法也都默认为 final 方法。String 对象一旦创建就固定不变了,对 String 对象的任何改变都不影响到原对象,相关的任何改变操作都会生成新的 String 对象。

2)String 类是通过 char 数组来保存字符串的,String 对 equals 方法进行了重定,比较的是值相等。

String a = "test"; 

String b = "test"; 

String c = new String("test");

a、b 和字面上的 test 都是指向 JVM 字符串常量池中的"test"对象,他们指向同一个对象。而 new 关键字一定会产生一个对象 test,该对象存储在堆中。所以 new String("test")产生了两个对象,保存在栈中的 c 和保存在堆中的 test。而在 java 中根本就不存在两个完全一模一样 的字符串对象,故在堆中的 test 应该是引用字符串常量池中的 test。

例:

String str1 = "abc"; 

//栈中开辟一块空间存放引用 str1,str1 指向池中 String 常量"abc"
String str2 = "def"; 

//栈中开辟一块空间存放引用 str2,str2 指向池中 String 常量"def"
String str3 = str1 + str2;

//栈中开辟一块空间存放引用 str3
//str1+str2 通过 StringBuilder 的最后一步 toString()方法返回一个新的 String 对象"abcdef" 

//会在堆中开辟一块空间存放此对象,引用 str3 指向堆中的(str1+str2)所返回的新 String 对象。System.out.println(str3 == "abcdef");//返回 false

因为 str3 指向堆中的"abcdef"对象,而"abcdef"是字符池中的对象,所以结果为 false。JVM 对 String str="abc"对象放在常量池是在编译时做的,而 String str3=str1+str2 是在运行时才知道的,new 对象也是在运行时才做的。


24:String 为什么要设计成不可变的?

1)字符串常量池需要 String 不可变。因为 String 设计成不可变,当创建一个 String 对象时,若此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。如果字符串变量允许必变,会导致各种逻辑错误,如改变一个对象会影响到另一个独立对象。

2)String 对象可以缓存 hashCode。字符串的不可变性保证了 hash 码的唯一性,因此可以缓存 String 的 hashCode,这样不用每次去重新计算哈希码。在进行字符串比较时,可以直接比较 hashCode,提高了比较性能;

3)安全性。String 被许多 java 类用来当作参数,如 url 地址,文件 path路径,反射机制所需的 Strign 参数等,若 String 可变,将会引起各种安全隐患。


25:BeanFactory 和 ApplicationContext 有什么区别

> BeanFactory 可以理解为含有 bean 集合的工厂类。BeanFactory 包含了种 bean 的定义, 以便在接收到客户端请求时将对应的 bean 实例化。
> BeanFactory 还能在实例化对象的时生成协作类之间的关系。此举将 bean 自身与 bean 客户端的配置中解放出来。BeanFactory 还包含了 bean 生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。

> 从表面上看,application context 如同 bean factory 一样具有 bean 定义、bean 关联关系的设置,根据请求分发 bean 的功能。但 application context 在此基础上还提供了其他的功能。
> 提供了支持国际化的文本消息

> 统一的资源文件读取方式
> 已在监听器中注册的 bean 的事件


26:Spring Bean 的生命周期

> Spring Bean 的生命周期简单易懂。在一个 bean 实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个 bean 不在被调用时需要进行相关的析构操作,并从 bean 容器中移除。
> Spring bean factory 负责管理在 spring 容器中被创建的 bean 的生命周期。

Bean 的生命周期由两组回调(call back)方法组成。

  1. 初始化之后调用的回调方法。

  2. 销毁之前调用的回调方法。

Spring 框架提供了以下四种方式来管理 bean 的生命周期事件: 

> InitializingBean 和 DisposableBean 回调接口
> 针对特殊行为的其他 Aware 接口
> Bean 配置文件中的 Custom init()方法和 destroy()方法
> @PostConstruct 和@PreDestroy 注解方式


27:Spring IOC 如何实现

> Spring 中的 org.springframework.beans 包和 org.springframework.context 包构成了 Spring 框架 IoC 容器的基础。
> BeanFactory 接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。 ApplicationContex 接口对 BeanFactory(是一个子接口)进行了扩展,在 BeanFactory 的基础上添加了其他功能,比如与 Spring 的 AOP更容易集成,也提供了处理 message resource 的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对 Web 应用的WebApplicationContext。

> org.springframework.beans.factory.BeanFactory 是Spring IoC容器的具体实现,用来包装和管理前面提到的各种 bean。BeanFactory 接口是 Spring IoC 容器的核心接口。


28:说说 Spring AOP

> 面向切面编程,在我们的应用中,经常需要做一些事情,但是这些事情与核心业务无关,比如,要记录所有 update*方法的执行时间时间,操作人等等信息,记录到日志

> 通过 spring 的 AOP 技术,就可以在不修改 update*的代码的情况下完成该需求。


29:Spring AOP 实现原理

> Spring AOP 中的动态代理主要有两种方式,JDK 动态代理和 CGLIB 动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是 InvocationHandler 接口和 Proxy 类。
> 如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。CGLIB (Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final, 那么它是无法使用 CGLIB 做动态代理的。


30:动态代理(cglib 与 JDK)

> JDK 动态代理类和委托类需要都实现同一个接口。也就是说只有实现了某个接口的类可以使用 Java 动态代理机制。但是,事实上使用中并不是遇到的所有类都会给你实现一个接口。因此,对于没有实现接口的类,就不能使用该机制。而 CGLIB 则可以实现对类的动态代理。


31:Spring 事务实现方式

> 1、编码方式
所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理
> 2、声明式事务管理方式
声明式事务管理又有两种实现方式:

基于 xml 配置文件的方式;

另一个实在业务方法上进行@Transaction 注解,将事务规则应用到业务逻辑中


32:Spring事务底层原理

> a、划分处理单元——IOC
由于 spring 解决的问题是对单个数据库进行局部事务处理的,具体的实现首先用 spring 中的 IOC 划分了事务处理单元。并且将对事务的各种配置放到了 ioc 容器中(设置事务管理器,设置事务的传播特性及隔离机制)。
> b、AOP 拦截需要进行事务处理的类
Spring 事务处理模块是通过 AOP 功能来实现声明式事务处理的,具体操作(比如事务实行的配置和读取,事务对象的抽象),用 TransactionProxyFactoryBean 接口来使用 AOP 功能,生成 proxy 代理对象,通过 TransactionInterceptor 完成对代理方法的拦截,将事务处理的功能编织到拦截的方法中。读取 ioc 容器事务配置属性,转化为 spring 事务处理
需要的内部数据结构(TransactionAttributeSourceAdvisor),转化为 TransactionAttribute 表示的数据对象。

> c、对事物处理实现(事务的生成、提交、回滚、挂起)
spring 委托给具体的事务处理器实现。实现了一个抽象和适配。适配的具体事务处理器:DataSource 数据源支持、hibernate 数据源事务处理支持、JDO 数据源事务处理支持,JPA、JTA 数据源事务处理支持。这些支持都是通过设计 PlatformTransactionManager、AbstractPlatforTransaction 一系列事务处理的支持。 为常用数据源支持提供了一系列的 TransactionManager。

> d、结合
> PlatformTransactionManager 实现了 TransactionInterception 接口,让其与 TransactionProxyFactoryBean 结合起来,形成一个Spring 声明式事务处理的设计体系。


33:如何自定义注解实现功能

> 创建自定义注解和创建一个接口相似,但是注解的 interface 关键字需要以@符号开头。 

> 注解方法不能带有参数;
> 注解方法返回值类型限定为:基本类型、String、Enums、Annotation 或者是这些类型的数组;

> 注解方法可以有默认值;
> 注解本身能够包含元注解,元注解被用来注解其它注解


34:Spring MVC 运行流程

1.spring mvc 将所有的请求都提交给 DispatcherServlet,它会委托应用系统的其他模 负责对请求 进行真正的处理工作。
2.DispatcherServlet 查询一个或多个 HandlerMapping,找到处理请求的 Controller.
3.DispatcherServlet 请请求提交到目标 Controller

4.Controller 进行业务逻辑处理后,会返回一个 ModelAndView
5.Dispathcher 查询一个或多个 ViewResolver 视图解析器,找到 ModelAndView 对象指定 的视图对象
6.视图对象负责渲染返回给客户端。


35:Spring MVC 启动流程

> 在 web.xml 文件中给 Spring MVC 的 Servlet 配置了 load-on-startup,所以程序启动的时候会初始化 Spring MVC,在 HttpServletBean 中将配置的 contextConfigLocation 属性设置到 Servlet 中,然后在 FrameworkServlet 中创建了 WebApplicationContext

> DispatcherServlet 根据 contextConfigLocation 配置的 classpath 下的 xml 文件初始化了Spring MVC 总的组件。


36:Spring 的单例实现原理

> Spring 对 Bean 实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是 ConcurrentHashMap 对象。


37:Spring 框架中用到了哪些设计模式

> 代理模式—在 AOP 和 remoting 中被用的比较多。
> 单例模式—在 spring 配置文件中定义的 bean 默认为单例模式。
> 模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
> 前端控制器—Spring 提供了 DispatcherServlet 来对请求进行分发。
> 视图帮助(View Helper )—Spring 提供了一系列的 JSP 标签,高效宏来辅助将分散的代码整合在视图里。
> 依赖注入—贯穿于 BeanFactory / ApplicationContext 接口的核心理念。
> 工厂模式—BeanFactory 用来创建对象的实例。


38:为什么选择 Netty

1) API 使用简单,开发门槛低;
2) 功能强大,预置了多种编解码功能,支持多种主流协议;
3) 定制能力强,可以通过 ChannelHandler 对通信框架进行灵活的扩展;
4) 性能高,通过与其它业界主流的 NIO 框架对比,Netty 的综合性能最优;
5) 成熟、稳定,Netty 修复了已经发现的所有 JDK NIO BUG,业务开发人员不需要再为 NIO 的 BUG 而烦恼;
6) 社区活跃,版本迭代周期短,发现的 BUG 可以被及时修复,同时,更多的新功能会被 加入;
7) 经历了大规模的商业应用考验,质量已经得到验证。在互联网、大数据、网络游戏、 企业应用、电信软件等众多行业得到成功商用,证明了它可以完全满足不同行业的商业应 用。
正是因为这些优点,Netty 逐渐成为 Java NIO 编程的首选框架。

Netty 线程模型

> 首先,Netty 使用 EventLoop 来处理连接上的读写事件,而一个连接上的所有请求都保证在一个 EventLoop 中被处理,一个 EventLoop 中只有一个 Thread,所以也就实现了一个连接上的所有事件只会在一个线程中被执行。一个EventLoopGroup 包含多个 EventLoop,可以把一个 EventLoop 当做是 Reactor 线程模型中的一个线程,而一个 EventLoopGroup 类似于一个 ExecutorService

说说 Netty 的零拷贝

>“零拷贝”是指计算机操作的过程中,CPU 不需要为数据在内存之间的拷贝消耗资源。而它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。

Netty 内部执行流程

1. Netty的接收和发送ByteBuffer采用DIRECTBUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。

2. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个 Buffer 那样方便的对组合 Buffer 进行操作,避免了传统通过内存拷贝的方式将几个小 Buffer 合并成一个大的 Buffer。
3. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到


39:说说业务中,Netty 的使用场景

> 构建高性能、低时延的各种 Java 中间件,例如 MQ、分布式服务框架、ESB 消息总线等, Netty 主要作为基础通信框架提供高性能、低时延的通信服务;
> 公有或者私有协议栈的基础通信框架,例如可以基于 Netty 构建异步、高性能的 WebSocket 协议栈;

> 各领域应用,例如大数据、游戏等,Netty 作为高性能的通信框架用于内部各模块的数据分发、传输和汇总等,实现模块之间高性能通信。

原生的 NIO 在 JDK 1.7 版本存在 epoll bug

> 它会导致 Selector 空轮询,最终导致 CPU 100%。官方声称在 JDK 1.6 版本的 update18 修复了该问题,但是直到 JDK 1.7 版本该问题仍旧存在,只不过该 BUG 发生概率降低了一些 而已,它并没有得到根本性解决。


40:TCP

什么是 TCP 粘包/拆包

1、要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于 MSS(最大报文长度),TCP 在传输前将进行拆包。
3、要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

TCP 粘包/拆包的解决办法

1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、发送端将每个数据包封装为固定长度(不够的可以通过补 0 填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

文章转载自萌小璐,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论