前言:记录遇到/见到的一些面试题,自行查阅总结的答案,不一定完全准确,欢迎指正。
Memcached
Memcached是一种高性能、分布式的内存对象缓存系统,将数据存储在内存中, 便于快速访问和检索数据,从而提高Web应用程序的性能和响应速度,支持跨平台。
集群下可能存在问题和解决
微服务系统下可能存在问题和解决
Shiro
简介
Shiro是一个Java的安全框架,提供身份认证、授权、加密和会话管理等功能,可以帮助开发人员快速实现安全性较高的应用程序。
核心组件以及作用
核心组件包含:
认证过程、授权过程
认证过程:
授权过程:
资源过滤类型
- anno:不需要认证即可请求
- authc:必须认证才能请求
- user:身份认证通过后并且选择记住我,可以请求
- perms:必须有资源权限才能请求
- role:必须有角色权限才能请求
Spring、SpringBoot、SpringMVC
请求从前端到后端的过程
静态属性注入
Bean的作用域
- Singleton
- prototype
- request
- session
- global session
AOP通知类型
SpringBoot配置文件执行顺序
- bootstrap>application
- properties>yaml
- 项目同级目录config目录>项目同级目录>项目resource目录下的config目录>项目resource目录
事务失效情况
Spring中的设计模式
- 单例模式:Bean
- 工厂模式:Bean工厂
- 代理模式:AOP
- 适配器模式
- 模板模式:JDBCTemplate
SpringBoot
SpringBoot是Spring组件一站式的解决方案,集成很多开发框架,使开发者可以快速搭建Spring项目。简化了Spring的大量配置文件问题,可以快速创建一键运行的Spring应用。
内置Tomcat服务器,不需要单独部署war包;
提供各种Starter启动器,开箱即用;
自动配置
常用注解:
@SpringBootApplication
@Component、@Service、@Controller、@Mapper、@Repository、@ResponseBody、@RequestBody、@RestController、@Autowired、@Resource、@Qulifier、@RequestMapping、@GetMapping、@PostMapping、@Value、@ConfigurationProperties、@Import、@Configuration、@Bean
SpringCloud
组件及作用
Feign的原理
Feign底层依赖于Java的动态代理机制,声明式的http Client相对于编程式http代码逻辑更简洁;集中管理HTTP请求方法,代码边界清晰;Feign是一个声明式的、基于注解的HTTP客户端库,用于简化使用RestFul服务的调用。
工作原理是基于Java的动态代理和注解处理技术。通过使用注解描述API接口和请求信息,Feign可以在运行时生成代理对象,并将方法调用转化为HTTP请求。
服务熔断、服务降级、服务限流
熔断:当服务的错误率超过一定的阈值时,熔断器会自动断开服务的调用,防止错误的服务继续对系统造成负载压力,从而保证整个系统的可用性。如A服务调用B服务,B服务卡机导致时间超长,直接断路B,不再请求B。
限流:是一种控制流量的手段,通过设置最大并发数、最大请求数等方式,保证系统在高并发场景下不会被过多的请求拖垮。流量控制,使服务能够承受不超过自己能力的流量压力。
降级:指通过切换到备用方案来保证服务可用,例如使用缓存或使用降级接口等方式。如流量高峰期,服务器压力剧增,对一些服务和页面做策略的降级,所有调用直接返回降级数据。
熔断:Hystrix,通过维护一个计数器,记录服务的错误率,当错误率超过一定阈值时,熔断器会自动断开服务的调用,防止错误的服务继续对系统造成负载压力,从而保证整个系统的可用性。
限流:Sentinel
降级:Spring Cloud Circuit Breaker、Netflix Hystrix
熔断和降级的区别:
相同:都是为了保证集群大部分服务的可用性和可靠性,防止崩溃;用户最终体验都是某个功能不可用
不同:熔断是被调用方故障,触发的系统主动规则;降级是基于全局考虑,停止一些正常服务,释放资源。
RPC和HTTP的区别
http是指从客户端到服务器端的请求消息,rpc是远程过程调用协议,HTTP请求是使用具有标准语义的通用的接口定向到资源的,这些语义能够被中间组件和提供服务的来源机器进行解析。
Rpc的机制是根据语言的API来定义,而不是根据基于网络的应用来定义的。
RPC | HTTP | |
---|---|---|
传输协议 | 可以基于TCP,也可以HTTP | 基于HTTP协议 |
传输效率 | 使用自定义的TCP协议,可以让请求报文体积更小;使用HTTP2协议,可以很多的减少报文体积,提高传输效率 | 基于HTTP1.1的协议,请求会包含很多无用的内容;基于HTTP2,简单的封装一下可以作为RPC来使用 |
性能消耗 | 可以基于thrift实现高效的二进制传输 | 大部分通过json来实现,字节大小和序列化耗时 |
负载均衡 | 自带 | 需要配置Nginx |
服务治理 | 自动通知,不影响上游 | 需要事先通知 |
场景 | 内部的服务调用,性能消耗低,传输效率高,服务治理方便 | 对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用 |
Redis
使用Redis存一个有效时间值
@Autowired
private StringRedisTemplate redisTemplate;
redisTemplate.opsForValue().set(K key, V value, long timeout,TimeUnit unit)
分布式锁
Redis分布式锁是一种基于Redis实现的分布式锁,可以保证多个进程或线程在同时访问共享资源时只有一个能够获得锁。
实现方式:
数据类型
- 字符串String
- 列表List
- 集合Set
- 哈希Hash
- 有序集合Zset
- 基数统计HyperLogLogs
- 位图BitMaps
- 地理位置Geospatial
内存淘汰策略
- 从已设置过期时间结果集中选择最少使用的数据
- 从已设置过期时间结果集中选择将要过期的数据
- 从已设置过期时间结果集中随机选择
- 移除最少使用的
- 随机移除
- 内存满了,直接拒绝写入
持久化机制
- RDB:占用空间小、存储速度慢、恢复速度快、会丢失数据、启动优先级低
将内存中的数据写入到磁盘中,通过fork一个子进程来生成RDB文件
- AOF:占用空间大、存储速度快、恢复速度慢、启动优先级高
以独立日志的方法记录每次写命令,重启时会重新执行AOF文件中的命令达到恢复数据目的
为什么快
- 纯内存数据库,比磁盘IO读取存储要快
- 使用的是非阻塞IO、IO多路复用,使用单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争
- 采用单线程的模型,保证操作的原子性,减少了线程的上下文切换和竞争
- 避免了多线程的锁消耗
Java基础
Java8新特性
- lambda表达式:是一种匿名函数,可以将其作为参数传递,也可以作为方法返回值
- Steam API:提供一种新的处理集合数据的方式,可以方便地进行过滤、映射、排序等操作。
- 允许在接口中定义默认方法和静态方法,可以在实现类中重写
- Optional类
- 新的时间和日期API
动态代理
需要实现接口(invocationHanlder),在invoke方法定义增加内容和调用目标对象的方法,通过Proxy.newProxyInstance(目标对象.classLoder,目的对象.interfaces,代理对象)来创建代理对象实例。
需要引入第三方依赖,不能用于增强声明final的类或方法,因为底层是使用字节码技术生成被代理类的子类。实现MethodInterceptor接口,通过intercept方法来增强。通过CglibProxy().getProxy(目标对象.class)来获取代理对象。
HashMap
jdk1.8- | jdk1.8+ | |
---|---|---|
存储结构 | 数组+链表 | 数组+链表+红黑树 |
初始化方法 | 单独的方法:inflateTable() | 集成到扩容函数:resize() |
Hash值计算 | 4次位运算+5次异或运算 | 一次位运算+一次异或运算 |
存储数据 | 无冲突直接存数组;冲突存链表 | 无冲突,直接存数组; 有冲突并且链表长度小于8,存链表; 有冲突并且长度大于8时,转红黑树存入。 |
插入数据 | 头插法 | 尾插法 |
扩容后存储位置 | 全部按照原来的方式重新计算 | 按照扩容后的规律,扩容后位置=原位置/原位置+旧容量 |
实现 | 采用拉链法,将链表和数组结合。创建一个链表数组,数组每一格都是一个链表,遇到冲突将元素加到链表中。 | 主要在解决冲突上有了变化。当链表的长度大于8,元素个数大于64,会转为红黑树,以减少搜索时间。 |
Put() | 计算Key的hashCode,HashCode高低位异或运算得到hash值,再通过hash&(len -1)得到存入的下标(索引); 如果该索引不存在元素,直接存入;... | |
Get() | 通过hash()计算得到具体的索引位置,遍历链表/红黑树,找到和键值相等的元素位置。 | |
扩容机制 | 空参数的构造函数:以默认容量、负载因子、阈值初始化数组,内部是空数组。 有参构造函数:按照指定的参数。第一次Put时会初始化数组,其容量不小于指定容量的2 的幂数,根据负载因子计算阈值。扩容是2n | 空参数的构造函数:实例化的HashMap默认为null,即没有实例化。第一次调用put才会进行扩容,默认16。 有参构造:按照指定的参数。第一次Put时会初始化数组,其容量不小于指定容量的2 的幂数,根据负载因子计算阈值。扩容是2n 首次Put,先触发扩容(初始化),存入数据后判断是否需要扩容 |
Synchronized和volatile
Volatile是线程同步的轻量级实现,性能比synchronized要好,只能应用于变量
多线程访问volatile不会发生阻塞,volatile主要解决变量在多线程之间的可见性
只可以保证数据的可见性,不能保证数据的原子性
Synchronized可以应用变量、方法、代码块
Synchronized解决的是多线程之间访问资源的同步性
可以保证可见性和原子性
锁的类型
- 悲观锁:synchronized、接口Lock的实现类
- 乐观锁:CAS、版本号控制
- 自旋锁:自选锁即是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待(不放弃CPU 资源),然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,此时获取锁的线程一直处于活跃状态(而非阻塞)
- 读写锁
- 可重入锁
Java引用类型
- 强引用:jvm宁愿oom也不会回收的对象
- 软引用:内存不足时会尝试回收
- 弱引用:会被垃圾收集器直接回收,最常见的就是ThrealLocal.
- 虚引用:可以用来判断对象是否被回收
为什么不建议使用Executors来创建线程池
Executors创建线程池,用的是new ThreadPoolExecutor来创建,SingleThreadExecutor和FixedThreadPool时用的无界队列,如果任务过多一致往队列里放,占用的内存增多,最终可能导致内存溢出。同时不能自定义线程名字,不方便排查问题,应该使用new ThreadPoolExecutor来定义。
线程池的拒绝策略
序列化和反序列化
序列化:把对象转换为字节序列的过程
反序列化:把字节序列恢复为对象的过程
序列化针对对象而言,static属性属于类,因此不会被序列化。
只要我们对内存中的对象进行持久化或网络传输,就需要序列化和反序列化。
JSON格式实际上就是将一个对象转化为字符串,服务器和浏览器交互的数据格式JSON就是字符串,而String类实现了Serializable接口,并显示指定SerialVersionUID,而持久化数据库的实体属性也是同样是实现了接口。没有实现,进行序列化可能抛出异常NotSerializableException。
不显式指定UID,JVM在序列化时会根据属性自动生成一个,然后和属性一起序列化,再进行持久化或网络传输;反序列化时,JVM再根据数据生成一个新的ID进行和旧的比较,一致则反序列化成功。如果显式指定,JVM在序列化和反序列化依然会生成,但是是我们指定的值,自然就一致了。
消息队列
如何保证消息的可靠性传输
- 生产者:使用Confirm机制,实现ConfirmCallBack接口
- RabbitMQ:将队列和消息设置为持久化
- 消费者:手动ACK确认机制
MySQL
in和exist的区别
exist子查询不直接返回结果集,返回true或false
in直接返回结果集,主查询再去结果集中查询符合条件的进行输出
查询慢以及优化
慢的可能原因:
- 没有索引或者没有使用到索引
- I/O吞吐量小
- 内存不足,网络速度慢
- 查询的数据量大
- 锁或者死锁
- 返回不必要的行和列
解决方案:
- 纵向、横向的分割表,减小表的尺寸
- 升级硬件
- 根据查询条件,建立索引,优化索引,限制访问结果集的数据量
- 优化SQL语句
-
- SELECT避免*
- 使用Where代替having
- 多表连接使用别名
- 用exist代替in、not exist代替 not in
- 用exist代替distinct
- 避免索引列使用NOT、函数、运算
- 用>=代替>
- 用in代替or
- 避免索引列使用IS NULL或IS NOT NULL
- 避免使用 和 !=
- 为搜索字段建立索引
索引
- 哈希索引:采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要像B+树从根节点开始遍历,只需要一次哈希算法即可,是无序的。优势在于等值查询,但是如果重复键较多时,哈希冲突比较严重。不适合需要范围查询、排序查询的场景。
- B树索引:B树是一种多路平衡树,使用这种存储结构来存储大量数据,整个高度比二叉树要矮。树的高度能决定磁盘IO的次数,磁盘IO次数越少,性能的提升也越大,因此比二叉树更适合。
- B+树索引:所有数据都存储在叶子节点上,非叶子节点只存储索引。叶子结点中的数据使用双向链表的方式进行关联。升序排序
-
- B+树的非叶子节点不存储数据,所以树的每一层就能够存储更多的索引(键值)数量,也就是说B+树在层高相同的情况下,比B树存储的数据量更多,间接地减少磁盘IO次数。
- 针对范围查询的情况,B+树遍历全部数据只需要遍历叶子节点,而B树则需要遍历整棵树,因此范围查询/全表查询B+树更高效率。
- 适合范围查询、支持联合索引的最左侧原则、支持order by排序、可以使用like模糊查询,以上哈希都不行
行级锁
优点:只锁定行,多线程访问冲突概率小;回滚更改的数据也少;可以长时间锁定单一的行
缺点:加锁速度慢;占用更多的内存
千万级表优化
- 读写分离
- 水平拆分
- 垂直拆分
- 性能优化
-
- 硬件和操作系统层面:CPU、内存大小、磁盘读写速度、网络带宽
- 架构设计层面:主从集群、读写分离、分库分表、热点数据使用缓存数据库
- SQL优化
CPU飙升
可能是因为创建过多的线程,有线程一直占用CPU资源无法被释放,如死循环
死锁
使用jstack导出dump日志,分析日志定位具体死锁的代码。
死锁条件:互斥、不可剥夺、循环等待、请求和保持
避免死锁:
- 设置事务等待锁的超时时间(innodb_lock_wait_timeout)
- 开启主动死锁检查(innodb_deadlock_detect)
- 对于更新频繁的字段,采用唯一索引
磁盘打满如何处理
- 备份
- 清理日志文件
- 压缩表格
- 删除不必要的数据
- 增加磁盘空间
Linux
常用命令
文件和目录:cd\pwd\ls\cp\mv\rm\mkdir\
查看文件:cat\head\tail
搜索:find -name \whereis
文件权限:chmod\chown\chgrp
文本处理:grep
打包解压:tar
系统和关机:shutdown\reboot\logout
进程相关:top\jps\ps\kill\killall
ElasticSearch
底层原理
倒排索引,将数据按分词器进行拆分多个词语,将数据Id和词语做关联关系。