文章目录
1、自我介绍
面试官您好,我叫xxx。我从接触c、c++基础编程语言开始,然后系统学习了计算机网络,数据库概论,数据结构以及算法分析等核心课程,然后还深入学习了java,php等主流开发语言。之后较为全面地学习了spring、springmvc、springboot等主流的开发框架,本人勤奋好学,不仅对学校课程学以致用,还自学了redis、mybatis、vue等流行框架,并且都会通过写博客来记录并巩固自己的学习成果。态度决定一切,本人抗压能力良好,性格开朗有上进心,实习期间分配的任务我都能够按时按量地完成,遇到问题我喜欢直面挑战,能够为团队作出有力的贡献。虽然我的实际工作经验还不是很丰富,但相信有了扎实的专业基础知识和实习期间的实践经验,加上好学上进的精神,我相信能够胜任贵公司应聘岗位需求,希望能够给我这次机会,谢谢。
2、项目经验
1、jwt结构
Header(tye、alg)、Payload(有效信息:iss、exp、sub)、Signature
主要是把头部的base64UrlEncode与负载的base64UrlEncode拼接起来,再使用头部声明的算法进行加密,加密结果再进行base64url加密,最终得到的结果作为签名部分。
服务器端接受token,使用密钥secret、header和payload生成signature,对比是否一致
2、springboot定时任务
首先都要使用@EnableScheduling 开启定时任务
1、基于注解(@Scheduled):使用Cron表达式设置时间,单线程,方法级别注解
2、实现接口(SchedulingConfigurer),重写configureTasks方法动态设置执行时间。
3、基于注解设定多线程定时任务,在1基础上增加@EnableAsync开启多线程,@Async在方法上
3、jwt设置过期
3、mysql
1、mysql(事务)的四大特性?
一哥驰援
1、一致性(Consistency)在事务开始之前和事务结束以后,数据库的完整性没有被破坏(符合规则)
2、隔离性(Isolation)防止多个事务并发执行时导致数据的不一致
3、持久性(Durability)事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
4、原子性(Atomicity)要么全部完成,要么全部不完成
2、数据库并发情况下可能出现的问题?
更脏不换
1、更新丢失 多个事务操作更新丢失
2、脏读 事务回滚后,读取的是未提交的
3、不可重复读 同个事务的数据不一致
4、幻读 Insert操作多出数据
3、数据库的隔离级别?
从高->低:
1、Serializable(序列化):事务序列化执行
2、repeatable read(可重复读):一个事务内两次读到的数据是一样的
3、read committed(读提交):写的时候不能读
4、read uncommitted(读未提交):写还没提交的时候可以读,但不能同时写
mysql默认Repeatable read
4、悲观锁和乐观锁?
1、悲观锁:对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度
使用了select…for update
的方式,查询出信息后就把当前的数据锁定,直到我们修改完毕后再解锁
2、乐观锁:使用版本号或者时间戳
5、怎么建立索引
1、包含有NULL值的列不建议索引
2、短索引,串列进行索引如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。
3、在WHERE、JOIN和order by中出现的列需要建立索引
4、建立组合索引,需要符合最左缀匹配原则
在MySQL建立联合索引时会遵守最左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。
6、高效的查询语句
1、不要在列上进行运算(!=)、函数(substring),这将导致索引失效而进行全表扫描
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
3、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2=100应改为:select id from t where num=100*2
4、不要使用select *
5、在以通配符%和_开头作查询时,MySQL不会使用索引,可设置前缀
7、数据库引擎
MyISAM、MyISAM、Memory
8、 数据库中 MyISAM 和 InnoDB 的区别
事务:
MyISAM :不支持事务,但是每次查询都是原子的。
InnoDB :支持 ACID 的事务,支持事务的四种隔离级别。
并发:
MyISAM :支持表级锁,即每次操作是对整个表加锁
InnoDB :支持行级锁及外键约束
索引:
MyISAM :采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,
InnoDB :主键索引采用聚集索引,索引的数据域存储数据文件本身。辅索引的数据域存储主键的值,因此首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
9、如何通俗地理解三个范式
第一范式:1NF 是对属性的原子性约束,字段不可分解,地址常用字段可分解。
第二范式:2NF 是非主键列完全依赖主键,例如一个人订多个房间,人的信息会重复,因为只依赖于人id,这只是部分依赖。
第三范式:3NF 是非主键列直接依赖主键,例如订单商品,虽然不重复,但是是商品信息直接依赖商品号,间接依赖订单号。
4、redis
1、缓存击穿
某个缓存的key失效,导致大量的请求全部转向查询数据库,导致数据库压力过大。
解决方法:
- 对热点key设置永不过期或提前预热数据
- 加互斥锁,缓存中没有热点key对应的数据时,由获得锁的线程去读取数据库然后设置缓存,没有获得锁的请求等待100ms,再重新去缓存取数据。
- 接口限流与熔断,降级。
2、限流与熔断,降级
解决接口级故障的核心思想是优先保障核心业务和优先保障绝大部分用户。比如登录功能很重要,当访问量过高时,停掉注册功能,为登录腾出资源
熔断:一般是某个服务故障或者是异常引起的,当某个异常条件被触发,直接熔断整个服务,而不是一直等到此服务超时,为了防止防止整个系统的故障。
降级:服务器当压力剧增的时候,根据当前业务情况及流量,对一些服务和页面进行有策略的降级。以此缓解服务器资源的的压力,以保证核心业务的正常运行。
限流:单位时间限制用户访问次数,限制同个ip的访问限制。
大部分客户的得到正确的相应。
3、缓存穿透
Redis 缓存穿透指的是大量请求Redis缓存时,查找不到对应key,导致请求每次都会触发查询数据库,导致数据库压力过大。如果请求量大的时候极有可能压垮数据库。
解决方法:
- 做好参数校验,无效的请求直接返回,只能避免一部分情况,攻击者总是可以找到一些没有覆盖的情况。
- 如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
- **推荐:**采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
4、布隆过滤器
直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。
-
首先需要k个hash函数,每个函数可以把key散列成为1个整数
-
初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
-
某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
-
判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。
优点:不需要存储key,节省空间
缺点:找不到说明一定不存在,找到不一定存在。
5、缓存雪崩
缓存雪崩主要指的是短时间内大量key失效,导致所有请求全部转向数据库,导致数据库压力过大。
- 在设置缓存事件时加随机值,避免缓存扎堆失效。
- 可以把缓存层设计成高可用的,个别节点宕掉,依然可以提供服务。利用sentinel(哨兵)或cluster(集群)实现。
- 双缓存机制,缓存A的失效时间为20分钟,缓存B没有失效时间,从缓存A读取数据,缓存A中没有时,去缓存B中读取数据,并且启动一个异步线程来更新缓存A。
- 加锁排队(容易造成堵塞,不推荐)
6、缓存一致性
1、先操作缓存,在操作数据库:(删除缓存后线程B访问缓存为空,访问数据库写入缓存造成脏数据)
2、先操作数据库,再操作缓存:删除缓存可能失败
策略:通过数据库的binlog日志来异步淘汰key,以mysql为例 可以使用阿里的canal将binlog日志采集发送到MQ队列里面,然后通过ACK机制 确认处理 这条更新消息,删除缓存,保证数据缓存一致性。
3、大量请求只操作缓存,后期再入db
binlog,即二进制日志,它记录了数据库上的所有改变,并以二进制的形式保存在磁盘中。它可以用来查看数据库的变更历史、数据库增量备份和恢复、Mysql的复制(主从数据库的复制)。
canalcanal的工作原理就是把自己伪装成MySQL slave,模拟MySQL slave的交互协议向MySQL Mater发送 dump协议,MySQL mater收到canal发送过来的dump请求,开始推送binary log给canal,然后canal解析binary log,再发送到存储目的地,比如MySQL,Kafka,Elastic Search等等。主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费。
7、ack消息确认机制
ACK机制是消费者从MQ收到消息并处理完成后,反馈给MQ,MQ收到反馈后才将此消息从队列中删除。如果一个消费者在处理消息出现了网络不稳定、服务器异常等现象,那么就不会有ACK反馈,RabbitMQ会认为这个消息没有正常消费,会将消息重新放入队列中。
8、redis集群
9、redis持久化
rdb:在指定时间间隔后,(子线程)将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复,save和bgsave,默认是rdb
命令:
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内容空间。
aof:将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
优点
- 每一次修改都会同步,文件的完整性会更加好
- 没秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
命令:
10、过期键的淘汰策略
当前Redis3.0版本支持的淘汰策略有6种:
\1. volatile-lru:从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
\2. volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。
\3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key。
\4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。
\5. allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。
\6. no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
11、zset中跳跃表的实现
12、redis的事务管理
13、redis常见api
5、spring
1、spring中的动态代理机制:
代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
1、JDK动态代理(面向接口)
2、CGLIB动态代理 :当目标对象没有实现的接口时
JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
2、jdk动态代理
jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
1.创建一个实现接口InvocationHandler的类,要代理的对象作为属性,它必须实现invoke方法,
2.通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)创建一个代理
3.通过代理对象调用方法
3、CGLIB动态代理
CGLib其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。
4、jdk和cglib使用情况
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
5、jdk和cglib区别
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2、CGLIB是针对类实现代理,对于final方法,无法进行代理
cglib使用字节码扩展父类运行效率更高,但是创建慢,jdk运行慢,反射创建快
6、使用cglib
1、添加CGLIB库,SPRING_HOME/cglib/*.jar
2、在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
7、bean的作用域
8、bean的生命周期
-
实例化 Instantiation
-
属性赋值 Populate
-
初始化 Initialization
-
销毁 Destruction
生命周期接口
至于剩下的两个生命周期接口就很简单了,实例化和属性赋值都是 Spring 帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。
InitializingBean 对应生命周期的初始化阶段,在上面源码的 invokeInitMethods(beanName, wrappedBean, mbd);方法中调用。
有一点需要注意,因为 Aware 方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用 Aware 接口获取的资源,这也是我们自定义扩展 Spring 的常用方式。
除了实现 InitializingBean 接口之外还能通过注解或者 xml 配置的方式指定初始化方法,至于这几种定义方式的调用顺序其实没有必要记。因为这几个方法对应的都是同一个生命周期,只是实现方式不同,我们一般只采用其中一种方式。
DisposableBean 类似于 InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了 DisposableBean 接口的 Bean 然后调用其 destroy() 方法,感兴趣的可以自行跟一下源码。
9、声明式事务
@Transactional
10、AOP编程
11、事务的隔离级别
12、事务的传播行为
6、java基础
1、ArrayList和LinkedList哪个更占空间
一般情况下,LinkedList的占用空间更大,因为每个节点要维护指向前后地址的两个节点,但也不是绝对,如果刚好数据量超过ArrayList默认的临时值时,ArrayList占用的空间也是不小的,因为扩容的原因会浪费将近原来数组一半的容量
2、ArrayList原始容量,扩容机制
10,ArrayList帮我们做了动态扩容的处理,如果发现新增数据后,List的大小已经超过数组的容量的话,就会新增一个为原来1.5倍容量的新数组,然后把原数组的数据原封不动的复制到新数组中,再把新数组赋值给原来的数组对象。
3、ArrayList实现原理
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长。两个属性,elementData存储ArrayList内的元素,size表示它包含的元素的数量。
4、transient关键字
用transient关键字标记的成员变量不参与序列化过程
5、arraylist线程安全性
对ArrayList进行添加元素的操作的时候是分两个步骤进行的,即第一步先在object[size]的位置上存放需要添加的元素;第二步将size的值增加1。由于这个过程在多线程的环境下是不能保证具有原子性的,因此ArrayList在多线程的环境下是线程不安全的。
1、用vector类:自动增长的对象数组,其add操作是用synchronized关键字修饰的,从而保证了add方法的线程安全。保证了数据的一致性,但由于加锁导致访问性能大大降低。
2、用Collections工具类将线程不安全的ArrayList类转换为线程安全的集合类。Collections.synchronizedList
3、使用Arrays的copy方法(写时复制,读写分离)当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器
8、对java泛型的理解
泛型本质是将数据类型参数化,它通过擦除的方式来实现,使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉,这个过程就称为类型擦除。声明了泛型的 .java 源代码,在编译生成 .class 文件之后,泛型相关的信息就消失了。可以认为,源代码中泛型相关的信息,就是提供给编译器用的。泛型信息对 Java 编译器可以见,对 Java 虚拟机不可见。
9、泛型的应用
1、泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
2、泛型接口
3、泛型方法
public class Teacher {
public static <T> T println(T t){
System.out.println(t);
return t;
}
}
10、泛型的作用
1、避免集合类型元素在运行期出现类型装换异常,增加编译时类型的检查。
2、解决重复代码的编写,能够复用代码。
11、类加载机制
7、linux
1、linux查看日志
tail(尾)、head(头)、cat(全部)、more(分页,只能向后,加载全部)、less(分页,前后都可以,less不加载全部更快)
8、网络协议
1、http和htttps区别
http是超文本传输协议,信息是明文传输,HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、身份认证的网络协议.客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
2、输入一个url地址发生了什么
1、DNS 解析:将域名解析成 IP 地址
2、TCP 连接:TCP 三次握手
3、发送 HTTP 请求
4、服务器处理请求并返回 HTTP 报文
5、浏览器解析渲染页面
6、断开连接:TCP 四次挥手
3、网络分层
应用层:各种软件应用产生数据报文(FTP,HTTP,DNS)
传输层:对数据报文在链路上传输进行控制的, 作用在于告诉数据报文如何去目的地(TCP,UDP)
网络层:是对数据报文进行地址寻址的协议,作用在于告诉数据报文去哪(IP,ICMP,RIP)
数据链路层:告诉数据报文如何在路上行走(ARP,PPP)
4、三次握手
1、客户端发送一个数据包到服务器端口提示要发送请求
2、服务器发回响应包以示传达确认信息
3、客户端再回传一个数据包,代表连接成功,准备接受,开始发送了
5、为什么需要三次握手
2次握手可以完成连接的建立,但会带来资源浪费的问题,三次握手可以防止已失效的连接请求报文段突然又传送到了服务端,造成资源浪费。
6、http请求报文结构
请求报文由请求行(request line)、请求头(header)、请求体
1、请求行包含请求方法、URL、协议版本
例如:POST /chapter17/user.html HTTP/1.1
2、请求头包含请求的附加信息,由关键字/值对组成
Host:表示主机名,User-Agent:请求发出者,Accept-Encoding:接收编码,Cookie、Accept、content-type
3、请求体:请求参数数据
7、http响应报文结构
响应报文由响应行(request line)、响应头部(header)、响应主体
1、 响应行包含:协议版本,状态码,状态码描述
- 1xx:指示信息–表示请求已接收,继续处理。
- 2xx:成功–表示请求已被成功接收、理解、接受。
- 3xx:重定向–要完成请求必须进行更进一步的操作。
- 4xx:客户端错误–请求有语法错误或请求无法实现。403 (FORBIDDEN),404 (NOT FOUND)
- 5xx:服务器端错误–服务器未能实现合法的请求。
2、响应头部包含响应报文的附加信息,Allow(服务器支持的方法),Server(服务器名字),Content-Type
3、 服务器返回给客户端的实际内容
8、浏览器渲染机制
- 根据 HTML 解析出 DOM 树
- 根据 CSS 解析生成 CSS 规则树
- 结合 DOM 树和 CSS 规则树,生成渲染树
- 根据渲染树计算每一个节点的信息
- 根据计算好的信息绘制页面
9、四次挥手
- 第一次挥手:由浏览器发起的,发送给服务器,表示请求报文发送完毕,准备关闭连接
- 第二次挥手:由服务器发起的,告诉浏览器,表示请求报文接受完毕
- 第三次挥手:由服务器发起,告诉浏览器,表示响应报文发送完毕
- 第四次挥手:由浏览器发起,告诉服务器,我响应报文接受完毕,我关闭了,你也关闭
10、Post和Get的区别
GET和POST本质上都是TCP链接,区别:
1、传送方式:get通过地址栏传输,post通过报文传输。
2、传送长度:get参数有长度限制(受限于url长度),而post无限制
3、get参数通过url传递,post放在request body中
3、GET常用于获取信息,POST 用于修改服务器上的数据
11、TCP和UDP
9、mybatis
1、什么是mybatis
Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
2、mybatis的优点
1、SQL语句写在xml中,解除了与程序代码的耦合,便于统一管理。
2、减少代码量,无需手动开关连接。
3、支持对象与数据库的ORM字段关系映射。
3、mybatis的缺点
1、当字段多、关联表多时,sql语句编写工作量较大。
2、sql语句依赖数据库,移植性较差。
4、mybatis和hibernate的区别
1、针对高级查询,Mybatis需要手动编写SQL语句和ResultMap。而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于业务流程。
1、Mapper 接口的工作原理
JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而 执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。
2、#{}和${}的区别是什么?
#{}是预编译处理,KaTeX parse error: Expected 'EOF', got '#' at position 22: …替换。 Mybatis 在处理#̲{}时,会将 sql 中的#{…{}时,就是把${}替换成变量的值。 使用#{}可以有效的防止 SQL 注入,提高系统安全性。
4、mybatis常用标签
5、mybatis的接口方法查找策略
Mapper 接口里的方法是使用 全限名(namespace对应接口)+方法名(sql标签的id对应方法)的保存和寻找策略。
<mapper namespace="com.kuang.mapper.StudentMapper">
<select id="getStudents" resultMap="Student">
select * from student
</select>
</mapper>
6、collection和association的应用
association:一对一,多对一
<select id="getStudents" resultMap="StudentTeacher2" >
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--关联对象property 关联对象在Student实体类中的属性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
collection:一对多
<select id="getAllTeacher" resultMap="TeacherStudent">
select t.id tid,t.name tname,s.id sid,s.name sname
from teacher t,student s
where t.id=s.tid
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"/>
<collection property="studentList" javaType="List" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
7、在 mapper 中如何传递多个参数?
1、#{0}代表接收第一个参数,#{1}代表第二 参数。
2、@param(value)注解,#{value}
3、多个参数封装成 map,#{map的键}取值
8、Mybatis和Hibernate
Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自 动 ORM 映射工具。
Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
9、mybatis联合查询和嵌套查询
联合查询是几个表联合查询,只查询一次, 通过在 resultMap 里面配置 association 节点配置一对一的类就可以完成
嵌套查询是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面 查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置。
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">-->
<association property="teacher" column="tid" javaType="Teacher" select="getThisTeacher"/>
</resultMap>
<select id="getThisTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
10、Mybatis延迟加载
MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力
启用:lazyLoadingEnabled=true|false。
11、延迟加载的原理
代理机制,首先生成代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来, 然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。
12、Mybatis 的使用
1、SqlSessionFactoryBuilder的build方法获取sqlsessionfactory
2、sqlSessionFactory的openSession方法获取SqlSession
3、SqlSession的getMapper(class)获取mapper接口
4、调用接口方法获取结果(session.close关闭)
SqlSessionFactory(相当于连接池) 的生命周期就等同于 MyBatis 的应用周期,SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。
13、mybatis的缓存
Mybatis的一级缓存是默认开启的,指的是Mybaits中SqlSession对象的缓存。当我们执行查询之后,查询的结果会同时存入到 SqlSession为我们提供的一块区域中。
Mybatis的二级缓存是默认关闭的,它指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其二级缓存。
14、怎么开启二级缓存
1、打开总开关,只需要在mybatis总配置文件中加入一行设置
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
2、在需要开启二级缓存的mapper.xml中加入cache标签
<cache/>
15、mybatis的配置文件
configuration(配置)properties(属性):导入db.properties文件,datasource中使用${}取值settings(设置):设置懒加载(lazyLoadingEnabled)、缓存(cacheEnabled)、typeAliases(类型别名):优化xml中全限定类名plugins(插件)environments(环境配置) environment(环境变量) transactionManager(事务管理器) dataSource(数据源) (配置数据库账号密码url)mappers(映射器)每一个mapper.xml文件都需要在配置文件中注册
10、多进程,多线程
1、进程的理解
概念:进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。
结构:进程一般由程序,数据集合和进程控制块三部分组成。
1、程序(代码区)用于描述进程要完成的功能,是控制进程执行的指令集;
2、数据集合是程序在执行时所需要的数据和工作区,存放已初始化的全局变量、静态变量(全局和局部)、常量数据;
3、程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志。
2、进程的特性
动态性:进程是程序的一次执行过程,是临时,有生命期而且动态产生和消亡的;
并发性:任何进程都可以同其他进行一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序,数据和进程控制块三部分组成
2、线程的理解
概念:线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
特性:各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。
结构:一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。
3、进程和线程的关系和区别
1、进程是操作系统分配资源的最小单位,线程是程序执行的最小单位
2、一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
3、进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
4、调度和切换:线程上下文切换比进程上下文切换要快得多
5、属于同一进程的线程,堆是共享的,栈是私有的,属于同一进程的所有线程都具有相同的地址空间。
4、进程通信的方法
5、线程通信的方法
6、多进程和多线程使用的场景
1)需要频繁创建销毁的优先用线程( 进程的创建和销毁开销过大 )
原因请看上面的对比。
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的
2)需要进行大量计算的优先使用线程( CPU频繁切换 )
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
这种原则最常见的是图像处理、算法处理。
3)强相关的处理用线程,弱相关的处理用进程
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。
4)可能要扩展到 多机分布 的用 进程 , 多核分布 的用 线程
原因请看上面对比。
5)都满足需求的情况下,用你最熟悉、最拿手的方式
至于“数据共享、同步”、“ 编程 、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。
需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。
https://blog.csdn.net/weixin_39731083/article/details/82015830
7、CPU、操作系统和应用程序
计算机的核心是CPU,它承担了所有的计算任务,而操作系统是计算机的管理者,它负责任务的调度,资源的分配和管理,统领了整个计算机硬件,应用程序是具有某种功能的程序,程序是运行于操作系统之上的。
8、任务调度
大部分操作系统的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。
9、java创建线程的方式
1、继承Thread类,重写run方法,调用线程对象的start()方法
2、实现Runnable接口,实现run方法
3、实现Callable接口,实现call方法
4、利用线程池创建线程
10、线程的生命周期
1 、初始状态 : 创建了线程对象,但是没有调用 start 方法开启线程。
2 、就绪状态: 调用了 start 方法,到线程队列中排队,抢占 cpu 的时间片。但是还没有抢占上
3 、运行状态: 抢到了 cpu 的时间片。正在执行。
4 、 终止状态: cpu 时间片之内完成了线程的执行。
5 、 阻塞状态: cpu 时间片执行期间,遇到了意外的情况。
11、锁的种类
12、线程池
使用spring中的ThreadPoolTaskExecutor,因为可以使用自定义任务装饰器实现TaskDecorator接口来跟踪多任务线程。
13、线程池的创建参数
newSingleThreadExecutor
11、servlet编程
1、webxml注册servlet
<servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>MyServlet</servlet-class> <url-pattern></url-pattern></servlet>
2、servlet常用对象
servletconfig(可读取web.xml中的初始化参数) 单个 this.getServletConfig
servletcontext(整个应用的配置信息)
request对象
response对象
3、servlet是单例的
4、实现servlet有五个需要重写的方法
init【初始化】,destroy【销毁】,service【服务】,ServletConfig【Servlet配置】,getServletInfo【Serlvet信息】
5、HttpServlet类
HttpServlet类已经实现了Servlet接口的所有方法,我们只需重写doGet()和doPost(),方法两个参数httpservletrequest和httpservletresponse
6、请求流程
对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
7、ServletContext
所有servlet共享一个servletcontext,可以通过此进行通信,类似一个容器,this.getServletContext()
ServletContext的setAttribute(String name,Object obj)
8、重定向和转发
重定向:
response.sendRedirect(“**.jsp”)
内部封装:
response.setStatus(302); //302代表重定向
response.setHeader(“Location”, “/zhongfucheng/index.jsp”);
转发(服务器之间,浏览器无感):
//获取到requestDispatcher对象,跳转到index.jsp
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/index.jsp");
//调用requestDispatcher对象的forward()实现转发,传入request和response方法
requestDispatcher.forward(request, response);
9、httpservletrequest和httpservletresponse
getRequestURL、getHeaders、getParameter
10、防盗链
//获取到网页是从哪里来的
String referer = request.getHeader(“Referer”);
//如果不是从我的首页来或者从地址栏直接访问的,
if ( referer == null || !referer.contains("localhost:8080/zhongfucheng/index.jsp") ) { response.sendRedirect("/zhongfucheng/index.jsp"); return;}
11、编码
tomcat默认是iso 8859-1(不支持中文),也可以通过response.setCharacterEncoding(“UTF-8”);设置编码格式
浏览器采用的是gbk
string.getBytes(“默认是gb2312,支持中文编码”)
//设置头信息,告诉浏览器我回送的数据编码是utf-8的 response.setHeader("Content-Type", "text/html;charset=UTF-8");
解决编码方法
response.setContentType("text/html;charset=UTF-8");//集合了方法1和response.setCharacterEncoding("UTF-8");
12、get解决编码
get的请求不是在请求体中而是在路径中,所以request.setCharacterEncoding(“UTF-8”);无效,通过编码反查解决
//此时得到的数据已经是被ISO 8859-1编码后的字符串了,这个是乱码
String name = request.getParameter("username");
//乱码通过反向查ISO 8859-1得到原始的数据
byte[] bytes = name.getBytes("ISO8859-1");
//通过原始的数据,设置正确的码表,构建字符串
String value = new String(bytes, "UTF-8");
12、面试场景
1、查询千万级数据
2、高并发请求处理
https://www.cnblogs.com/doubilaile/p/8295213.html
https://www.cnblogs.com/ljl123/articles/9049603.html
https://www.zhihu.com/question/266766495
https://bbs.csdn.net/topics/392032759
13、maven
1、maven的作用
2、标签的作用
3、maven的使用
14、git
15、项目经验
1、微服务架构
单体式应用内部包含了所有需要的服务。而且各个服务功能模块有很强的耦合性,也就是相互依赖彼此,很难拆分和扩容
优点:隔离性、可扩展性、易部署、易优化
缺点:管理困难、监控麻烦
方法:
1、服务注册和发现、服务提供方上报地址给服务注册中心,调用方订阅 服务变更通知、动态的接收服务注册中心推送的服务地址列表。
2、cat和skywalking进行服务监控
3、熔断、隔离、限流和降级、超时机制、弱依赖
2、实际上RPC也就是一种编程模型,初衷就是你可以不在乎底层的网络技术协议而实现远程调用
无论基于Http协议还是基于TCP协议都不影响他是RPC框架
1.减少传输量。
2.简化协议。
3.用长连接,不再每一个请求都重新走三次握手流程
3、使用Feign做rpc调用
Feign封装了Ribbon和Hystrix也就是客户端负载均衡以及服务容错保护,简化了我们自行封装RestTemplate服务调用客户端的开发量,Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。通过自定义的编码解码器以及错误处理。
1、过@EnableFeignClients注解开启Spring Cloud Feign的支持功能
配置
server: port: 8082#配置eureka、服务发现注册eureka: client: service-url: defaultZone: http://localhost:8761/eureka instance: status-page-url-path: /info health-check-url-path: /health#服务名称spring: application: name: product profiles: active: ${boot.profile:dev}#feign的配置,连接超时及读取超时配置feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic
原理:当定义的Feign中的接口被调用时,通过JDK的动态代理来生成RequestTemplate。在使用@FeignClient注解的时候 是默认使用了ribbon进行客户端的负载均衡的,默认的是轮询的策略,那么如果我们想要更改策略的话,需要修改消费者yml中的配置。策略:随机、轮询、重试最空闲连接。
4、oss对象存储和腾讯云点播
bucket:存储空间类似盘、存储类型、存储权限
endpoint:地域节点
AccessKeyId、AccessKeySecret
重命名文件操作实际是通过CopyObject接口来实现、名字生成算法
客户端上传视频后返回唯一id,服务端根据id获取视频播放地址,然后对视频进行转码等处理
转码是将视频码流转换成另一个视频码流的过程,是一种离线任务。通过转码,可以改变原始码流的编码格式、分辨率和码率等参数,从而适应不同终端和网络环境的播放。使用转码功能可以实现:流畅、标清、高清以及超清
自适应码流播放器能够根据当前带宽,动态选择最合适的码率播放
5、唯一id生成算法
snowflake:时间(毫秒级)+集群ID+机器ID+序列号
优点:递增数据库索引、内存生成不依赖db
6、elasticsearch基本原理和数据结构
elk:Elasticsearch,
Logstash和
Kibana,基于restful风格进行索引和文档的增删查改
倒排索引:可以使用ik分词器、text和keyword
7、redis数据结构
Key是String类型,Redis 支持的 value 类型包括了 String、List 、 Hash 、 Set 、 Sorted Set 、BitMap。
由于需要记录点赞人和被点赞人,还有点赞状态(点赞、取消点赞),还要固定时间间隔取出 Redis 中所有点赞数据,分析了下 Redis 数据格式中 Hash 最合适。
Scheduler:0 0 */2 * * ?每两个小时入库一次
一个跳跃表应该有若干个层(Level)链表组成;跳跃表中最底层的链表包含所有数据; 每一层链表中的数据都是有序的;
一主二从:slaveof配置主从,info replication查看,从机只能读不能写
哨兵模式解决选举:主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。
sentinel monitor mymaster 127.0.0.1 6379 1(为1 不可用)
8、线程池
corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去。
maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量。
keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁。
unit:keepAliveTime的单位。
workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种。
threadFactory:线程工厂,用于创建线程,一般用默认即可。
handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务
- 当一个任务被提交后,线程池首先检查正在运行的线程数是否达到核心线程数,如果未达到则创建一个线程。
- 如果线程池内正在运行的线程数已经达到了核心线程数,任务将会被放到 BlockingQueue 内。
- 如果 BlockingQueue 已满,线程池将会尝试将线程数扩充到最大线程池容量。
- 如果当前线程池内线程数量已经达到最大线程池容量,则会执行拒绝策略拒绝任务提交
1、直接丢弃(DiscardPolicy)
2、丢弃队列中最老的任务(DiscardOldestPolicy)。
3、抛异常(AbortPolicy)
4、将任务分给调用线程来执行(CallerRunsPolicy)。
队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列做为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM
一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量
9、kafka
- Producer:Producer即生产者,消息的产生者,是消息的入口。
- Broker:Broker是kafka实例,每个服务器上有一个或多个kafka的实例,我们姑且认为每个broker对应一台服务器。每个kafka集群内的broker都有一个不重复的编号,如图中的broker-0、broker-1等……
- Topic:消息的主题,可以理解为消息的分类,kafka的数据就保存在topic。在每个broker上都可以创建多个topic。
- Partition:Topic的分区,每个topic可以有多个分区,分区的作用是做负载,提高kafka的吞吐量。同一个topic在不同的分区的数据是不重复的,partition的表现形式就是一个一个的文件夹!
- Replication:每一个分区都有多个副本,副本的作用是做备胎。当主分区(Leader)故障的时候会选择一个备胎(Follower)上位,成为Leader。在kafka中默认副本的最大数量是10个,且副本的数量不能大于Broker的数量,follower和leader绝对是在不同的机器,同一机器对同一个分区也只可能存放一个副本(包括自己)。
- Message:每一条发送的消息主体。
- Consumer:消费者,即消息的消费方,是消息的出口。
- Consumer Group:我们可以将多个消费组组成一个消费者组,在kafka的设计中同一个分区的数据只能被消费者组中的某一个消费者消费。同一个消费者组的消费者可以消费同一个topic的不同分区的数据,这也是为了提高kafka的吞吐量!
- Zookeeper:kafka集群依赖zookeeper来保存集群的的元信息,来保证系统的可用性。
10、日志
使用spring的ThreadPoolTaskExecutor,需要指定TaskDecorator,在装饰代码中获取到ThreadLocal的值。
原理:自定义ContextDecorator配合MDC(线程安全的存放诊断日志的容器)进行线程跟踪
11、nginx
代理服务器可以帮助我们接收用户的请求,然后将用户的请求按照规则帮我们转发到不同的服务器节点之上。这个过程用户是无感知的,用户并不知道是哪个服务器返回的结果,我们还希望他可以按照服务器的性能提供不同的权重选择。
Nginx提供的负载均衡策略有2种:内置策略和扩展策略。内置策略为轮询,加权轮询,Ip hash。扩展策略,自定义扩展。动静分离
yum intsall ***tar -zxvf nginx-1.18.0.tar.gzcd nginx-1.18.0//配置upstream lb{ ip_hash server 127.0.0.1:8080 weight=1; server 127.0.0.1:8081 weight=1;}location / { proxy_pass http://lb;}
12、apollo对应的是spring cloud config
是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。
- application (应用)
- environment (环境)
- cluster (集群)
- namespace (命名空间)
@EnableApolloConfig @Value可以获得值
13、Web流多次读写
自定义RequestHandler 继承HttpServletRequestWrapper (装饰者模式)
然后在自定义过滤器中
RequestHandler request = new RequestHandler((HttpServletRequest) servletRequest);
原理,装饰者模式:
RequestHandler 和HttpServletRequest继承同一个接口,但是RequestHandler 又持有HttpServletRequest,
这样在读取request流的时候就是调用的RequestHandler 的方法,这个方法中使用一个byte字节数组来缓存流,从而做到多次读取。
14、git
git log查看历史提交commitId
git revert commitId 回滚
16、优秀面经
1、商品秒杀实现过程:
可以将数据库中所有的SKU缓存到redis中,进行下单的时候,检查redis中是否存在,如果存在并有库存,满足购买条件,则下单,进行支付,当支付的请求进入服务器的时候,首先检查redis中的SKU商品,如果被其他人抢走了,则支付失败;如果剩余的满足订单条件,获取分布式锁,加入消费消息到数据库中,并将消息发送给rabbitMQ-broker,扣除redis库存,支付成功,释放分布式锁,通过中间件更新mysql中的数据,如果服务器崩溃,可以通过数据库消费消息刷新库存和redis缓存
2、 最左缀匹配原理:https://blog.csdn.net/weixin_43268933/article/details/108505662
3、同城灾备、异地多活
在一些极端场景下,有可能所有服务器都出现故障,例如机房断电、机房火灾、地震等这些不卡抗拒因素会导致系统所有服务器都故障从而导致业务整体瘫痪,而且即使有其他地区的备份,把备份业务系统全部恢复到能够正常提供业务,花费的时间也比较长。为了满足中心业务连续性,增强抗风险能力,多活作为一种可靠的高可用部署架构,成为各大互联网公司的首要选择。
1、多活架构的关键点就是指不同地理位置上的系统都能够提供业务服务,这里的“活”是指实时提供服务的意思。
2、与“活”对应的是字是“备”,备是备份,正常情况下对外是不提供服务的,如果需要提供服务,则需要大量的人工干预和操作,花费大量的时间才能让“备”变成“活。
常见的多活方案有同城双活、两地三中心、三地五中心、异地多活等多种技术方案
zookeeper数据同步,mysqlz主从复制
异地多活:考虑数据一致性