目录
开发基础
设计模式
设计模式了解么
请你讲讲单例模式、请你手写一下单例模式
简单来说使用单例模式可以带来下面几个好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
懒汉式(双重检查加锁版本)
public class Singleton {
//volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getInstance() {
//检查实例,如果不存在,就进入同步代码块
if (uniqueInstance == null) {
//只有第一次才彻底执行这里的代码
synchronized(Singleton.class) {
//进入同步代码块后,再检查一次,如果仍是null,才创建实例
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
静态内部类方式
静态内部实现的单例是懒加载的且线程安全。
只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
请你讲讲工厂模式,手写实现工厂模式
Nginx
简单介绍一下Nginx
Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。
反向代理
谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了
- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 *** 了。
- 反向代理: 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
通过下面两幅图,大家应该更好理解(图源:http://blog.720ui.com/2016/nginx_action_05_proxy/):
所以,简单的理解,就是正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。
负载均衡
在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。
Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负载均衡调度算法,感兴趣的可以自行查阅。
负载均衡相比于反向代理更侧重的是将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。
动静分离
动静分离是让站里的页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。
为什么要用 Nginx?
这部分内容参考极客时间—Nginx核心知识100讲的内容。
如果面试官问你这个问题,就一定想看你知道 Nginx 服务器的一些优点吗。
Nginx 有以下5个优点:
- 高并发、高性能(这是其他web服务器不具有的)
- 可扩展性好(模块化设计,第三方插件生态圈丰富)
- 高可靠性(可以在服务器行持续不间断的运行数年)
- 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx服务的情况下升级 Nginx)
- BSD许可证(意味着我们可以将源代码下载下来进行修改然后使用自己的版本)
Nginx 的四个主要组成部分了解吗?
这部分内容参考极客时间—Nginx核心知识100讲的内容。
- Nginx 二进制可执行文件:由各模块源码编译出一个文件
- nginx.conf 配置文件:控制Nginx 行为
- acess.log 访问日志: 记录每一条HTTP请求信息
- error.log 错误日志:定位问题
NginX如何做负载均衡
常见的负载均衡算法有哪些
一致性哈希的一致性是什么意思
一致性哈希是如何做哈希的
Servlet
Servlet生命周期
- 第一次被访问, 创建Servlet实例, 服务器调用init方法.
- Servlet长驻内存.
- 访问Servlet, 服务器调用Service.
- 销毁Servlet之前, 调用destory.
Cookie与Session的区别
- 存储位置不同. Cookie保存在客户端; Session保存在服务器.
- 时长不同. Cookie保存信息时间长, 有半个月; Session保存时间短, 大约30min.
- 安全性不同, Cookie会被清空, 稳定性差, 安全性不高; Session安全性高.
- 性能不同, Cookie性能更高一些; Session当访问量增多时, 会降低服务器性能.
- 数据量不同. 单个Cookie保存数据量不超过4kb, 很多服务器限制最多发送20个Cookie; Session无限制.
GET请求和POST请求方式的区别
- 数据量不同. GET请求通过数据栏发送请求, 数据量不能太大, 通常在1kb左右(有的浏览器限制为4kb); POST通过请求实体提交数据, 理论上没有数据量的限制.
- 安全性不同. GET请求通过地址栏URL提交数据, 相对不安全; POST请求通过请求实体提交数据, 相对更安全.
说一下转发(Forward)和重定向(Redirect)的区别?
转发是服务器行为,重定向是客户端行为。
转发(Forword) 通过RequestDispatcher对象的forward(HttpServletRequest request,HttpServletResponse response)
方法实现的。RequestDispatcher
可以通过HttpServletRequest
的 getRequestDispatcher()
方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。
request.getRequestDispatcher("login_success.jsp").forward(request, response);
重定向(Redirect) 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。
- 从地址栏显示来说:forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的URL。
- 从数据共享来说:forward:转发页面和转发到的页面可以共享request里面的数据。redirect:不能共享数据。
- 从运用地方来说:forward:一般用于用户登陆的时候,根据角色转发到相应的模块。redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。
- 从效率来说:forward:高。redirect:低。
计算机网络
在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议/浏览器输入URL发生了什么?
总体来说分为以下几个过程:
- DNS解析
- TCP连接
- 发送HTTP请求
- 服务器处理请求并返回HTTP报文
- 浏览器解析渲染页面
- 连接结束
TCP和UDP区别? TCP如何保证传输可靠性?
4. TCP 三次握手和四次挥手
为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。
- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
为什么要三次握手
三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。
第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常。
第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常
第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。
为什么要传回 SYN
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。
传了 SYN,为啥还要传 ACK
双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方(主动关闭方)到接收方(被动关闭方)的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。
断开一个 TCP 连接则需要“四次挥手”:
- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号
- 服务器-关闭与客户端的连接,发送一个FIN给客户端
- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1
为什么要四次挥手
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
上面讲的比较概括,推荐一篇讲的比较细致的文章:https://blog.csdn.net/qzcsu/article/details/72861891
5. IP地址与MAC地址的区别
参考:https://blog.csdn.net/guoweimelon/article/details/50858597
IP地址是指互联网协议地址(Internet Protocol Address)IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的MAC地址。
6. HTTP请求,响应报文格式
HTTP请求报文主要由请求行、请求头部、请求正文3部分组成
HTTP响应报文主要由状态行、响应头部、响应正文3部分组成
详细内容可以参考:https://blog.csdn.net/a19881029/article/details/14002273
开发框架和中间件
Spring
Spring中的bean的作用域有哪些?
- singleton:唯一bean实例,Spring中的bean默认都是单例的。
- prototype:每次请求都会创建一个新的bean实例。
- request:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- session:每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP session内有效。
- global-session:全局session作用域,仅仅在基于Portlet的Web应用中才有意义,Spring5中已经没有了。Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。它们基于Portlet容器,可以像Servlet一样处理HTTP请求。但是与Servlet不同,每个Portlet都有不同的会话。
10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?
在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。
Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢? 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。
Spring的bean的生命周期以及更多内容可以查看:一文轻松搞懂Spring中bean的作用域与生命周期
11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?
事务传播行为
事务传播行为(为了解决业务层方法之间互相调用的事务问题): 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
- TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
- TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
说说你对MVC的理解
说说你对AOP的理解
AOP: 面向切面编程。(Aspect-Oriented Programming) 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。
说说你对IoC的理解
IOC(Inversion Of Controll,控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交给IOC容器来管理,并由IOC容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。IOC容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
interface Fruit {
public abstract void eat();
}
class Apple implements Fruit {
public void eat(){
System.out.println("Apple");
}
}
class Orange implements Fruit {
public void eat(){
System.out.println("Orange");
}
}
class Factory {
public static Fruit getInstance(String ClassName) {
Fruit f=null;
try {
f=(Fruit)Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
class Client {
public static void main(String[] a) {
Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
if(f!=null){
f.eat();
}
}
}
Spring框架中用到了哪些设计模式?
- 工厂设计模式:Spring使用工厂模式通过BeanFactory和ApplicationContext创建bean对象。
- 代理设计模式:Spring AOP功能的实现。
- 单例设计模式:Spring中的bean默认都是单例的。
- 模板方法模式:Spring中的jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
Spring事务中的隔离级别有哪几种?
在TransactionDefinition接口中定义了五个表示隔离级别的常量:
ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,Mysql默认采用的REPEATABLE_READ隔离级别;Oracle默认采用的READ_COMMITTED隔离级别。
ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
说说Spring Boot常用的注解
说说Bean的生命周期
Spring Bean 的生命周期简单易懂。在一个 bean 实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个 bean 不在被调用时需要进行相关的析构操作,并从 bean容器中移除。Spring bean factory 负责管理在 spring 容器中被创建的 bean 的生命周期。Bean 的生命周期由两组回调(call back)方法组成。
1.初始化之后调用的回调方法。
2.销毁之前调用的回调方法。
Spring 框架提供了以下四种方式来管理 bean 的生命周期事件:
1、InitializingBean 和 DisposableBean 回调接口
2、针对特殊行为的其他 Aware 接口
3、Bean 配置文件中的 Custom init()方法和 destroy()方法
4、@PostConstruct 和@PreDestroy 注解方式
简单介绍Spring
介绍一下MyBatis的缓存机制
Mybatis的一级缓存是SqlSession级别, 基于 PerpetualCache 的 HashMap 本地缓存。第一次执行select时候会发现sqlsession缓存没有记录,会去数据库查找,然后把结果保存到缓存,第二次同等条件查询下,就会从缓存中查找到结果。另外为了避免脏读,每次执行更新新增删除时候会清空当前sqlsession缓存。
二级缓存是namespace级别的, 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储。同一个namespace下的搜寻语句共享一个二级缓存。如果开启了二级缓存,则先从二级缓存中查找,查找不到则委托为SimpleExecutor查找,而它则会先从一级缓存中查找,查找不到则从数据库查找。
mybaits的二级缓存一般不怎么使用,默认一级缓存是开启的。
说说你对Spring Boot的理解,以及它和Spring的区别?
说说Spring Boot的自动装配
在 Spring 框架***有 5 种自动装配,让我们逐一分析。
1.no:这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean定义中用标签明确的设置依赖关系。
2.byName:该选项可以根据 bean 名称设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的名称自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
3.byType:该选项可以根据 bean 类型设置依赖关系。当向一个 bean 中自动装配一个属性时,容器将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
4.constructor:造器的自动装配和 byType 模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的 bean,那么将会抛出异常。
5.autodetect:该模式自动探测使用构造器自动装配或者 byType 自动装配。首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在 bean 内部没有找到相应的构造器或者是无参构造器,容器就会自动选择 byTpe 的自动装配方式。
说说@Autowired和@Resource注解的区别
说说Spring Boot的启动流程
介绍一下Spring MVC的执行流程
主要组件
(1)前端控制器 DispatcherServlet(不需要程序员开发)
作用:接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
(2)处理器映射器HandlerMapping(不需要程序员开发)
作用:根据请求的URL来查找Handler
(3)处理器适配器HandlerAdapter
注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
(4)处理器Handler(需要程序员开发)
(5)视图解析器 ViewResolver(不需要程序员开发)
作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)
(6)视图View(需要程序员开发jsp)
View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
11大流程
(1)用户发送请求至前端控制器DispatcherServlet;
(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;
(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet响应用户。
在MyBatis中$和#有什么区别
#{}是预编译处理,${}是字符串替换。Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;使用#{}可以有效的防止SQL注入,提高系统安全性。
Mybatis在处理{}替换成变量的值。
preparestatement 与 statement区别 什么是sql注入
说说Soring Boot的起步依赖
消息中间件
什么是消息中间件
是利用高效可靠的消息传递机制进行异步的数据传输,并基于数据通信进行分布式系统的集成。通过提供消息队列模型和消息传递机制,可以在分布式环境下扩展进程间的通信。
消息中间件的传递模式有哪些?
- 点对点模式----消息生产者将消息发送到队列中,消息消费者从队列中接收消息。消息可以在队列中进行异步传输。
- 发布/订阅模式---发布订阅模式是通过一个内容节点来发布和订阅消息,这个内容节点称为主题(topic),消息发布者将消息发布到某个主题,消息订阅者订阅这个主题的消息,主题相当于一个中介。主题使得消息的发布与订阅相互独立,不需要进行接触即可保证消息的传递,发布/订阅模式在消息的一对多广播时采用。
消息中间件的作用是什么?
- 服务解耦
- 异步调用
- 流量削峰
如何保证消息不丢失
消息走向: 消息提供方-内存中-消息接收方, 消息提供方避免: Confirm模式
- 生产者完成消息发送后,可能会因为网络或MQ自身原因,消息没有被MQ正确接收.
- 采用消息发送确认模式, 即Confirm模式
- 开启Confirm模式, 连接服务器后,调用 channel.confirmSelect().
- 向MQ发送消息后,等待服务器返回确认信息
- 接收MQ返回确认信息, 接收确认信息分为同步接收和异步接收.
- 同步接收确认信息:waitForConfirmsOrDie(超时毫秒值), 发送消息后,等待接收确认信息,超过指定时间后抛出异常(缺点:对每一条消息进行确认效率较低). 也可以在发送一批消息后,对一批消息一起进行确认(缺点:一批消息中如果存在失败的消息,无法获知哪些消息失败).
- 异步接收确认信息: c.addConfirmListener(确认消息回调, 丢失消息回调)
内存中避免: 持久化到硬盘中.
- 队列持久化: 创建队列时,持久化参数设置为true.
- 消息持久化: 发送消息时,消息添加持久化属性.
消息接收方避免: 避免自动确认, 自动确认后会删除消息, 改为手动确认.
如何保证消息的处理顺序(保证消息全局有序)?
– 生产者将所有需要顺序处理的消息发送到同一个队列
– 只用一个消费者从队列接收消息
如何解决消息重复消费问题?
幂等性控制
• 幂等性:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。
• 操作数据前,先从数据库查询数据,判断数据是否已经存在或已经修改过
• 数据库中设置唯一id,防止重复添加数据
• 将已消费的消息id存入redis,处理消息前先检查redis中是否已存在该消息的id
消费者无法处理消息怎么办?
使用死信队列
• 什么是死信队列: 消费者如果无法处理消息或处理失败,可以选择将消息发送到死信队列,之后再采用其他方式来处理死信队列中的消息。
• 为什么使用死信队列: 消费者由于某种原因消息处理失败,并希望这条消息可以以其他方式 再次被处理,这时就可以将消息发送到死信队列,让其他消费者来接收处理。
消息没有被及时消费的原因:
• 消息被拒绝(basic.reject/ basic.nack)并且不再重新投递 requeue=false
• TTL(time-to-live) 消息超时未消费
• 达到最大队列长度
在任何一个队列上,都可以关联一个死信交换机,变成死信的消息会 被自动发送到死信交换机,而与死信交换机关联的队列就是死信队列.
所谓死信交换机和死信队列,其实就是普通的交换机和队列。正常从 死信队列接收处理这些死信消息即可。
发生消息堆积怎么办?
– 临时增加消费者数量,加快消费速度
– 将堆积消息丢弃,系统空闲时间再重新发送这些消息
– 启动一个消费者接收所有堆积的消息,并将消息直接发送到其他队列,使当前队列后续消息可以被时时消费
常见的消息中间件产品有哪些?
- RabbitMQ:最常用的消息服务,Springboot直接集成
- ActiveMQ
- RocketMQ:阿里的消息服务器
- Kafka:在大数据方向常用
- TubeMQ:腾讯的开源消息服务器,万亿级别数据吞吐量
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafaka |
---|---|---|---|---|
单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic数量对吞吐量的影响 | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 | ||
可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 | |
时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 |
功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景。而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |
启动Rabbitmq服务器
# 启动rabbitmq
rabbitmq -r
# 访问管理界面
http://服务器地址:15672
# 用户名密码都是 guest
Rabbitmq 的六种模式是什么:
- 简单
- 工作
- 发布和订阅
- 路由
- 主题
- RPC异步调用
工作模式中,怎么合理地分发消息?
需要做两点设置:
-
手动ACK 让服务器可以知道一条消息有没有处理完成
-
qos=1 每次抓取的消息数量 每次只接收1条消息,处理完成之前不接收下一条 在手动ACK模式下才有效
Rabbitmq有几种交换机?
四种类型:
- Direct
- Fanout - 实现群发
- Topic
- Headers(不常用)
Rabbitmq怎么群发消息
使用发布和订阅模式:
使用一个 Fanout 类型的交换机实现消息群发操作
1 消息队列MQ的套路
消息队列/消息中间件应该是Java程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处
面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。
《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。
1)通过异步处理提高系统性能
如上图,在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。
通过以上分析我们可以得出消息队列具有很好的削峰作用的功能——即通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此使用消息队列进行异步处理之后,需要适当修改业务流程进行配合,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
2)降低系统耦合性
我们知道模块分布式部署以后聚合方式通常有两种:1.分布式消息队列和2.分布式服务。
先来简单说一下分布式服务:
目前使用比较多的用来构建SOA(Service Oriented Architecture面向服务体系结构)的分布式服务框架是阿里巴巴开源的Dubbo。如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:《高性能优秀的服务框架-dubbo介绍》:https://juejin.im/post/5acadeb1f265da2375072f9c
再来谈我们的分布式消息队列:
我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
我们最常见的事件驱动架构类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:
消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。 从上图可以看到消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计。
消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。
备注: 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,比如在我们的ActiveMQ消息队列中还有点对点工作模式,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。
这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题!
1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?
- 系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!
- 系统复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
- 一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可!
1.4 关于消息队列其他一些常见的问题展望
- 引入消息队列之后如何保证高可用性?
- 如何保证消息不被重复消费呢?
- 如何保证消息的可靠性传输(如何处理消息丢失的问题)?
- 我该怎么保证从消息队列里拿到的数据按顺序执行?
- 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
- 如果让你来开发一个消息队列中间件,你会怎么设计架构?
开发工具
架构
Netty
介绍一下自己对 Netty 的认识
为什么要用
说说业务中,Netty 的使用场景
什么是TCP 粘包/拆包,解决办法
Netty线程模型。
Dubbo 在使用 Netty 作为网络通讯时候是如何避免粘包与半包问题?
讲讲Netty的零拷贝?
分布式
微服务
Spring Cloud Alibaba
Nacos服务注册及健康状态如何检测?
nacos服务客户端(要注册到nacos的服务)启动时会每隔一段时间(默认为5秒)向nacos发送心跳包,nacos注册中心15秒内没有检测到心跳包会默认认为nacos处于一种不健康状态,30秒还收不到心跳包则认为这个服务已不可用。
项目中如何实现服务的调用?
项目中调用服务我们用过两种方式一种方式是RestTemplate,一种是OpenFeign,这两种方式在进行服务调用时,都可以借助Ribbon实现负载均衡。
Nacos的配置管理模型以及配置数据的获取?
Nacos配置管理模型中,为了实现更好的环境隔离给出了namespace,group,dataId的感念,一个配置中心可以有多个命名空间,一个命名空间可以有多个分组,一个分组内可以有多个groupId,服务启动时会每隔30描述向配置中心请求一次数据,2.0之前默认采取的时长轮询拉取模式。
为什么要限流,Sentinel 限流常用算法?
限流的目的是为了保证服务更加可靠的运行,不至于系统在遇到突发流量时,出现系统宕机的现象。常用的限流算法有计数器法,滑动窗口算法,漏斗算法,漏桶算法等。
网关层面如何实现负载均衡以及常用算法?
网关层面的负载均衡我们借助了Ribbon进行实现,常用算法有轮询,权重,随机,hash等,这个算法都可以在配置中心进行配置,然后基于业务不同,做动态调整。
Dubbo踩过哪些坑,分别是怎么解决的?
(说了异常处理时业务异常捕获的问题,自定义了一个异常拦截器)
服务治理怎么实现的?
(说了限流、压测、监控等模块的实现)
Devops
Docker
Docker 诞生的背景
服务多了,维护困难了
Docker 平台基本架构
Client/Server,参考官方的架构图
Docker 平台核心对象
镜像-image,容器-Container
Docker 平台的安装
在CentOS系统上在线安装,离线安装
Docker 服务的基本操作
status,start,stop ,restart,enable,disable,docker info,docker -v
Docker 镜像
(Image)基本操作(pull,images,rm,save,load,inspect,history,…)
Docker 容器基本操作
(run,ps,ps -a,stop,start,restart,exec,logs,exit,rm,rm -f,prune)
Docker 中的数据管理
(数据卷-volume,直接的目录挂载)
Docker 平台下镜像(Image)的制作
文件系统~软件+Dockerfile,build
Docker平台下容器之间的互联
虚拟网络network
- Docker是什么?(虚拟引擎,容器化技术平台,基于docker创建镜像,启动容器)
- Docker用于解决什么问题?(简化部署-例如sentinel镜像,运维,提高其服务的可维护性)
- Docker的基本架构是怎样的?(Client/Server,pull,run,build都属于客户端指令,通过这些指令向docker服务发起请求)
- Docker中有哪些核心对象?(Image/Container/…)
- 如何理解docker中的镜像(Image)?(一套文件系统,是静态,需要放到容器中去运行。类似一个jar包,需要JVM解释执行)
- 如何理解Docker中的容器(Container)?(一个的进程,拥有独立的namespace,通过namaspace实现容器隔离)
- 什么数据卷,为什么要使用数据卷,如何使用数据卷? 数据卷是一个可供一个或多个容器使用的特殊目录,可以在容器之间实现资源共享或重用,默认会一直存在,即使容器被删除。我们在启动容器时,通常会基于数据卷实现宿主机与容器之间的目录挂载,
- 为什么我们要自己制作镜像?制作镜像的步骤是怎样的?
- 如何加载本地(Linux宿主机)镜像文件,存储到docker的本地镜像仓库中?
- 如何从远程镜像仓库去下载镜像文件?(docker pull 镜像文件)
- 如何基于docker运行常用的镜像文件?(MySql,Redis,Nginx,Naocs等)
- 如何在启动镜像容器时实现目录或数据卷的挂载?(是宿主机挂载到容器,-v)
- 数据卷或直接的目录挂载有什么不同?(数据卷是docker中的一个对象有docker管理)
- 如何查看容器启动或运行日志?(docker logs 容器id)
- 退出容器后想再进入容器怎么办?(首先docker ps查看容器是否在运行,假如没有运行要start启动)
- 如何实现容器互联?(基于宿主机,建立虚拟网络)