栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

八股文整理笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

八股文整理笔记

面试问题
  • 一、Java基础
    • 1.jdk1.7到jdk1.8HashMap发生了什么变化(底层)?
    • 2.jdk1.7到jdk1.8虚拟机发生了什么变化
    • 3.String、StringBuilder、StringBuffer
    • 4.ArrayList和linkedList有什么区别?
    • 5.ConcurrentHashMap的扩容机制
    • 6.接口和抽象类的区别
    • 7.CopyOnWriteArrayList的底层原理是怎样的
  • 二、Redis
    • 1.Redis为什么这么快
    • 2.Redis分布式锁底层是如何实现的?
    • 3.redis的持久化机制
    • 4.Redis的过期键的删除策略
    • 5.缓存雪崩、缓存穿透、缓存击穿
    • 6.redis主从复制的核心原理
    • 7.redis数据结构
    • 8.如何保证数据库与缓存的一致性?
    • 9.Redis有哪些数据结构?分别有哪些典型的应用场景?
    • 10.redis集群方案
  • 三、MySql
    • 1.索引的基本原理
    • 2.怎么处理慢查询
    • 3.事务的基本特性和隔离级别
    • 4.最左前缀原则是什么
    • 5.什么是MVCC
    • 6.B树和B+树的区别,为什么Mysql使用B+树
    • 7.Mysql慢查询该如何优化?
    • 8.简述MyISAM和InnoDB的区别
    • 9.mysql主从同步原理
    • 10.锁的类型有哪些
  • 四、JVM
    • 1.说一下JVM中,哪些是共享区,哪些可以作为gc root
    • 2.你们项目如何排查JVM问题
    • 3.GC如何判断对象可以被回收
    • 4.JVM内存模型
    • 5.说说类加载器双亲委派模型
    • 6.Java的内存结构,堆分为哪几部分,默认年龄多大进入老年代
    • 7.什么是字节码?采用字节码的好处是什么?
    • 8.六、如何进行JVM调优?JVM参数有哪些?怎么查看一个JAVA进的JVM参数?
  • 五、Java并发
    • 1.并发的三大特性
    • 2.ThreadLocal内存泄露原因,如何避免
    • 3.ThreadLocal的原理和使用场景
    • 4.ThreadLocal的底层原理
    • 5.ReentrantLock中的公平锁和非公平锁的底层实现
    • 6.volatile关键字,他是如何保证可见性,有序性
    • 7.如果你提交任务时,线程池队列已满,这时会发生什么
    • 8.sychronized的自旋锁、偏向锁、轻量级锁、重量级锁,分别介绍和联系
    • 9.简述线程池原理,FixedThreadPool用的阻塞队列是什么
    • 10.JAVA如何开启线程?怎么保证线程安全?
    • 11.谈谈你对AOS的理解。AOS如何实现可重入锁?
  • 六、分布式
    • 1.CAP理论,base理论
    • 2.dubbo集群容错策略有哪些
    • 3.Dubbo支持哪些负载均衡策略
    • 4.Dubbo是如何完成服务导出的?
    • 5.Dubbo是如何完成服务引入的?
    • 6.session的分布式方案
    • 7.Spring Cloud和Dubbo的区别
    • 8.zk的watch机制实现原理
    • 9.Zookeeper中的领导者选举的流程是怎样的?
    • 10.说说你了解的分布式锁实现
    • 11.分布式锁的使用场景是什么?有哪些实现方案?
    • 12.如何理解RPC
    • 13.简述zk的命名服务、配置管理、集群管理
  • 七、微服务
    • 1.SOA、分布式、微服务之间有什么关系和区别?
    • 2.Spring Cloud有哪些常用组件,作用是什么?
    • 3.Spring Cloud核心组件及其应用
    • 4.高并发场景下如何实现系统限流?
    • 5.什么是Hystrix?简述实现机制
    • 6.什么是服务熔断?什么是服务降级?区别是什么?
    • 7.什么是服务雪崩?什么是服务限流?
    • 8.你的项目中是怎么保证微服务敏捷开发的?微服务的链路追踪、持续集成、AB发布要怎么做?
    • 9.谈谈你对微服务的理解,微服务有哪些优缺点?
  • 八、消息队列
    • 1.Kafka、ActiveMQ、RabbitMQ、RocketMQ对比
    • 2.Kafka的Pull和Push分别有什么优缺点
    • 3.RocketMQ的底层实现原理
    • 4.Kafka消息丢失的场景及解决方案
    • 5.使用MQ如何保证分布式事务的最终一致性?
    • 6.如何保证消息的高效读写?
    • 7.MQ如何保证消息顺序
    • 8.让你设计一个MQ,你会如何设计?
    • 9.RabbitMQ架构设计
  • 九、计算机网络
    • 1.BIO、NIO、AIO分别是什么
    • 2.Netty的线程模型是怎么样的
    • 3.零拷贝是什么
    • 4.TCP的三次握手和四次挥手
    • 5.五、描述下HTTP和HTTPS的区别。
    • 6.HTTPS是如何保证安全传输的
    • 7.跨域请求是什么?有什么问题?怎么解决?
    • 8.什么是SSO?与OAuth2.0有什么关系?
    • 9.浏览器发出一个请求到收到响应经历了哪些步骤?
    • 10.epoll和poll的区别
  • 十、Spring相关
    • 1.简述Mybatis的插件运行原理,如何编写一个插件。
    • 2.Mybatis存在哪些优点和缺点
    • 3.Spring Boot、Spring MVC和Spring有什么区别
    • 4.Spring Boot自动配置原理?
    • 5.Spring MVC的主要组件?
    • 6.SpringMVC中的控制器是不是单例模式?如果是,如何保证线程安全?
    • 7.Spring框架中的单例Bean是线程安全的么?
    • 8.如何实现一个IOC容器

一、Java基础 1.jdk1.7到jdk1.8HashMap发生了什么变化(底层)?

1.1.7中底层是数组+链表,1.8中底层是数组+链表+红黑树(数组的默认初始容量是16、扩容因子为0.75,每次采用2倍的扩容。也就是说,每当我们数组中的存储容量达到75%的时候,就需要对数组容量进行2倍的扩容。链表长度大于等于8时会树化,小于6时进行链化。),加入红黑树的目的是提高HashMap插入和查询整体效率。
2.1.7中链表插入使用的是头插法,1.8中链表插入使用的是尾插法,因为1.8中插入key和value时需要判断链表元素个数,所以需要遍历链表统计元素的个数,所以正好就直接使用尾插法。
3.1.7中哈希算法比较复杂,存在各种右移与异或运算,1.8中进行了简化,因为复杂的哈希算法的目的就是提高散列性,来提供HashMap的整体效率,而1.8中新增了红黑树,所以可以适当简化哈希算法,节省CPU资源。

2.jdk1.7到jdk1.8虚拟机发生了什么变化

1.7中存在永久代,1.8中没有永久代,替换它的是元空间,元空间所占的内存不是虚拟机内部,而是本地内存空间,这么做的原因是,不管永久代还是元空间,他们都是方法区的具体实现,之所以元空间的内存改成本地内存,官方的说法是为了和JRockit统一,不过额外 还有一些原因,比如方法区所存储的类信息通常是比较难确定的,所以对于方法区的大小是比较难指定的,太小了容易出现方法区溢出,太大了又会占用太多虚拟机的空间,而转移到本地内存后则不会影响虚拟机所占用的内存。

3.String、StringBuilder、StringBuffer

String是final修饰的,不可变的,每次操作都会产生新的String对象
StringBuffer和StringBuilder都是在原对象上操作
StringBuffer是线程安全的,StringBuilder是线程不安全的
StringBuffer方法都是synchronized修饰的
性能:StringBuilder>StringBuffer>String

场景:经常需要改变字符串内容时使用StringBuilder、StringBuffer
优先使用StringBuilder,多线程使用共享变量时使用StringBuffer

4.ArrayList和linkedList有什么区别?

1.ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,linkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
2. 相对于ArrayList,linkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
3. linkedList比ArrayList更占内存,因为linkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
4. ArrayList和linkedList都是先了List接口,但是linkedList还额外实现了Deque接口

5.ConcurrentHashMap的扩容机制
  • 1.7版本
    1.1.7版本的ConcurrentHashMap是基于Segment分段实现的
    2.每个Segment相对于一个小型的HashMap
    3.每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
    4.先生成新的数组,然后转移元素到新数组中
    5.扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值
  • 1.8版本
    1.1.8版本的ConcurrentHashMap不再基于Segment实现
    2.当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容
    3.如果某个线程put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
    4.ConcurrentHashMap是支持多个线程同时扩容的
    5.扩容之前也先生成一个新的数组
    6.在转移元素时,先将原数组分组,将每组分给不同的线程来进行元素的转移,每个线程负责一组或多组的元素转移工作
6.接口和抽象类的区别
  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法。
  • 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的。
  • 抽象类只能继承一个,接口可以实现多个。

接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。

而抽象类的设计目的,是代码复用。当不同的类具有某些相同的行为(记为行为集合A),且其中一部分行为的实现方式一致时(A的非真子集,记为B),可以让这些类都派生于一个抽象类。在这个抽象类中实现了B,避免让所有的子类来实现B,这就达到了代码复用的目的。而A减B的部分,留给各个子类自己实现。正是因为A-B在这里没有实现,所以抽象类不允许实例化出来(否则当调用到A-B时,无法执行)。

抽象类是对类本质的抽象,表达的是 is a的关系,比如:BMw is a car。抽象类包含并实现子类的通用特性,将子类存在差异化的特性进行抽象,交由子类去实现。
而接口是对行为的抽象,表达的是like a的关系。比如:Bird like a Aircraft(像飞行器一样可以飞),但其本质上is a Bird。接口的核心是定义行为,即实现类可以做什么,至于实现类主体是谁、是如何实现的,接口并不关心。

使用场景:当你关注一个事物的本质的时候,用抽象类;当你关注一个操作的时候,用接口。

抽象类的功能要远超过接口,但是,定义抽象类的代价高。因为高级语言来说(从实际设计上来说也是)每个类只能继承一个类。在这个类中,你必须继承或编写出其所有子类的所有共性。虽然接口在功能上会弱化许多,但是它只是针对一个动作的描述。而且你可以在一个类中同时实现多个接口。在设计阶段会降低难度

7.CopyOnWriteArrayList的底层原理是怎样的

1.首先CopyonwriteArrayList内部也是用过数组来实现的,在向CopyonwriteArrayList添加元素时,会复制一个新的数组,写操作在新数组上进行,读操作在原数组上进行
2.并且,写操作会加锁,防止出现并发写入丢失数据的问题
3.写操作结束之后会把原数组指向新数组
4.CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占内存,同时可能读到的数据不是实时最新的数据,所以不适合实时性要求很高的场景

二、Redis 1.Redis为什么这么快

1.基于内存:Redis是使用内存存储,没有磁盘IO上的开销。数据存在内存中,读写速度快。
2.单线程实现( Redis 6.0以前):Redis使用单个线程处理请求,避免了多个线程之间线程切换和锁资源争用的开销。
3.IO多路复用模型:Redis 采用 IO 多路复用技术。Redis 使用单线程来轮询描述符,将数据库的操作都转换成了事件,不在网络I/O上浪费过多的时间。
4.高效的数据结构:Redis 每种数据类型底层都做了优化,目的就是为了追求更快的速度。

2.Redis分布式锁底层是如何实现的?

1.首先利用setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
2.然后还要利用lua脚本来保证多个redis操作的原子性
3.同时还要考虑到锁过期,所以需要额外的一个看门狗定时任务来监听锁是否需要续约
4.同时还要考虑到redis节点挂掉后的情况,所以需要采用红锁的方式来同时向N/2+1个节点申请锁,都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到

3.redis的持久化机制

RDB:Redis Database 将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘。
手动触发:
1.save命令,使Redis处于阻塞状态,直到RDB持久化完成,才会响应其他客户端发来的命令,所以在生产环境一定要慎用
2.bgsave命令,fork出一个子进程执行持久化,主进程只在fork过程中有短暂的阻塞,子进程创建之后,主进程就可以响应客户端请求了
自动触发:
1.save m n:在m秒内,如果有n个键发生改变,则自动触发持久化,通过bgsave执行,如果设置多个、只要满足其一就会触发,配置文件有默认配置(可以注释掉)
2.flushall:用于清空redis所有的数据库,flushdb清空当前redis所在库数据(默认是0号数据库),会清空RDB文件,同时也会生成dump.rdb、内容为空
3.主从同步+全量同步时会自动触发bgsave命令,生成rdb发送给从节点
优点:
1、整个Redis数据库将只包含一个文件dump.rdb,方便持久化。
2、容灾性好,方便备份。
3、性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何lO操作,保证了redis的高性能
4.相对于数据集大时,比AOF的启动效率更高。
缺点:
1、数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这
种方式更适合数据要求不严谨的时候)
2、由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。会占用cpu

AOF:Append only File 以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录,调操作系统命令进程刷盘
1、所有的写命令会追加到AOF缓冲中。
2、AOF缓冲区根据对应的策略向硬盘进行同步操作。
3、随着AF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
4、当Redis重启时,可以加载AOF文件进行数据恢复。
同步策略:
1.每秒同步:异步完成,效率非常高,一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失
2.每修改同步:同步持久化,每次发生的数据变化都会被立即记录到磁盘中,最多丢一条
3.不同步:由操作系统控制,可能丢失较多数据
优点:
1、数据安全
2、通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof 工具
解决数据一致性问题。
3、AOF机制的rewrite模式。定期对AOF文件进行重写,以达到压缩的目的
缺点:
1、AOF文件比RDB文件大,且恢复速度慢。
2、数据集大的时候,比rdb启动效率低。
3、运行效率没有RDB高

两者相比:
AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF

4.Redis的过期键的删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

Redis中同时使用了惰性过期和定期过期两种过期策略。

5.缓存雪崩、缓存穿透、缓存击穿

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
3.缓存预热
4.互斥锁

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:
1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=o的直接拦截;
2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
3.采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
1.设置热点数据永远不过期。
2.加互斥锁

6.redis主从复制的核心原理

通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
全量复制:
(1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的
(2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗
(3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗
部分复制:
1.复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
2.复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列作为复制积压缓冲区,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
3.服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。从节点Redis断开重连的时候,就是根据运行ID来
判断同步的进度:
如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

7.redis数据结构

String:字符串
List:列表
Hash:哈希表
Set:无序集合
Sorted Set:有序集合
bitmap:布隆过滤器
GeoHash:坐标,借助Sorted Set实现,通过zset的score进行排序就可以得到坐标附近的其它元素,通过将score还原成坐标值就可以得到元素的原始坐标
HyperLogLog:统计不重复数据,用于大数据基数统计
Streams:内存版的kafka

8.如何保证数据库与缓存的一致性?

由于缓存和数据库是分开的,无法做到原子性的同时进行数据修改,可能出现缓存更新失败,或者数据库更新失败的情况,这时候会出现数据不一致,影响前端业务
1.先更新数据库,再更新缓存。缓存可能更新失败,读到老数据
2.先删缓存,再更新数据库。并发时,读操作可能还是会将旧数据读回缓存
3.先更新数据库,再删缓存。也存在缓存删除失败的可能

最经典的缓存+数据库读写的模式,Cache Aside Patten。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存。

为什么是删除而不是更新?
删除更加轻量,延迟加载的一种实现,更新可能涉及多个表、比较耗时

延时双删:先删除缓存,再更新数据库,休眠1s、再次删除缓存。写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据,并发还是可能读到旧值覆盖缓存

终极方案:
将访问操作串行化
1.先删缓存,将更新数据库的操作放进有序队列中
2.从缓存查不到的查询操作,都进入有序队列
会面临的问题:
1.读请求积压,大量超时,导致数据库的压力:限流、熔断
2.如何避免大量请求积压:将队列水平拆分,提高并行度。
3.保证相同请求路由正确

9.Redis有哪些数据结构?分别有哪些典型的应用场景?

Redis的数据结构有:
1.字符串:可以用来做最简单的数据,可以缓存某个简单的字符串,也可以缓存某个json格式的字符串,Redis分布式锁的实现就利用了这种数据结构,还包括可以实现计数器、Session共享、分布式ID
2.哈希表:可以用来存储一些key-value对,更适合用来存储对象
3.列表:Redis的列表通过命令的组合,既可以当做栈,也可以当做队列来使用,可以用来缓存类似微信公众号、微博等消息流数据
4.集合:和列表类以,也可以存储多个元素,但是不能重复,集合可以进行交集、并集、差集操作,从而可以实现类似,我和某人共同关注的人、朋友圈点赞等功能
5.有序集合:集合是无序的,有序集合可以设置顺序,可以用来实现排行榜功能

10.redis集群方案

主从
哨兵模式:
sentinel,哨兵是redis集群中非常重要的一个组件,主要有以下功能:

  • 集群监控:负责监控redis master和slave 进程是否正常工作。
  • 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
  • 故障转移:如果master node 挂掉了,会自动转移到slave node 上。
  • 配置中心:如果故障转移发生了,通知client客户端新的master地址。

哨兵用于实现redis集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。

  • 故障转移时,判断一个master node是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
  • 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
  • 哨兵通常需要3个实例,来保证自己的健壮性。
  • 哨兵+redis主从的部署架构,是不保证数据零丢失的,只能保证redis集群的高可用性。
  • 对于哨兵+redis主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。

Redis Cluster是一种服务端Sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行

方案说明

  • 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384个槽位
  • 每份数据分片会存储在多个互为主从的多节点上
  • 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
  • 同一分片多个节点间的数据不保持强一致性
  • 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
  • 扩容时需要需要把旧节点的数据迁移一部分到新节点

在redis cluster架构下,每个redis 要放开两个端口号,比如一个是6379,另外一个就是加1w的端口号,比如16379。
16379端口号是用来进行节点间通信的,也就是cluster bus的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus用了另外一种二进制的协议,gossip协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

优点

  • 无中心架构,支持动态扩容,对业务透明
  • 具备Sentinel的监控和自动Failover(故障转移)能力
  • 客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
  • 高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点

  • 运维也很复杂,数据迁移需要人工干预
  • 只能使用0号数据库
  • 不支持批量操作(pipeline管道操作)
  • 分布式逻辑和存储模块耦合等

Redis Sharding是Redis Cluster出来之前,业界普遍使用的多Redis实例集群方法。其主要思想是采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上。Java redis客户端驱动jedis,支持Redis Sharding功能,即Shardedjedis以及结合缓存池的ShardedjedisPool
优点
优势在于非常简单,服务端的Redis实例彼此独立,相互无关联,每个Redis实例像单服务器一样运行,非常容易线性扩展,系统的灵活性很强
缺点
由于sharding处理放到客户端,规模进一步扩大时给运维带来挑战。
客户端sharding不支持动态增删节点。服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化

三、MySql 1.索引的基本原理

索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
素引的原理:就是把无序的数据变成有序的查询
1.把创建了索引的列的内容讲行排序
2.对排序结果生成倒排表
3.在倒排表内容上拼上数据地址链
4.在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

2.怎么处理慢查询

关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

在业务系统中,除了使用主键进行查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。

慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
1.首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
2.分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
3.如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

3.事务的基本特性和隔离级别

事务基本特性ACID分别是:
原子性指的是一个事务中的操作要么全部成功,要么全部失败。
一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。

隔离性有4个隔离级别,分别是:
1.read uncommit读未提交,可能会读到其他事务未提交的数据,也叫做脏读。
用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。
2.read commit 读已提交,两次读取结果不一致,叫做不可重复读。不可重复读解决了脏读的问题,他只会读取已经提交的事务。用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。
3.repeatable read可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
4.serializable串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。

4.最左前缀原则是什么

当一个SQL想要利用索引时,就一定要提供该索引所对应的字段中最左边的字段,也就是排在最前面的字段,比如针对a,b,c三个字段建立了一个联合索引,那么在写一个sql时就一定要提供a字段的条件,这样才能用到联合索引,这是由于在建立a,b,c三个字段的联合索引时,底层的B+树是按照a,b,c三个字段从左往右去比较大小进行排序的,所以如果想要利用B+树进行快速查找也得符合这个规则

5.什么是MVCC

多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据,版本链

MVCC只在READ COMMITTED和REPEATABLE READ两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。

聚簇索引记录中有两个必要的隐藏列:
trx_id:用来存储每次对某条聚簇索引记录进行修改的时候的事务id。
roll_pointer:每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)

已提交读和可重复读的区别就在于它们生成ReadView的策略不同。

开始事务时创建readview,readView维护当前活动的事务id,即未提交的事务id,排序生成一个数组

访问数据,获取数据中的事务id(获取的是事务id最大的记录),对比readview:

如果在readview的左边(比readview都小),可以访问(在左边意味着该事务已经提交)

如果在readview的右边(比readview都大)或者就在readview中,不可以访问,获取roll_pointer,取上一版本重新对比(在右边意味着,该事务在readview生成之后出现,在readview中意味着该事务还未提交)

已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。

这就是Mysql的MVCC,通过版本链,实现多版本,可并发读-写,写-读。通过ReadView生成策略的不同实现不同的隔离级别。

6.B树和B+树的区别,为什么Mysql使用B+树

B树的特点:
1.节点排序
2.一个节点了可以存多个元素,多个元素也排序了

B+树的特点:
1.拥有B树的特点
2.叶子节点之间有指针
3.非叶子节点上的元素在叶子节点上都冗余了,也就是叶子节点中存储了所有的元素,并且排好顺序

Mysql索引使用的是B+树,因为索引是用来加快查询的,而B+树通过对数据进行排序所以是可以提高查询速度的,然后通过一个节点中可以存储多个元素,从而可以使得B+树的高度不会太高,在MysqI中一个Innodb页就是一个B+树节点,一个Innodb页默认16kb,所以一般情况下一颗两层的B+树可以存2000行左右的数据,然后通过利用B+树叶子节点存储了所有数据并且进行了排序,并且叶子节点之间有指针,可以很好的支持全表扫描,范围查找等SQL语句。

7.Mysql慢查询该如何优化?

1.检查是否走了索引,如果没有则优化SQL利用索引
2.检查所利用的索引,是否是最优索引
3.检查所查字段是否都是必须的,是否查询了过多字段,查出了多余数据
4.检查表中数据是否过多,是否应该进行分库分表了
5.检查数据库实例所在机器的性能配置,是否太低,是否可以适当增加资源

8.简述MyISAM和InnoDB的区别

MyISAM:
不支持事务,但是每次查询都是原子的:
支持表级锁,即每次操作暴对整个表加锁;

存储表的总行数:
一个MYISAM表有三个文件:索引文件、表结构文件、数据文件:
采用非聚集索引,索引文件的数据城存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。

InnoDb:
支持ACID的事务,支持事务的四种隔离级别;
支持行级锁及外键约束:因此可以支持写并发;

不存储总行数:
一个innoDb引学存储在一个文件空问(共享表空问,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;

9.mysql主从同步原理

mysql主从同步的过程:
Mysql的主从复制中主要有三个线程:master (binlog dump thread)、s1ave (I/o thread、SQL thread),Master一条线程和Slave中的两条线程。
1.主节点binlog,主从复制的基础是主库记录数据库的所有变更记录到binlog。binlog是数据库服务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件。
2.主节点log dump线程,当binlog有变动时,log dump线程读取其内容并发送给从节点。
3.从节点I/o线程接收binlog内容,并将其写入到relay log文件中。
4.从节点的SQL线程读取relay log文件内容对数据更新进行重放,最终保证主从数据库的一致性。

注:主从节点使用binglog文件+position偏移量来定位主从同步的位置,从节点会保存其已接收到的偏移量,如果从节点发生宕机重启,则会自动从position的位置发起同步。由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。

全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。

半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。

10.锁的类型有哪些

基于锁的属性分类:共享锁、排他锁。
基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎)、记录锁、间隙锁、临键锁。
基于锁的状态分类:意向共享锁、意向排它锁。

共享锁(Share Lock)
共享锁又称读锁,简称s锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。

排他锁(eXclusive Lock)
排他锁又称写锁,简称x锁;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取。避免了出现脏数据和脏读的问题。

表锁
表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;

行锁
行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问;
特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高;

记录锁(Record Lock)
记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录。精准条件命中,并且命中的条件字段是唯一索引
加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。

页锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。

特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

间隙锁(Gap Lock)
属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。

范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE_READ(重复读)的事务级别中。

触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事务里,A事务的两次查询出的结果会不一样。

比如表里面的数据ID为1,4,5,7,10,那么会形成以下几个间隙区间,-n-1区间,1-4区间,7-10区间,10-n区间(-n代表负无穷大,n代表正无穷大)

临建锁(Next-Key Lock)
也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住
触发条件:范围查询并命中,查询命中了索引。
结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之后,在范围区间内数据不允许被修改和插入。

如果当事务A加锁成功之后就设置一个状态告诉后面的人,已经有人对表里的行加了一个排他锁了,你们不能对整个表加共享锁或排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态就知道自己是不是可以对表加锁,避免了对整个索引树的每个节点扫描是否加锁,而这个状态就是意向锁。
意向共享锁
当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共享锁。

意向排他锁
当一个事务试图对整个表进行加排它锁之前,首先需要获得这个表的意向排它锁。

四、JVM 1.说一下JVM中,哪些是共享区,哪些可以作为gc root

1、堆区和方法区是所有线程共享的,栈、本地方法栈、程序计数器是每个线程独有的

2、什么是gc root,JVM在进行垃圾回收时,需要找到“垃圾”对象,也就是没有被引用的对象,但是直接找“垃圾”对象是比较耗时的,所以反过来,先找“非垃圾”对象,也就是正常对象,那么就需要从某些“根”开始去找,根据这些“根”的引用路径找到正常对象,而这些“根”有一个特征,就是它只会引用其他对象,而不会被其他对象引用,例如:栈中的本地变量、方法区中的静态变量、本地方法栈中的变量、正在运行的线程等可以作为gc root。

2.你们项目如何排查JVM问题

对于还在正常运行的系统:
1.可以使用jmap来查看VM中各个区域的使用情况
2.可以通过jstack来查看线程的运行情况,比如哪些线程阻塞、是否出现了死锁
3.可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc比较频繁,那么就得进行调优了
4.通过各个命令的结果,或者jvisualvm等工具来进行分析
5.首先,初步猜测频繁发送fullgc的原因,如果频繁发生fullgc但是又一直没有出现内存溢出,那么表示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对象进入到老年代,对于这种情况,就要考虑这些存活时间不长的对象是不是比较大,导致年轻代放不下,直接进入到了老年代,尝试加大年轻代的大小,如果改完之后,fullgc减少,则证明修改有效
6.同时,还可以找到占用CPU最多的线程,定位到具体的方法,优化这个方法的执行,看是否能避免某些对象的创建,从而节省内存

对于已经发生了OOM的系统:
1.一般生产系统中都会设置当系统发生了OOM时,生成当时的dump文件(-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/usr/local/base)
2.我们可以利用jsisualvm等工具来分析dump文件
3.根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位到具体的代码
4.然后再进行详细的分析和调试

总之,调优不是一蹴而就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题

3.GC如何判断对象可以被回收

引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以靣收,
可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

引用计数法,可能会出现A引用了B,B又引用了A,这时候就算他们都不再使用了,但因为相互引用计数器=1永远无法被回收。

GC Roots的对象有:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象

可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活

每个对象只能触发一次finalize(0方法

由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。

4.JVM内存模型



5.说说类加载器双亲委派模型

JVM中存在三个默认的类加载器:

  1. BootstrapClassLoader
  2. ExtClassLoader
  3. AppClassLoader
    AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是BootstrapClassLoader。

JVM在加载一个类时,会调用AppClassLoader的loadClass方法来加载这个类,不过在这个方法中,会先使用ExtClassLoader的loadClass方法来加载类,同样ExtClassLoader的loadClass方法中会先使用BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果BootstrapClassLoader没有加载到,那么ExtClassLoader就会自己尝试加载该类,如果没有加载到,那么则会由AppClassLoader来加载这个类。

所以,双亲委派指得是,JVM在加载类时,会委派给Ext和Bootstrap进行加载,如果没加载到才由自己进行加载。

6.Java的内存结构,堆分为哪几部分,默认年龄多大进入老年代

1.年轻代
a.Eden区 (8)
b. From Survivor区(1)
c. To Survivor区 (1)
2.老年代
默认对象的年龄达到15后,就会进入老年代

7.什么是字节码?采用字节码的好处是什么?

java中的编译器和解释器:
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。

编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了Java的编译与解释并存的特点。

Java源代码—>编译器—>jvm可执行的Java字节码(即虚拟指令)—>jvm---->jvm中解释器—>机器可执行的二进制机器码---->程序运行。

采用字节码的好处:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

8.六、如何进行JVM调优?JVM参数有哪些?怎么查看一个JAVA进的JVM参数?

JVM调优主要就是通过定制JVM运行参数来提高JAVA应用程度的运行数据

JVM参数大致可以分为三类:
1、标注指令:-开头,这些是所有的HotSpot都支持的参数。可以用java -help 打印出来。
2、非标准指令:-X开头,这些指令通常是跟特定的HotSpot版本对应的。可以用java-X打印出来。
3、不稳定参数:-XX开头,这一类参数是跟特定HotSpot版本对应的,并且变化非常大。详细的文档资料非常少。JDK1.8版本下,有几个常用的不稳定指令:

java-XX:+PrintCommandLineFlags:查看当前命令的不稳定指令。
java-XX:+PrintFlagslnitial:查看所有不稳定指令的默认值。

五、Java并发 1.并发的三大特性

原子性
原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元,2个操作必须全部完成。

private long count = 0;
public void calc(){
count++;
}

1:将count从主存读到工作内存中的副本中
2:+1的运算
3:将结果写入工作内存
4:将工作内存的值刷回主存(什么时候刷入由操作系统决定,不确定的)

那程序中原子性指的是最小的操作单元,比如自增操作,它本身其实并不是原子性操作,分了3步的,包括读取变量的原始值、进行加1操作、写入工作内存。所以在多线程中,有可能一个线程还没自增完,可能才执行到第二部,另一个线程就已经读取了值,导致结果错误。那如果我们能保证自增慢作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据。

关键字:synchronized
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

//线程1
boolean stop = false;
while(!stop){
	dosomething();
}
//线程2
stop = true;

如果线程2改变了stop的值,线程1一定会停止吗?不一定。当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。

关键字:volatile、synchronized、final
有序性
虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。

int a = 0;
bool flag = false;
public void write() {
	a = 2;//1
	flag = true;//2
}
public void multiply() {
	if (flag){	//3
		int ret = a * a;//4
	}
}

write方法里的1和2做了重排序,线程1先对flag赋值为true,随后执行到线程2,ret直接计算出结果,再到线程1,这时候a才赋值为2,很明显迟了一步

关键字:volatile、synchronized
volatile本身就包含了禁止指令重排序的语义,而synchronized关键字是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则明确的。

synchronized关键字同时满足以上三种特性,但是volatile关键字不满足原子性。

在某些情况下,volatile的同步机制的性能确实要优于锁(使用synchronized关键字或java.util.concurrent包里面的锁)因为volatile的总开销要比锁低

2.ThreadLocal内存泄露原因,如何避免

内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。

强引用:使用最普遍的引用(new),一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。

如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使VM在合适的时间就会回收该对象。

弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用
java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。

ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,
value为线程变量的副本

hreadLocalMap使用ThreadLocal的弱引用作为key,如果一I个ThreadLocal不存在外部强引用时,
Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null,而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉,但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链(红色链条)

key使用强引用
当hreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key使用弱引用
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocalt也会被回收。当key为nul,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

ThreadLocal正确的使用方法
1.每次使用完ThreadLkal都调用它的remove()方法清除数据
2.将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。

3.ThreadLocal的原理和使用场景

每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有
ThreadLocal对象及其对应的值

ThreadLocalMap由一个个Entry对象构成

Entry继承自weakReference>,一个Entry由ThreadLoca1对象和object构成。由此可
见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收

当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。

get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。

使用场景:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用该线程绑定的
connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离

4.ThreadLocal的底层原理

1.ThreadLocal是Java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据

2.ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值

3.如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMapt也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是,在使用了ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清楚Entry对象

4.ThreadLocal经典的应用场景就是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享同一个连接)

5.ReentrantLock中的公平锁和非公平锁的底层实现

首先不管是公平锁和非公平锁,它们的底层实现都会使用AQS来进行排队,它们的区别在于:线程在使用lockO方法加锁时,如果是公平锁,会先检查AQS队列中是否存在线程在排队,如果有线程在排队,则当前线程也进行排队,如果是非公平锁,则不会去检查是否有线程在排队,而是直接竞争锁。

不管是公平锁还是非公平锁,一旦没竞争到锁,都会进行排队,当锁释放时,都是唤醒排在最前面的线程,所以非平锁只是体现在了线程加锁阶段,而没有体现在线程被唤醒阶段。

另外,ReentrantLock是可重入锁,不管是公平锁还是非公平锁都是可重入的。

6.volatile关键字,他是如何保证可见性,有序性

1.对于加了volatile关键字的成员变量,在对这个变量进行修改时,会直接将CPU高级缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性
2.在对volatile修饰的成员变量进行读写时,会插入内存屏障,而内存屏障可以达到禁止重排序的效果,从而可以保证有序性

7.如果你提交任务时,线程池队列已满,这时会发生什么

1.如果使用的无界队列,那么可以继续提交任务是没关系的
2.如果使用的有界队列,提交任务时,如果队列满了,如果核心线程数没有达到上限,那么则增加线程,如果线程数已经达到了最大值,则使用拒绝策略进行拒绝

8.sychronized的自旋锁、偏向锁、轻量级锁、重量级锁,分别介绍和联系

1.偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程ID,该线程下次如果又来获取该锁就可以直接获取到了

2.轻量级锁:由偏向锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过自旋来实现的,并不会阻塞线程

3.如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞

4.自旋锁:自旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就无所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较消耗时间,自旋锁是线程通过CAS获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程一直在运行中,相对而言没有使用太多的操作系统资源,比较轻量。

9.简述线程池原理,FixedThreadPool用的阻塞队列是什么

线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:
1.如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

2.如果此时线程池中的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。

3.如果此时线程池中的数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。

4.如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务。

5.当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数

FixedThreadPool代表定长线程池,底层用的linkedBlockingQueue,表示无界的阻塞队列。

10.JAVA如何开启线程?怎么保证线程安全?

线程和进程的区别:进程是操作系统进行资源分配的最小单元。线程是操作系统进行任务分配的最小单元,线程隶属于进程。

如何开启线程?
1、继承Thread类,重写run方法。
2、实现Runnable接口,实现run方法。
3、实现Callable接口,实现call方法。通过FutureTask创建一个线程,获取到线程执行的返回值。
4、通过线程池来开启线程。

怎么保证线程安全?
加锁:1、JVM提供的锁,也就是Synchronized关键字。2、JDK提供的各种锁Lock。

11.谈谈你对AOS的理解。AOS如何实现可重入锁?

1、AQS是一个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。
2、在AQS中,维护了一个信号量state和一个线程组成的双向链表队列。其中,这个线程队列,就是用来给线程排队的,而state就像是一个红绿灯,用来控制线程排队或者放行的。在不同的场景下,有不用的意义。
3、在可重入锁这个场景下,state就用来表示加锁的次数。0标识无锁,每加一次锁,state就加1。释放锁state就减1。

六、分布式 1.CAP理论,base理论

Consistency(一致性):
即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。
对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题。
从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。
Availability(可用性):
即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。
Partition Tolerance(分区容错性):
即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响。
CP和AP:分区容错是必须保证的,当发生网络分区的时候,如果要继续服务,那么强一致性和可用性只能2选1

base是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)

base理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的。base理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

基本可用:
1.响应时间上的损失:正常情况下,处理用户请求需要0.5s返回结果,但是由于系统出现故障,处理用户请求的
时间变为3s。
2.系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。

软状态:数据同步允许一定的延迟

最终一致性:系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要求实时

2.dubbo集群容错策略有哪些

Failover Cluster失败自动切换:dubbo的默认容错方案,当调用失败时自动切换到其他可用的节点,具体的重试次数和间隔时间可通过引用服务的时候配置,默认重试次数为1也就是只调用一次。

Failback Cluster失败自动恢复:在调用失败,记录日志和调用信息,然后返回空结果给consumer,并且通过定时任务每隔5秒对失败的调用进行重试

Failfast Cluster快速失败:只会调用一次,失败后立刻抛出异常

Failsafe Cluster失败安全:调用出现异常,记录日志不抛出,返回空结果

Forking Cluster并行调用多个服务提供者:通过线程池创建多个线程,并发调用多个provider,结果保存到阻塞队列,只要有一个provider成功返回了结果,就会立刻返回结果

Broadcast Cluster广播模式:逐个调用每个provider,如果其中一台报错,在循环调用结束后,抛出异常。

3.Dubbo支持哪些负载均衡策略

1.随机:从多个服务提供者随机选择一个来处理本次请求,调用量越大则分布越均匀,并支持按权重设置随机概率
2.轮询:依次选择服务提供者来处理请求,并支持按权重进行轮询,底层采用的是平滑加权轮询算法
3.最小活跃调用数:统计服务提供者当前正在处理的请求,下次请求过来则交给活跃数最小的服务器来处理
4.一致性哈希:相同参数的请求总是发到同一个服务提供者

4.Dubbo是如何完成服务导出的?

1.首先Dubbo会将程序员所使用的@DubboService注解或@Service注解进行解析得到程序员所定义的服务参数,包括定
义的服务名、服务接口、服务超时时间、服务协议等等,得到一个ServiceBean。
2.然后调用ServiceBean的export方法进行服务导出
3.然后将服务信息注册到注册中心,如果有多个协议,多个注册中心,那就将服务按单个协议,单个注册中心进行注册
4.将服务信息注册到注册中心后,还会绑定一些监听器,监听动态配置中心的变更
5.还会根据服务协议启动对应的Web服务器或网络框架,比如Tomcat、Netty等

5.Dubbo是如何完成服务引入的?

1.当程序员使用@Reference注解来引入一个服务时,Dubbo会将注解和服务的信息解析出来,得到当前所引用的服务
名、服务接口是什么
2.然后从注册中心进行查询服务信息,得到服务的提供者信息,并存在消费端的服务目录中
3.并绑定一些监听器用来监听动态配置中心的变更
4.然后根据查询得到的服务提供者信息生成一个服务接口的代理对象,并放入Spring容器中作为Bean

6.session的分布式方案

1、采用无状态服务,抛弃session

2、存入cookie(有安全风险)

3、服务器之间进行Session同步,这样可以保证每个服务器上都有全部的Session信息,不过当服务器数量比较多的时候,同步是会有延迟甚至同步失败;

4、IP绑定策略
使用Nginx(或其他复杂均衡软硬件)中的IP绑定策略,同一个IP只能在指定的同一个机器访问,但是这样做失去了负载均衡的意义,当挂掉一台服务器的时候,会影响一批用户的使用,风险很大;

5、使用Redis存储
把Session放到Redis中存储,虽然架构上变得复杂,并且需要多访问一次Redis,但是这种方案带来的好处也是很大的:
(1)实现了 Session共享;
(2)可以水平扩展(增加Redis服务器);
(3)服务器重启Session不丢失(不过也要注意Session在Redis中的刷新/失效机制)
(4)不仅可以跨服务器Session共享,甚至可以跨平台(例如网页端和APP端)。

7.Spring Cloud和Dubbo的区别

底层协议:springcloud基于http协议,dubbo基于Tcp协议,决定了dubbo的性能相对会比较好
注册中心:Spring Cloud使用的eureka,dubbo推荐使用zookeeper
模型定义:dubbo将一个接口定义为一个服务,SpringCloud则是将一个应用定义为一个服务
SpringCloud是一个生态,而Dubbo是SpringCloud生态中关于服务调用一种解决方案(服务治理)

8.zk的watch机制实现原理
new Zookeeper(String connectString, int sessionTimeout, Watcher watcher)
这个watcher将作为整个zooKeeper会话期间的上下文,一直被保存在客户端zKwatchManager的defaultwatcher

也可以动态添加watcher:getData(),exists,getChildren。

分布式环境下的观察者模式:通过客服端和服务端分别创建有观察者的信息列表。客户端调用相应接口时,首先将对应的Watch事件放到本地的zKWatchManager中进行管理。服务端在接收到客户端的请求后根据请求类型判断是否含有Watch事件,并将对应事件放到WatchManager中进行管理。
在事件触发的时候服务端通过节点的路径信息查询相应的Watch事件通知给客户端,客户端在接收到通知后,首先查询本地的ZKWatchManager获得对应的Watch信息处理回调操作。这种设计不但实现了一个分布式环境下的观察者模式,而且通过将客户端和服务端各自处理Watch事件所需要的额外信息分别保存在两端,减少彼此通信的内容。大大提升了服务的处理性能

客户端实现过程:
1.标记该会话是一个带有Watch事件的请求
2.通过 DataWatchRegistration 类来保存 watcher事件和节点的对应关系
3.客户端向服务器发送请求,将请求封装成一个Packet对象,并添加到一个等待发送队列outgoingQueue中
4.调用负责处理队列outgoingQueue的SendThread线程类中的readResponse方法接收服务端的回调,并在最后执行 finishPacket ()方法将Watch 注册到ZKWatchManager,sendThread 通讨发送path 路径和watcher为true,到server注册watch事件。
ZKWatchManager保存了 Map dataWatchers、 MapexistsWatchersMapchildrenWatchers三个集合,客户端会在dataWatchers中会添加一个key为path路径的本地事件

服务端实现过程:
1.解析收到的请求是否带有Watch注册事件,通过FinalRequestProcessor类中的processRequest函数实现的。当getDataRequest.getWatch()值为True时,表明该请求需要进行Watch 监控注册。
2.将对应的Watch事件存储到WatchManager,通过zks.getzKDatabase().getData函数实现,WatchManger该类中有HashMapwatchTable,key为path,Watcher是一个客户端网络连接封装,当节点变化时会通知对应的连接(连接通过心跳保持)

服务端触发过程:
1.调用WatchManager中的方法触发数据变更事件
2.封装了一个具有会话状态、事件类型、数据节点3种属性的WatchedEvent对象。之后查询该节点注册的Watch事件,如果为空说明该节点没有注册过Watch事件。如果存在Watch事件则添加到定义的Wathcers集合中,并在WatchManager管理中删除。最后,通过调用process方法向客户端发送通知

客户端回调过程:
1.使用SendThread.readResponse()方法来统一处理服务端的相应
2.将收到的字节流反序列化转换成WatcherEvent对象。调用eventThread.queueEvent()方法将接收到的事件交给EventThread线程进行处理
3.从ZKWatchManager中查询注册过的客户端Watch信息。查询到后,会将其从zKWatchManager的管理中删除。因此客户端的Watcher机制是一次性的,触发后就会被删除
4.将查询到的Watcher存储到waitingEvents队列中,调用EventThread类中的run方法循环取出在waitingEvents 队列中等待的Watcher事件进行处理

9.Zookeeper中的领导者选举的流程是怎样的?

对于Zookeeper集群,整个集群需要从集群节点中选出一个节点作为Leader,大体流程如下:
1.集群中各个节点首先都是观望态,一开始都会投票给自己,认为自己比较适合作为leader
2.然后相互交互投票,每个节点会收到其他节点发过来的选票,然后pk,先比较zxid,zxid大者获胜,zxid如果相等则比较myid,myid大者获胜
3.一个节点收到其他节点发过来的选票,经过PK后,如果PK输了,则改票,此节点就会投给zxid或myid更大的节点,并将选票放入自己的投票箱中,并将新的选票发送给其他节点
4.如果pk是平局则将接收到的选票放入自己的投票箱中
5.如果pk赢了,则忽略所接收到的选票
6.当然一个节点将一张选票放入到自己的投票箱之后,就会从投票箱中统计票数,看是否超过一半的节点都和自己所投的节点是一样的,如果超过半数,那么则认为当前自己所投的节点是leader
7.集群中每个节点都会经过同样的流程,pk的规则也是一样的,一旦改票就会告诉给其他服务器,所以最终各个节点中的投票箱中的选票也将是一样的,所以各个节点最终选出来的leader也是一样的,这样集群的leader就选举出来了

10.说说你了解的分布式锁实现

分布式锁所要解决的问题的本质是:能够对分布在多台机器中的线程对共享资源的互斥访问。在这个原理上可以有很多的实现方式:
1.基于Mysql,分布式环境中的线程连接同一个数据库,利用数据库中的行锁来达到互斥访问,但是Mysql的加锁和释放锁的性能会比较低,不适合真正的实际生产环境
2.基于zookeeper,zookeeper中的数据是存在内存的,所以相对于Mysql性能上是适合实际环境的,并且基于zookeeper的顺序节点、临时节点、Watch机制能非常好的来实现的分布式锁
3.基于Redis,Redis中的数据也是在内存,基于Redis的消费订阅功能、数据超时时间,lua脚本等功能,也能很好的实现的分布式锁

11.分布式锁的使用场景是什么?有哪些实现方案?

在单体架构中,多个线程都是属于同一个进程的,所以在线程并发执行时,遇到资源竞争时,可以利用ReentrantLock、synchronized等技术来作为
锁,来控制共享资源的使用。

而在分布式架构中,多个线程是可能处于不同进程中的,而这些线程并发执行遇到资源竞争时,利用ReentrantLock、synchronized等技术是没办法来控制多个进程中的线程的,所以需要分布式锁,意思就是,需要一个分布式锁生成器,分布式系统中的应用程序都可以来使用这个生成器所提供的锁,从而达到多个进程中的线程使用同一把锁。

目前主流的分布式锁的实现方案有两种:
1.zookeeper:利用的是zookeeper的临时节点、顺序节点、watch机制来实现的,zookeeper分布式锁的特点是高一致性,因为zookeeper保证的是CP,所以由它实现的分布式锁更可靠,不会出现混乱
2.redis:利用redis的setnx、lua脚本、消费订阅等机制来实现的,redis分布式锁的特点是高可用,因为redis保证的是Ap,所以由它实现的分布式锁可能不可靠,不稳定(一旦redis中的数据出现了不一致),可能会出现多个客户端同时加到锁的情况

12.如何理解RPC

远程过程调用
RPC要求在调用方中放置被调用的方法的接口。调用方只要调用了这些接口,就相当于调用了被调用方的实际方法,十分易用。于是,调用方可以像调用内部接口一样调用远程的方法,而不用封装参数名和参数值等操作。

包含
1.动态代理,封装调用细节
2.序列化与反序列化,数据传输与接收
3.通信,可以选择七层的http,四层的tcp/udp
4.异常处理等

首先,调用方调用的是接口,必须得为接口构造一个假的实现。显然,要使用动态代理。这样,调用方的调用就被动态代理接收到了。

第二,动态代理接收到调用后,应该想办法调用远程的实际实现。这包括下面几步:
1.识别具体要调用的远程方法的IP、端口
2.将调用方法的入参进行序列化
3.通过通信将请求发送到远程的方法中

这样,远程的服务就接收到了调用方的请求。它应该:
1.反序列化各个调用参数
2.定位到实际要调用的方法,然后输入参数,执行方法
3.按照调用的路径返回调用的结果

13.简述zk的命名服务、配置管理、集群管理

命名服务:
通过指定的名字来获取资源或者服务地址。Zookeeper可以创建一个全局唯一的路径,这个路径就可以作为一个名字。被命名的实体可以是集群中的机器,服务的地址,或者是远程的对象等。一些分布式服务框架(RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据特定的名字来获取资源的实体、服务地址和提供者信息等
配置管理:
实际项目开发中,经常使用.properties或者xml需要配置很多信息,如数据库连接信息、fps地址端口等等。程序分布式部署时,如果把程序的这些配置信息保存在zk的znode节点下,当你要修改配置,即znode会发生变化时,可以通过改变zk中某个目录节点的内容,利用watcher通知给各个客户端,从而更改配置。
集群管理:
集群管理包括集群监控和集群控制,就是监控集群机器状态,剔除机器和加入机器。zookeeper可以方便集群机器的管理,它可以实时监控znode节点的变化,一旦发现有机器挂了,该机器就会与zk断开连接,对应的临时目录节点会被删除,其他所有机器都收到通知。新机器加入也是类似。

七、微服务 1.SOA、分布式、微服务之间有什么关系和区别?

1.分布式架构是指将单体架构中的各个部分拆分,然后部署不同的机器或进程中去,SOA和微服务基本上都是分布式架构的
2.SOA是一种面向服务的架构,系统的所有服务都注册在总线上,当调用服务时,从总线上查找服务信息,然后调用
3.微服务是一种更彻底的面向服务的架构,将系统中各个功能个体抽成一个个小的应用程序,基本保持一个应用对应的一个服务的架构

2.Spring Cloud有哪些常用组件,作用是什么?

1.Eureka:注册中心
2.Nacos:注册中心、配置中心
3.Consul:注册中心、配置中心
4.Spring Cloud Config:配置中心
5.Feign/OpenFeign:RPC调用
6.Kong:服务网关
7.Zuul:服务网关
8.Spring Cloud Gateway:服务网关
9.Ribbon:负载均衡
10.Spring CLoud Sleuth:链路追踪
11.Zipkin:链路追踪
12.Seata:分布式事务
13.Dubbo:RPC调用
14.Sentinel:服务熔断
15.Hystrix:服务熔断

3.Spring Cloud核心组件及其应用

Eureka:服务注册与发现
注册:每个服务都向Eureka登记自己提供服务的元数据,包括服务的ip地址、端口号、版本号、通信协议等。eureka将各个服务维护在了一个服务清单中(双层Map,第一层key是服务名,第二层key是实例名,value是服务地址加端口)。同时对服务维持心跳,剔除不可用的服务,eureka集群各节点相互注册每个实例中都有一样的服务清单。

发现:eureka注册的服务之间调用不需要指定服务地址,而是通过服务名向注册中心咨询,并获取所有服务实例清单(缓存到本地),然后实现服务的请求访问。

Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台(被调用方的服务地址有多个),Ribbon也是通过发起http请求,来进行的调用,只不过是通过调用服务名的地址来实现的。虽然说Ribbon不用去具体请求服务实例的ip地址或域名了,但是每调用一个接口都还要手动去发起Http请求

Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求,简化服务间的调用,在Ribbon的基础上进行了进一步的封装。单独抽出了一个组件,就是Spring Cloud Feign。在引入SpringCloud Feign后,我们只需要创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。
调用远程就像调用本地服务一样

Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,通过统计接口超时次数返回默认值,实现服务熔断和降级

Zuul:如果前端、移动端要调用后端系统,统一从zuul网关进入,由zuul网关转发请求给对应的服务,通过与Eureka进行整合,将自身注册为Eureka下的应用,从Eureka下获取所有服务的实例,来进行服务的路由。zuul还提供了一套过滤器机制,开发者可以自己指定哪些规则的请求需要执行校验逻辑,只有通过校验逻辑的请求才会被路由到具体服务实例上,否则返回错误提示。

4.高并发场景下如何实现系统限流?

限流一般需要结合容量规划和压测来进行。当外部请求接近或者达到系统的最大阈值时,触发限流,采取其他的手段进行降级,保护系统不被压垮。常见的降级策略包括延迟处理、拒绝服务、随机拒绝等。
计数器法:

1、将时间划分为固定的窗口大小,例如1s
2、在窗口时间段内,每来一个请求,对计数器加1。
3、当计数器达到设定限制后,该窗口时间内的之后的请求都被丢弃处理。
4、该窗口时间结束后,计数器清零,从新开始计数。

滑动窗口计数法:

1.将时间划分为细粒度的区间,每个区间维持一个计数器,每进入一个请求则将计数器加一。
2.多个区间组成一个时间窗口,每流逝一个区间时间后,则抛弃最老的一个区间,纳入新区间。
3.若当前窗口的区间计数器总和超过设定的限制数量,则本窗口内的后续请求都被丢弃。

漏桶算法:如果外部请求超出当前阈值,则会在容易里积蓄,一直到溢出,系统并不关心溢出的流量。从出口处限制请求速率,并不存在计数器法的临界问题,请求曲线始终是平滑的。无法应对突发流量,相当于一个空桶+固定处理线程

令牌桶算法:假设一个大小恒定的桶,这个桶的容量和设定的阈值有关,桶里放着很多令牌,通过一个固定的速率,往里边放入令牌,如果桶满了,就把令牌丢掉,最后桶中可以保存的最大令牌数永远不会超过桶的大小。当有请求进入时,就尝试从桶里取走一个令牌,如果桶里是空的,那么这个请求就会被拒绝。

5.什么是Hystrix?简述实现机制

分布式容错框架
1.阻止故障的连锁反应,实现熔断
2.快速失败,实现优雅降级
3.提供实时的监控和告警

资源隔离:线程隔离,信号量隔离
1.线程隔离:Hystrix会给每一个Command分配一个单独的线程池,这样在进行单个服务调用的时候,就可以在独立的线程池里面进行,而不会对其他线程池造成影响
2.信号量隔离:客户端需向依赖服务发起请求时,首先要获取一个信号量才能真正发起调用,由于信号量的数量有限,当并发请求量超过信号量个数时,后续的请求都会直接拒绝,进入fallback流程。信号量隔离主要是通过控制并发请求量,防止请求线程大面积阻塞,从而达到限流和防止雪崩的目的。

熔断和降级:调用服务失败后快速失败

熔断是为了防止异常不扩散,保证系统的稳定性

降级:编写好调用失败的补救逻辑,然后对服务直接停止运行,这样这些接口就无法正常调用,但又不至于直接报错,只是服务水平下降

1.通过HystrixCommand或者HystrixObservableCommand将所有的外部系统(或者称为依赖)包装起来,整个包装对象是单独运行在一个线程之中(这是典型的命令模式)。
2.超时请求应该超过你定义的阈值
3.为每个依赖关系维护一个小的线程池(或信号量);如果它变满了,那么依赖关系的请求将立即被拒绝,而不是排队等待。
4.统计成功,失败(由客户端抛出的异常),超时和线程拒绝。
5.打开断路器可以在一段时间内停止对特定服务的所有请求,如果服务的错误百分比通过阈值,手动或自动的关闭断路器。
6.当请求被拒绝、连接超时或者断路器打开,直接执行fallback逻辑。
7.近乎实时监控指标和配置变化。

6.什么是服务熔断?什么是服务降级?区别是什么?

1.服务熔断是指,当服务A调用的某个服务B不可用时,上游服务A为了保证自己不受影响,从而不再调用服务B,直接返回一个结果,减轻服务A和服务B的压力,直到服务B恢复。
2.服务降级是指,当发现系统压力过载时,可以通过关闭某个服务,或限流某个服务来减轻系统压力,这就是服务降级。

相同点:
1.都是为了防止系统崩溃
2.都让用户体验到某些功能暂时不可用

不同点:熔断是下游服务故障触发的,降级是为了降低系统负载

7.什么是服务雪崩?什么是服务限流?

1.当服务A调用服务B,服务B调用C,此时大量请求突然请求服务A,假如服务A本身能抗住这些请求,但是如果服务C抗不住,导致服务C请求堆积,从而服务B请求堆积,从而服务A不可用,这就是服务雪崩,解决方式就是服务降级和服务熔断。
2.服务限流是指在高并发请求下,为了保护系统,可以对访问服务的请求进行数量上的限制,从而防止系统不被大量请求压垮,在秒杀中,限流是非常重要的。

8.你的项目中是怎么保证微服务敏捷开发的?微服务的链路追踪、持续集成、AB发布要怎么做?

开发运维一体化。

敏捷开发:目的就是为了提高团队的交付效率,快速代,快速试错。

每个月固定发布新版本,以分支的形式保存到代码仓库中。快速入职。任务面板、站立会议。团队人员灵活流动,同时形成各个专家代表。测试环境-生产环境-》开发测试环境SIT、集成测试环境、压测环境STR、预投产环境、生产环境PRD。文档优先。晨会、周会、需求拆分会。

链路追踪:1、基于日志。形成全局事务ID,落地到日志文件。filebeat-logstash-Elasticsearch形成大型报表。2、基于MQ,往往需要架构支持。经过流式计算形成一些可视化的结果。

持续集成:SpringBoot maven pom->build->shell;Jenkins。

AB发布:1、蓝绿发布、红黑发布老版本和新版本是同时存在的。2、灰度发布、金丝雀发布。

9.谈谈你对微服务的理解,微服务有哪些优缺点?

微服务是由Martin Fowler大师提出的。微服务是一种架构风格,通过将大型的单体应用划分为比较小的服务单元,从而降低整个系统的复杂度。

优点:
1、服务部署更灵活:每个应用都可以是一个独立的项目,可以独立部署,不依赖于其他服务,耦合性降低。
2、技术更新灵活:在大型单体应用中,技术要进行更新,往往是非常困难的。而微服务可以根据业务特点,灵活选择技术栈。
3、应用的性能得到提高:大型单体应用中,往往启动就会成为一个很大的难关。而采用微服务之后,整个系统的性能是能够得到提高的。
4、更容易组合专门的团队:在单体应用时,团队成员往往需要对系统的各个部分都要有深入的了解,门槛是很高的。而采用微服务之后,可以给每个微服务组建专门的团队。
5、代码复用:很多底层服务可以以REST API的方式对外提供统一的服务,所有基础服务可以在整个微服务系统中通用。

缺点:
1、服务调用的复杂性提高了:网络问题、容错问题、负载问题、高并发问题。。。。。。
2、分布式事务:尽量不要使用微服务事务。
3、测试的难度提升了:
4、运维难度提升:单体架构只要维护一个环境,而到了微服务是很多个环境,并且运维方式还都不一样。所以对部署、监控、告警等要求就会变得非常困难。

八、消息队列 1.Kafka、ActiveMQ、RabbitMQ、RocketMQ对比

ActiveMQ:JMS规范,支持事务、支持XA协议,没有生产大规模支撑场景、官方维护越来越少

RabbitMQ:erlang语言开发、性能好、高并发,支持多种语言,社区、文档方面有优势,erlang语言不利于java程序员二次开发,依赖开源社区的维护和升级,需要学习AMQP协议、学习成本相对较高

以上吞吐量单机都在万级

kafka:高性能,高可用,生产环境有大规模使用场景,单机容量有限(超过64个分区响应明显变长)、社区更新慢吞

吐量单机百万

rocketmq:java实现,方便二次开发、设计参考了kafka,高可用、高可靠,社区活跃度一般、支持语言较少

吞吐量单机十万

2.Kafka的Pull和Push分别有什么优缺点

1.pull表示消费者主动拉取,可以批量拉取,也可以单条拉取,所以pull可以由消费者自己控制,根据自己的消息处理能力来进行控制,但是消费者不能及时知道是否有消息,可能会拉到的消息为空
2.push表示Broker主动给消费者推送消息,所以肯定是有消息时才会推送,但是消费者不能按自己的能力来消费消息,推过来多少消息,消费者就得消费多少消息,所以可能会造成网络堵塞,消费者压力大等问题

3.RocketMQ的底层实现原理

RocketMQ由NameServer集群、Producer集群、Consumer集群、Broker集群组成,消息生产和消费的大致原理如下:
1.Broker在启动的时候向所有的NameServer注册,并保持长连接,每30s发送一次心跳
2.Producer在发送消息的时候从NameServer获取Broker服务器地址,根据负载均衡算法选择一台服务器来发送消息
3.Conusmer消费消息的时候同样从NameServer获取Broker地址,然后主动拉取消息来消费

4.Kafka消息丢失的场景及解决方案

(1)消息发送

1、ack=0,不重试
producer发送消息完,不管结果了,如果发送失败也就丢失了。
2、 ack=1, leader crash
producer发送消息完,只等待1ead写入成功就返回了,leader crash了,这时fo11ower没来及同步,消息丢失。
3、unclean.leader.election.enable 配置true
允许选举ISR以外的副本作为1eader,会导致数据丢失,默认为false。producer发送异步消息完,只等待1ead写入成功就返回了,leader crash了,这时ISR中没有fo11ower,1eader从osR中选举,因为osR中本来落后于Leader造成消息丢失。
解决方案:
1、配置:ack=al1 /-1,tries > 1,unclean.leader.election.enable :false
producer发送消息完,等待fo11ower同步完再返回,如果异常则重试。副本的数量可能影响吞吐量。
不允许选举ISR以外的副本作为1eader。
2、配置:min.insync.replicas >1
副本指定必须确认写操作成功的最小副本数量。如果不能满足这个最小值,则生产者将引发一个异常(要么是NotEnoughReplicas,要么是NotEnoughReplicasAfterAppend)。
min.insync.replicas和ack更大的持久性保证。确保如果大多数副本没有收到写操作,则生产者将引发异常。
3、失败的offset单独记录
producer发送消息,会自动重试,遇到不可恢复异常会抛出,这时可以捕获异常记录到数据库或缓存,进行单独处理。

(2)消费

先commit再处理消息。如果在处理消息的时候异常了,但是offset已经提交了,这条消息对于该消费者来说就是丢失了,再也不会消费到了。

(3)broker的刷盘

减小刷盘间隔

5.使用MQ如何保证分布式事务的最终一致性?

分布式事务:业务相关的多个操作,保证他们同时成功或者同时失败。

最终一致性:与之对应的就是强一致性

MQ中要保护事务的最终一致性,就需要做到两点
1、生产者要保证100%的消息投递。事务消息机制
2、消费者这一端需要保证幂等消费。唯一ID+业务自己实现幂等
分布式MQ的三种语义:
at least once
at most once
exactly once:RocketMQ并不能保证exactly once。商业版本当中提供了exactlylonce的实现机制。

6.如何保证消息的高效读写?

零拷贝:kafka和RocketMQ都是通过零拷贝技术来优化文件读写。
传统文件复制方式:需要对文件在内存中进行四次拷贝。

Java当中对零拷贝进行了封装,Mmap方式通过MappedByteBuffer对象进行操作,而transfile通过FileChannel来进行操作。

Mmap适合比较小的文件,通常文件大小不要超过1.5G~2G之间。

Transfile没有文件大小限制。

RocketMQ当中使用Mmap方式来对他的文件进行读写。commitlog。1G

在kafka当中,他的index日志文件也是通过mmap的方式来读写的。在其他日志文件当中,并没有使用零拷贝的方式。

kafka使用fransfile方式将硬盘数据加载到网卡。

7.MQ如何保证消息顺序

全局有序和局部有序:MQ只需要保证局部有序,不需要保证全局有序。

生产者把一组有序的消息放到同一个队列当中,而消费者一次消费整个队列当中的消息。
RocketMQ中有完整的设计,但是在RabbitMQ和Kafka当中,并没有完整的设计,需要自己进行设计。
RabbitMQ:要保证目标exchange只对应一个队列。并且一个队列只对应一个消费者。
Kafka:生产者通过定制partition分配规则,将消息分配到同一个partition。Topic下只对应一个消费者。

8.让你设计一个MQ,你会如何设计?

两个误区:1、放飞自我,漫无边际。2、纠结技术细节。
好的方式:1、从整体到细节,从业务场景到技术实现。2、以现有产品为基础。RocketMQ
答题思路:MQ作用、项目大概的样子。
1、实现一个单机的队列数据结构。高效、可扩展。
2、将单机队列扩展成为分布式队列。-分布式集群管理
3、基于Topic定制消息路由策略。-发送者路由策略,消费者与队列对应关系,消费者路由策略
4、实现高效的网络通信。-Netty Http
5、规划日志文件,实现文件高效读写。-零拷贝,顺序写。服务重启后,快速还原运行现场。
6、定制高级功能,死信队列、延迟队列、事务消息等等。-贴合实际,随意发挥。

9.RabbitMQ架构设计

九、计算机网络 1.BIO、NIO、AIO分别是什么

1.BIO:同步阻塞IO,使用BIO读取数据时,线程会阻塞住,并且需要线程主动去查询是否有数据可读,并且需要处理完一个Socket之后才能处理下一个Socket
2.NIO:同步非阻塞IO,使用NIO读取数据时,线程不会阻塞,但需要线程主动的去查询是否有IO事件
3.AIO:也叫做NIO2.0,异步非阻塞IO,使用AIO读取数据时,线程不会阻塞,并且当有数据可读时会通知给线程,不需要线程主动去查询

2.Netty的线程模型是怎么样的

Netty同时支持Reactor单线程模型、Reactor多线程模型和Reactor主从多线程模型,用户可根据启动参数配置在这三种模型之间切换。

服务端启动时,通常会创建两个NioEventLoopGroup实例,对应了两个独立的Reactor线程池,bossGroup负责处理客户端的连接请求,workerGroup负责处理/O相关的操作,执行系统Task、定时任务Task等。用户可根据服务端引导类ServerBootstrap配置参数选择Reactor线程模型,进而最大限度地满足用户的定制化需求。

3.零拷贝是什么

零拷贝指的是,应用程序在需要把内核中的一块区域数据转移到另外一块内核区域去时,不需要经过先复制到用户空间,再转移到标内核区域去了,而直接实现转移。

4.TCP的三次握手和四次挥手

TCP协议是7层网络协议中的传输层协议,负责数据的可靠传输。
在建立TCP连接时,需要通过三次握手来建立,过程是:
1.客户端向限务端发送一个SYN
2.服务端接收到SYN后,给客户端发送一个SYN_ACK
3.客户端接收到SYN_ACK后,再给服务端发送一个ACK

在断开TCP连接时,需要通过四次挥手来断开,过程是:
1.客户端向服务端发送FIN
2.服务端接收FIN后,向客户端发送ACK,表示我接收到了断开连接的请求,客户端你可以不发数据了,不过服务端这边可能还
有数据正在处理
3.服务端处理完所有数据后,向客户端发送FIN,表示服务端现在可以断开连接
4.客户端收到服务端的FIN,向服务端发送ACK,表示客户端也会断开连接了

5.五、描述下HTTP和HTTPS的区别。

HTTP:匙互联网上应用最为广泛的一种网络通信协议,基于TCP,可以使浏览器工作更为高效,减少网络传输。
HTTPS:是HTTP的加强版,可以认为是HTTP+SSL(Secure Socket Layer)。在HTTP的基础上增加了一系列的安全机制。一方面保证数据传输安全,另一位方面对访问者增加了验证机制。是目前现行架构下,最为安全的解决方案。
主要区别:
1、HTTP的连接是简单无状态的,HTTPS的数据传输是经过证书加密的,安全性更高。
2、HTTP是免费的,而HTTPS需要申请证书,而证书通常是需要收费的,并且费用一般不低。
3、他们的传输协议不通过,所以他们使用的端口也是不一样的,HTTP默认是80端口,而HTTPS默认是443端口。
HTTPS的缺点:
1、HTTPS的握手协议比较费时,所以会影响服务的响应速度以及吞吐量。
2、HTTPS也并不是完全安全的。他的证书体系其实并不是完全安全的。并且HTTPS在面对DDOS这样的攻击时,几乎起不到任何作用。
3、证书需要费钱,并且功能越强大的证书费用越高。

6.HTTPS是如何保证安全传输的

https通过使用对称加密、非对称加密、数字证书等方式来保证数据的安全传输。

1.客户端向服务端发送数据之前,需要先建立TCP连接,所以需要先建立TCP连接,建立完TCP连接后,服务端会先给客户端发送公钥,客户端拿到公钥后就可以用来加密数据了,服务端到时候接收到数据就可以用私钥解密数据,这种就是通过非对称加密来传输数据

2.不过非对称加密比对称加密要慢,所以不能直接使用非对称加密来传输请求数据,所以可以通过非对称加密的方式来传输对称加密的秘钥,之后就可以使用对称加密来传输请求数据了

3.但是仅仅通过非对称加密+对称加密还不足以能保证数据传输的绝对安全,因为服务端向客户端发送公钥时,可能会被截取

4.所以为了安全的传输公钥,需要用到数字证书,数字证书是具有公信力、大家都认可的,服务端向客户端发送公钥时,可以把公钥和服务端相关信息通过Hash算法生成消息摘要,再通过数字证书提供的私钥对消息摘要进行加密生成数字签名,在把没进行Hash算法之前的信息和数字签名一起形成数字证书,最后把数字证书发送给客户端,客户端收到数字证书后,就会通过数字证书提供的公钥来解密数字证书,从而得到非对称加密要用到的公钥。

5.在这个过程中,就算有中间人拦截到服务端发出来的数字证书,虽然它可以解密得到非对称加密要使用的公钥,但是中间人是办法伪造数字证书发给客户端的,因为客户端上内嵌的数字证书是全球具有公信力的,某个网站如果要支持https,都是需要申请数字证书的私钥的,中间人如果要生成能被客户端解析的数字证书,也是要申请私钥的,所以是比较安全了。

7.跨域请求是什么?有什么问题?怎么解决?

跨域是指浏览器在发起网络请求时,会检查该请求所对应的协议、域名、端口和当前网页是否一致,如果不一致则浏览器会进行限制,比如在www.baidu.com的某个网页中,如果使用ajax去访问www.jd.com是不行的,但是如果是img、iframe、script等标签的srd属性去访问则是可以的,之
所以浏览器要做这层限制,是为了用户信息安全。但是如果开发者想要绕过这层限制也是可以的:

1.response添加header,比如resp.setHeader(“Access-Control-Allow-Origin",“*");表示可以访问所有网站,不受是否同源的限制
2.jsonp的方式,该技术底层就是基于script标签来实现的,因为script标签是可以跨域的
3.后台自己控制,先访问同域名下的接口,然后在接口中再去使用HTTPClient等工具去调用目标接口
4.网关,和第三种方式类似,都是交给后台服务来进行跨域访问

8.什么是SSO?与OAuth2.0有什么关系?

OAuth2.0的使用场景通常称为联合登录。一处注册,多处使用。
SSO Single Sign On单点登录。一处登录,多处同时登录。
SSO的实现关键是将Session信息集中存储。Spring Security

9.浏览器发出一个请求到收到响应经历了哪些步骤?

1.浏览器解析用户输入的URL,生成一个HTTP格式的请求
2.先根据URL域名从本地hosts文件查找是否有映射IP,如果没有就将域名发送给电脑所配置的DNS进行域名解析,得到IP地址
3.浏览器通过操作系统将请求通过四层网络协议发送出去
4.途中可能会经过各种路由器、交换机,最终到达服务器
5.服务器搜到请求后,根据请求所指定的端口,将请求传递给绑定了该端口的应用程序,比如8080被tomcat占用了
6.tomcat接收到请求数据后,按照httpt协议的格式进行解析,解析得到所要访问的servlet
7.然后servlet来处理这个请求,如果是SpringlMVC中的DispatchServlet,那么则会找到对应的Controller中的方法,并执行该方法得到结果
8.Tomcat得到响应结果后封装成HTTP响应的格式,并再次通过网络发送给浏览器所在的服务器
9.浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染

10.epoll和poll的区别

1.select模型,使用的是数组来存储Socket连接文件描述符,容量是固定的,需要通过轮询来判断是否发生了IO事件
2.poll模型,使用的是链表来存储Socket连接文件描述符,容量是不固定的,同样需要通过轮询来判断是否发生了IO事件
3.epoll模型,epoll和poll是完全不同的,epoll是一种事件通知模型,当发生了IO事件时,应用程序才进行IO操作,不需要像poll模型那样主动去轮询

十、Spring相关 1.简述Mybatis的插件运行原理,如何编写一个插件。

答:Mybstis只支持针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口
的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,拦截那些你指定需要拦截的方法。

编写插件:实现 Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,在配置文件中配置编写的插件。

@Intercepts({@signature(type = StatementHandler.class, method = "query", args =
{Statement.class, ResultHandler.class}),
	@Signature(type = StatementHandler.class, method = "update", args =
{Statement.class}),
	@Signature(type = StatementHandler.class, method = "batch", args = {
Statement.class })})
@Component

invocation.proceed()执行具体的业务逻辑
2.Mybatis存在哪些优点和缺点

优点:
1.基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL单独写,解除sql与程序代码的耦合,便于统一管理。
2.与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
3.很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
4.能够与Spring很好的集成;
5.提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

缺点:
1.SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
2.SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

3.Spring Boot、Spring MVC和Spring有什么区别

spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题、更方便将不同类不同方法中的共同处理抽取成切面、自动注入给方法执行,比如日志、异常等

springmvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端

springboot是spring提供的一个快速开发工具包,让程序员能更方便、更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制)、redis、mongodb、es,可以开箱即用

4.Spring Boot自动配置原理?

@import + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration+@Bean定义配置类,放到meta-INF/spring.factories下
使用Spring spi扫描meta-INF/spring.factories下的配置类
使用@lmport导入自动配置类

5.Spring MVC的主要组件?

Handler:也就是处理器。它直接应对着MVC中的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler
1、HandlerMapping
initHandlerMappings(context),处理器映射器,根据用户请求的资源uri来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行,这就是HandlerMapping需要做的事。
2、 HandlerAdapter
initHandlerAdapters(context),适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
3、 HandlerExceptionResolver
initHandlerExceptionResolvers(context),其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是
HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
4、ViewResolver
initViewResolvers(context),ViewResolver用来将String类型的祧图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成
5、 RequestToViewNameTranslator
initRequestToViewNameTranslator(context),ViewResolver是根据ViewName查找View,但有的Handler处理
完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
6、LocaleResolver
initLocaleResolver(context),解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。
7、ThemeResolver
initThemeResolver(context),用于解析主题。SpringMVc中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。
8、 MultipartResolver
initMultipartResolver(context),用于处理上传请求。处理方法是将普通的request包装成
MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用
getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。
9、 FlashMapManager
initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

6.SpringMVC中的控制器是不是单例模式?如果是,如何保证线程安全?

控制器是单例模式。
单例模式下就会有线程安全问题。
Spring中保证线程安全的方法
1、将scop设置成非singleton。prototype,request。
2、最好的方式是将控制器设计成无状态模式。在控制器中,不要携带数据。但是可以引用无状态的service和dao。

7.Spring框架中的单例Bean是线程安全的么?

Spring中的Bean默认是单例模式的,框架并没有对bean进行多线程的封装处理。

如果Bean是有状态的那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域把"singleton”改为“protopyte’这样每次请求Bean就相当于是new Bean()这样就可以保证线程的安全了。

有状态就是有数据存储功能
无状态就是不会保存数据 controller、service和dao层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。

Dao会操作数据库Connection,Connection是带有状态的,比如说数据库事务,Spring的事务管理器使用
Threadlocal为不同线程维护了一套独立的connection副本,保证线程之间不会互相影响(Spring是如何保证事务获取同一个Connection的)

不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等这些实现线程同步的方法了。

8.如何实现一个IOC容器

1、配置文件配置包扫描路径
2、递归包扫描获取.class文件
3、反射、确定需要交给IOC管理的类
4、对需要注入的类进行依赖注入

  • 配置文件中指定需要扫描的包路径
  • 定义一些注解,分别表示访问控制层、业务服务层、数据持久层、依赖注入注解、获取配置文件注解
  • 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件信息及文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储
  • 遍历这个set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象遍历这个IOC容器,获取到每一个类的实例,判断里面是有有依赖其他的类的实例,然后进行递归注入
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/445242.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号