面试全文
BIO、NIO、AIO
- 网络中,客户端与请求的几种关系
- 客户端阻塞,请求同步(时刻检查服务端是否给出响应),客户端发送了请求后,就一直等待服务端响应
- 客户端非阻塞,请求同步,客户端发送请求后,会轮询服务端是否给出响应,可以处理多个请求
- 客户端阻塞,请求异步(服务端完成之后,通知客户端),客户端发送请求后,等待服务端返回响应
- 客户端非阻塞,请求异步,客户端发送请求后,去执行别的任务,服务端给出响应后,通知客户端
- BIO 同步阻塞IO,使用BIO读取数据时,线程会阻塞住,并需要线程主动去查询是否有数据可读,并需要处理完一个Socket之后才能处理下一个Socket
- 可靠性差,吞吐量低,适用于连接比较少,且比较固定的场景,JKD1.4之前唯一的选择
- 编程模型最简单
- NIO 同步非阻塞IO,使用NIO读取数据时,线程不会阻塞,需要线程主动去查询是否有IO事件
selector
- 可靠性比较好,吞吐量较高,适用于连接比较多,连接比较短,例如聊天室,JDK1.4之后
- 编程模型最复杂
- AIO NIO2.0,异步非阻塞IO,使用AIO读取数据时,线程不会阻塞,并当有数据可读时会通知给线程,
不需要主动查询
- 可靠性好,吞吐量高,适用于连接比较多,比较长的操作(重操作),例如相册服务器,JDK1.7之后
- 编程模型比较简单,但是需要操作系统支持
NIO的核心组件
- channel、buffer、selector
- 每个channel对应一个buffer缓冲区,buffer的内容通过channel进行传递
- channel注册到selector,selector根据channel上发生的读写事件,将请求交给某个空闲的线程,selector对应一个或者多个线程
- buffer和channel都是可读可写的
select、epoll和poll的区别
- NIO中多路复用的三个实现机制,由Linux操作系统提供的API
- 用户空间和内核空间,为了保护系统安全,防止用户破坏系统内容,将内核划分为用户态(用户应用)和内核态(操作系统运行);用户空间不能直接访问底层的硬件设备,必须通过内核空间
- 文件描述符 File Descriptor FD,内核中的数据表,记录每个进程打开的文件(一切皆文件)。形式上是一个整数,实际上是一个索引值,指向内核中纬每个进程维护所打开的的文件的记录表,当程序打开或创建一个文件,就会向进程返回一个描述符,Unix/Linux中
- select模型,维护一个通道(FD)的集合(FD_set),扫描这些所有的fd,将fd复制到内核空间,由内核空间激活,再通过socket连接完成io
- 使用
数组来存储Socket,容量固定,通过轮询来判断是否发生IO事件,x64 2048- 开销比较大,需要数据拷贝,容量受限制
- poll模型,使用
链表存储FD(Socket),容量不固定,通过轮询来判断
机制和select差不多,将FD_set结构进行优化,但是也需要拷贝fd
- epoll模型,Event Poll,时间通知模型(事件驱动),不再扫描所有的fd,将用户关心的fd事件存放到内核的一个事件表中,当Socket发生IO事件后,应用程序才进行IO操作,不需要主动轮询
- linux2.6之后
- 只将发生了对应敏感事件的通道,进行处理,放入事件表,而没有事件的通道,进行休眠,减少用户空间与内核空间之间拷贝的数据
- 总结
- select 遍历,数组,容量固定,受限内核(32位 1024;64位 2048),IO效率一般
- poll 遍历,链表,无上限,IO效率一般
- epoll 事件回调,红黑树,无上限,IO效率高
- select 1984年出现,poll 1997出现,epoll 2002年出现,硬件发展阶段有关,早期需求不大
- java nio
DefaultSelectorProvider根据系统进行决定,也使用了epoll,mac下
零拷贝
- 避免让CPU执行大量数据拷贝任务,应用程序在需要把内核中的一块区域数据转移到另外一块内核区域去时,不需要先复制到用户空间,再转移到目标内核区域,实现直接转移
Netty和Tomcat
- Netty,基于NIO的异步网络通信框架,性能高,封装了原生NIO
- 封装的是IO模型,关注网络数据的传输,不关心具体的协议
- 异步、高性能、高扩展、高可定制化、易用性
- Tomcat,是一个web服务器,Servlet容器,内部只会运行Servlet程序,处理HTTP请求
Netty的高性能体现在哪些方面
- NIO模型,用最少资源做更多事情
- 内存零拷贝,减少不必要的内存拷贝
- 内存池设计,申请的内存可以重用,主要指直接内存,内部通过二叉查找树管理内存分配情况
- 串行化处理读写,避免使用锁带来的性能开销,
消息的处理尽可能在同一个线程内完成,期间不做线程切换,避免了多线程竞争和同步锁
- 表面上看串行化设计的CPU利用率不高,并发程度不够,但是通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行
- 这种局部无锁化的串行线程设计比一个队里-多个工作线程模型性能更优
- 高性能序列化协议,支持
protobuf等高性能序列化协议 - 高效并发编程,大量使用
volatile;CAS和原子类的广泛应用;线程安全容器;通过读写锁提升并发性能
Netty线程模型
- 支持
Reactor单线程模型、Reactor多线程模型和Reactor主从多线程模型,用户根据启动参数进行切换 - 服务器启动时,通常会创建两个
NioEventLoopGroup实例,对应两个独立的Reactor线程池,bossGroup负责处理客户端的连接请求,workerGroup负责处理I/O相关的操作,执行系统任务和定时任务等
Tomcat中为什么要使用自定义类加载器
- 一个Tomcat可以部署多个应用,每个应用都存在很多类,每个应用中的类都是独立的,全类名可以完全相同
一个订单系统中可以存在多个User类,一个库系统中也存在User类
- 不管内部部署多少应用,Tomcat启动之后就是一个java进程,也就是一个JVM,如果只存在一个类加载器,那么就只能加载一个User类,那么
不同的应用之间的相同全类名的类就会冲突 - Tomcat会为每个应用都生成一个类加载器,
WebAppClassLoader,这样每个应用都可以是用自己的类加载器,去加载自己的类,实现不同应用的类间隔
类通过
类加载器实例+类名进行区分
- 还可以利用自定义类加载器实现热加载功能
Tomcat监听类的修改时间,当类修改时间变化时,就会重新创建一个
WebAppClassLoader,进行加载
Tomcat如何进行优化
- 从两个方面,内存和线程
- 启动了一个Tomcat实际上就是启动了JVM,可以按照JVM调优的方向
- 缓存区方向,
appReadBufSize、bufferPoolSize,提高吞吐量 - 调整
minSpareThreads改变空闲时的线程数 - 调整
maxThreads设置处理连接的最大线程数 - 调整IO模型,使用NIO、AIO
- Tomcat7默认BIO
- Tomcat8以上默认NIO
跨域请求
- 浏览器在发起网络请求时,会检查该请求所对应的协议、域名、端口和当前网页是否一致,如果不一致浏览器会进行限制,是为了用户信息安全
- 协议、域名、端口相同,同源
- 在百度中访问京东是不行,但是如果是
img、iframe、script等标签的src属性去跨域访问是可以的
- 绕过限制
- response添加header,
resp.setHeader("Access-Control-Allow-Origin", "*")可以访问所有网站,不受是否同源的限制- jsonp的方式,底层基于
script标签实现- 后台自己控制,先访问同域名下的接口,然后接口中使用
HTTPClient等工具,调用目标接口- 网关,交给后台服务进行跨域访问
CSRF攻击
- CSRF,Cross Site Request Forgery,跨站请求伪造
- 一个正常的请求会将合法用户的session id保存到浏览器的cookie中
这时候,用户在浏览器中打开另一个tab页,那这个tab页,也可以获得浏览器的cookie,就会被黑客进行攻击
- 攻击过程
- 银行网站以GET请求发起转账操作,需要登录后才能操作
www.xxx.com/transfor.do?acctountNum=100&money=1000- 攻击者在某个论坛上传一个图片,链接地址就是转账的操作请求
- 某用户登录了银行网站,保存了session id,用打开了另一个网页点击这个图片,相当于携带了正确cookie的请求,完成转账
- 防御方式
- 使用POST请求,限制GET请求,隐藏参数信息
- 将cookie设置为
HttpOnly,response,setHeader("Set-Cookie","cookiename=cookievalue;HttpOnly"),应用程序不能读取cookie信息- 增加token,在请求中放入攻击者无法伪造的信息,该信息不存在cookie中,每次请求都会带上这个属性,
spring Security中的防范方式
OAuth2.0
- 权限认证框架,一个开放标准,允许用户授权第三方应用程序访问另外的服务提供者上的信息,不需要将用户名和密码提供给第三方应用或分享他们的数据内容
- 将授权和认证过程交给一个第三方进行担保,OAuth规范了这个流程
- 授权码模式,通过微信登陆百度,向微信申请授权码,向百度颁发一个授权码,百度通过授权码向微信申请令牌,通过令牌进行最终的登录
- 简化模式,用户同意之后,直接下发令牌给百度,适用于后台没有服务向开发平台申请令牌
- 密码模式,用户直接把账号密码给第三方应用,第三方代为申请令牌
- 客户端模式,用户给第三方一个标志位,第三方应用直接向开发平台申请令牌
- 选用的模式,取决于三方参与者之间的互信程度
JWT令牌
- 普通令牌,随机的字符串,没有特殊意义,当客户带上令牌去访问应用接口时,应用本身无法判断令牌是否正确,应用通过
认证平台(授权服务器)验证令牌是否有效
高并发场景下,检查令牌的网络请求成为性能瓶颈
- JWT令牌,将令牌的相关信息,全部冗余到令牌本身,资源服务器不需要发送请求给授权服务器进行令牌校验,资源服务器本身就可以解析令牌信息
本质上是一个加密字符串,可以脱离OAuth使用
SSO
- SSO和OAuth2.0 本质不同
- OAuth2.0的使用场景为
联合登陆,一处注册,多处使用
通过微信可以登录多个地方
- SSO,Single Sign On,单点登录,适合
内部系统之间的不同应用,一处登陆,多处同时登陆
淘宝完成登录,天猫也直接登录
- SSO的实现关键将
session信息集中存储
- redis存储
- spring security中实现了
认证和授权
- 认证和授权是权限认证框架进行扩展的两个方面
- 认证,对系统访问者的身份进行确认
用户名密码登陆,二维码登陆,手机短信登录,指纹,面部识别
- 授权,对系统访问者的行为进行控制
- 授权在认证之后,对系统内的用户隐私数据进行保护
- 后台接口访问权限(权限不同可以查看的数据也不同),前台控件的访问权限(系统管理员/普通人员)
- 如何设计一个权限认证框架
- RBAC模型,Role-Based Access Control,基于角色的访问控制,最小特权原则、责任分离原则和数据抽象原则
- 主体(用户)、角色(权限类别)、资源、访问系统的行为
Who对What(Which)进行How的操作,也就是主体对客体的操作
RBAC分类
- RBAC0,核心,多对多关系,一个主体对应多个角色,一个角色可以赋予多个主体
- RBAC1,有了继承关系,角色上有了
上下级/等级 - RBAC2,加入的约束概念,主要引入了静态职责分离
SSD(Static Separation of Duty)和动态职责分离DSD(Dynamic Separation of Duty)
- 互斥角色:同一个用户在两个互斥角色中只能选择一个
- 基数约束:一个用户拥有的角色是有限的,一个角色拥有的权限也是有限的
- 先决条件约束:用户想要获得高级角色,首先必须拥有低级角色
- DSD是
访问和角色之间的约束,可以动态的约束用户拥有的角色,如一个用户可以拥有两个角色,但是进行具体访问时只能激活一个角色
- RBAC3,是RBAC1与RBAC2合集
如何设计一个开放授权平台
- 类似微信小程序的各个小程序授权
- 认证和授权两个方向
- 认证,OAuth2.0方向
- 授权,需要待接入的第三方应用在开放平台注册,需要必要的信息(clientID,消息推送地址,一对公私钥)
- 一对公私钥,私钥授权平台保存,公钥给第三方应用的客户端
- 第三方应用引导客户端发起请求时,采用公钥进行参数加密,授权开放平台使用对应的私钥进行解密
- 授权开放平台同步响应的是消息是否处理成功的结果,真正的业务数据由授权开发平台异步推动给第三方应用预留的推送地址
- 授权行为控制,请求不能直接获取业务消息,只能按业务要求封装,直接发送给
消息推送地址,不直接返回 - 认证行为控制,客户端同样生成一份
非对称加密密钥,公钥给平台
嵌入式服务器
- spring boot 内嵌了
tomcat服务器,成为tomcat.jar,内部的一个应用,运行main方法时,会自动启动tomcat,利用tomcat的spi机制加载springMVC
- 节省下载安装tomcat,应用不需要打war包,然后放到webapp目录下运行
- 兼容了中间键,不需要另外安装
- SPI,Service Provider Interface,服务发现机制,通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类
- tomcat轮询jar包,找
SpringServletContainerInitializer,通过反射创建实例,并且将实现了WebApplicationInitializer放入set集合 - 通过反射进行创建web容器