你好,offer(最终版)
你好,offer(最终版)
- 计算机网络
- osi七层模型:物联网叔会试用
- 物理层
- 链路层
- 网络层
- 传输层
- 会话层
- 表示层
- 应用层
- 五层协议
- UDP相关
- TCP相关
- TCP的三从握手和四次挥手
- 三次握手
- 四次挥手
- 为什么TCP连接的时候是3次
- 为什么TCP连接的时候是3次,关闭的时候却是4次
- 为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?
- TCP粘包
- 为什么出现粘包现象
- TCP粘包该怎么做
- 滑动窗口协议
- DNS解析
- 域名结构
- DNS的解析过程:
- CDN原理,CDN加速过程
- 网页解析全过程
- GET和POST区别
- Session、Cookie和Token的主要区别
- Cookie
- session
- cookie与session区别
- token
- session与token区别
- servlet
- servlet的作用域:
- servlet生命周期:
- Servlet是线程安全的吗
- 跨域问题
- Socket
- socket 的典型应用
- Socket通讯的过程
- HTTP
- http和https的区别
- HTTPS的整体过程分为证书验证和数据传输阶段
- 一次完整的HTTP请求所经历几个步骤?
- 常用HTTP状态码
- HTTP1.0和HTTP1.1和HTTP2.0的区别
- HTTP1.0和HTTP1.1的区别:
- HTTP1.1和HTTP2.0的区别:
- Socket和http的区别
- 对称加密与非对称加密
- Get和Post的区别
- 网络攻击
- CSRF
- SYN攻击
- 优化:
- 转发和重定向
- 操作系统
- 什么是内核态和用户态?
- 如何实现内核态和用户态的切换?
- Spring
- Spring采取了以下4种关键策略
- Spring的优点
- Spring的缺点
- Spring由哪些模块组成
- Spring 框架中都用到了哪些设计模式?
- 控制反转(IOC)
- 控制反转(IoC)有什么作用
- Spring IoC 的实现机制:
- BeanFactory 和 ApplicationContext有什么区别
- Spring的DI:
- 有哪些不同类型的依赖注入实现方式
- 面向切面编程(AOP)
- Spring AOP中的动态代理
- Spring AOP里面的几个名词
- Spring通知有哪些类型?
- Spring Beans
- 解释Spring支持的几种bean的作用域
- Spring框架中的单例bean是线程安全的吗?
- Spring如何处理线程并发问题?
- Spring中bean的生命周期
- Spring注解
- @Autowired自动装配
- @Autowired和@Resource之间的区别
- Spring的源注解
- Spring事务
- Spring支持的事务管理类型/Spring事务实现方式有哪些
- spring 的事务隔离级别
- Spring MVC
- Spring MVC的主要组件
- 请描述Spring MVC的工作流程
- SpringBoot
- Spring Boot 有哪些优点?
- Spring Boot的启动流程
- Spring Boot Starter是什么
- spring-boot-starter-parent 有什么用
- spring boot 核心配置文件
- Spring Boot 打成的 jar 和普通的 jar 有什么区别
- 并发
- 并发编程三要素(线程的安全性问题体现在)
- 创建线程的四种方法
- 什么是 Future 和 FutureTask?
- 线程的基本操作:
- 中断线程
- 线程池
- 线程池的优点
- 线程池的使用
- Java线程池的的工作原理
- 线程池的核心组件
- 线程池的拒绝策略
- 常用的线程池
- 线程间的通信
- Java的锁:
- AQS:
- CAS:
- synchronized
- ReentrantLock
- volatile
- 数据库
- 数据库的三大范式
- 第一范式
- 第二范式
- 第三范式
- 数据库连接泄露
- 触发器
- 索引
- 索引的优点
- 索引的缺点
- 索引的数据结构
- B+树的好处
- B+树索引和Hash索引的区别
- 前缀索引
- 最左前缀匹配原则
- 添加索引的原则
- 聚簇索引和非聚簇索引
- 数据库的事务
- 数据库的分布式任务:
- 两阶段提交:
- 三阶段提交:
- where、groupby、having
- 索引
- MySQL使用自增主键的好处:
- MVCC
- 读已提交和可重复读
- 数据库连接泄露:
- SQL优化:
- SQL的生命周期:
- 一条更新语句的执行流程:
- 日志
- 数据库的优化:
- MySQL的主从复制原理以及流程:
- Java基础
- Java为什么能跨平台
- 字节码是什么
- 字节码的好处
- Java有哪些数据类型
- 基本数据类型:
- 访问修饰符
- &&和&的区别
- final finally finalize区别
- this super关键字的用法
- static存在的主要意义
- 面向对象的三大特性
- 封装
- 继承
- 多态
- 抽象类和接口的对比
- Java中只有值传递
- Java中的IO流
- BIO,NIO,AIO
- 反射
- 自动装箱与拆箱
- Integer a= 127 与 Integer b = 127相等吗
- 字符串常量池
- 流程:
- 异常
- 运行时异常(非受检查异常)和编译时异常(受检查异常)
- JVM如何处理异常
- throw 和 throws 的区别是什么
- try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
- NoClassDefFoundError 和 ClassNotFoundException 有什么区别
- Java常见的异常
- Error
- Exception
- 深拷贝和浅拷贝:
- 什么是字节码
- switch
- static关键字
- 面向对象的优势
- 抽象类和接口的区别
- 抽象类可以被final修饰吗
- 什么是不可变对象
- ==和equals的区别
- hashcode
- String、Stringbuffer、Stringbuilder
- String为什么设计成不可变的
- 字符串常量池(堆)
- String ans="a"和String ans=new String("a")的比较
- intern()方法
- 非new生成的integer对象和new Integer()生成的变量对比
- 反射
- 反射的API:
- 反射的步骤:
- 为什么引入反射
- 反射的使用步骤
- 反射机制的原理
- Java中的泛型
- Java的序列化
- 序列化的实现方式
- SerialVersionUID
- 不想序列化的字段
- 静态变量不会进行初始化
- 字节流是如何转换为字符流的
- 字节流和字符流的区别
- 集合
- Java集合的fail-fast机制
- ArrayList
- ArrayList的扩容机制:
- set是如何检查重读的:
- HashMap的插入流程:
- HashMap的扩容操作
- HashMap为什么线程不安全
- HashMap 的长度为什么是2的幂次方
- HashMap是怎么计算Hash值的
- ConcurentHashMap
- Swagger
- 使用Swagger解决的问题?
- 使用swagger:
- Swagger2常用注解说明
- Redis
- Redis的用途
- Redis的基本知识以及操作
- 为什么单线程的Redis能那么快
- Redis的五大数据类型
- String
- hash
- list
- set
- zset
- Redis提供了三个高级数据结构
- geospatital(地理空间)
- HyperLogLog
- 位图
- Redis事务
- Jedis
- SpringBoot整合
- Redis.conf详解
- Redis 缓存处理请求的两种情况
- 缓存的类型
- 只读缓存
- 读写缓存
- Redis核心-管道
- Redi的过期策略
- Redis的持久化
- AOF
- AOF执行过程
- AOF的三种写回策略
- 重写日志
- RDB
- AOF和RDB的混用
- Redis集群的数据同步
- 主从库间如何进行第一次同步
- 主从级联模式分担全量复制时的主库压力
- 主从库间网络断了怎么办?
- 主库挂了,怎么办?
- 哨兵模式
- 哨兵间的通信
- Redis发布订阅
- 保证缓存与数据库双写时的数据一致性
- Redis的过期删除策略
- Redis的内存淘汰策略:
- redis为什么不支持回滚
- 缓存异常:
- SpringBoot使用Redis做缓存
- Cache的缓存注解
- io多路复用的三种方法(Linux)
- Redis的分布式锁
- 分布式锁的缺陷
- Redis的启动命令
- 使用配置文件启动Redis的服务器
- 启动Redis
- 通过Jedis操作redis
- Mybatis
- JDBC的使用步骤:
- #{}和${}的区别:
- Mybatis和Hibernate的区别
- Maven
- 中间件以及消息队列
- dubbo
- dubbo为何而生:
- dubbo的核心组成
- 服务器注册与发现的流程:
- Spring Cloud和dubbo的区别
- Dubbo集群提供了哪些负载均衡策略
- zookeeper
- MQ
- RabbitMQ
- RabbitMQ通过交换器来进行发布消息
- 交换器有三种规则:
- RabbitMQ运转流程:
- AMQP协议:
- AMQP协议包括三层
- JVM
- JVM核心
- JVM包含两个组件和两个子系统
- 对象创建的过程:
- 内存异常有两种情况,
- 方法调用:
- JVM垃圾回收
- 判断对象是否存活:
- 垃圾回收算法
- Minor GC新生代、Major GC老年代
- 垃圾收集器
- JVM的类加载
- 类加载的七个过程:
- 类加载器:
- 双亲委派模式:
- 项目
- 用户管理
- 登录模块:
- 登陆成功模块:
- 人脸登陆模块:
- 注册模块:
- 品牌管理
- 品牌列表模块(模糊查询):
- 添加品牌模块:
- 删除品牌模块:
- 更新品牌模块:
- 商品管理
- 商品列表模块:
- 商品分类模块:
- 商品的创建模块:
计算机网络
osi七层模型:物联网叔会试用
物理层
比特流传输
链路层
控制网络层与物理层的通信
网络层
ip寻址和路由选择
传输层
两台主机进程之间的通信提供服务。传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。
会话层
负责建立(身份验证,权限鉴定)、管理和终止表示层实体之间的通信会话
表示层
使通信的应用程序能够解释交换数据的含义。该层提供的服务主要包括数据压缩,数据加密以及数据描述
应用层
通过应用程序间的交互来完成特定的网络应用。该层协议定义了应用进程之间的交互规则,通过不同的应用层协议为不同的网络应用提供服务。
五层协议
应用层、传输层、网络层、链路层和物理层
UDP相关
UDP连接前不进行三次握手,发送数据之前不需要建立连接,所以UDP具有较好的实时性,适用于及时通信。但是UDP只管发送数据,数据报发出去,就不保留数据备份,不能保证数据的可靠性
UDP支持一对一,一对多,多对一和多对多的交互通信而TCP连接只能是点到点的
TCP相关
TCP的三从握手和四次挥手
TCP通过三次握手建立一个连接,通过四次挥手来关闭一个连接
三次握手主要是为了确认通信双方收发数据的能力是正常的
三次握手
- 第一次握手:客户端向服务端发送连接请求,客户端随机生成一个起始序列号,客户端向服务端发送报文(包含syn和序列号)。此次证明服务端能够发送消息
- 第二次握手:服务端收到客户端发送来的报文后,发现syn,知道这是一个同步请求,然后将客户端的序列号保存下来。并随机生成一个服务端的序列号。向客户端发送确认报文(syn和相应ack(客户端的序列号+1)以及服务端的序列号以及ACK标志位)(此次证明服务端的收发能力都可以)
- 第三次握手:客户端收到回复后,发现syn为1,知道服务端同意了连接,然后保存服务端的序列号,再向服务端发送一段报文(syn和相应ack(服务端的序列号+1)以及客户端的序列号以及ACK标志位)。至此客户端和服务端建立起连接(此次证明客户端的收能力正常)
注意:不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是原来的
四次挥手
- 第一次挥手:当客户端的数据传输完的时候,客户端向服务端发送报文(包含fin和seq),申请断开连接。
- 第二次挥手:服务器收到客户端发来的fin报文后,给客户端回复确认报文(包含一个序列号和一个ack(序列号+1)和ACK标志),此时客户端处于半关闭状态,因为服务器可能还有数据没发完
- 第三次挥手:服务端将数据发送完毕后向客户端发送连接释放报文(fin和ack以及seq和ACK标志位)
- 第四次挥手:客户端收到服务端的释放报文后向服务端发送确认报文(ack,seq,ACK)。发送完确认报文后要等待两个发送周期后才释放TCP连接,服务端收到客户端的确认报文后会立即释放TCP连接
为什么TCP连接的时候是3次
因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据。
如果是三次握手,即便发生丢包也不会有问题,比如如果第三次握手客户端发的确认ack报文丢失,服务端在一段时间内没有收到确认ack报文的话就会重新进行第二次握手,也就是服务端会重发SYN报文段,客户端收到重发的报文段后会再次给服务端发送确认ack报文。
为什么TCP连接的时候是3次,关闭的时候却是4次
因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了,服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。
为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?
要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。
TCP粘包
发送方发送的若干包数据到接收方接收时粘成一包
为什么出现粘包现象
- 发送方原因:TCP默认会使用Nagle算法(数据在发送端被缓存,如果缓存到达指定大小就将其发送,或者在上一个数据的应答包到达,将缓存区一次性全部发送),正是Nagle算法造成了发送方有可能造成粘包现象。
- 接收方原因:TCP接收到分组时,并不会立刻送至应用层处理。TCP将收到的分组保存至接收缓存里,然后应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包。
TCP粘包该怎么做
- 如果发送方发送的多个分组本来就是同一个数据的不同部分,当然不需要处理粘包的现象。但如果多个分组本毫不相干,我们就一定要处理粘包问题了
- 固定发送信息长度,或在两个信息之间加入分隔符。
滑动窗口协议
最大效率的利用网络资源,增加网络的吞吐量
采用发送确认机制来保证次序,采用多包同时发送来保证高吞吐
问题:我们每次需要发多少个包过去呢?发送多少包是最优解呢?
每收到一个新的确认(ack),滑动窗口的位置就向右移动一格。
如果发送过程中包丢了,采取超时重发机制
DNS解析
浏览器通过域名(例如:www.baidu.com)发起一个网络请求的时候,会有DNS服务器将域名解析成ip地址,以便向正确的ip地址发送请求。
DNS:是一个域名系统,将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS在区域传输的时候使用TCP协议,其他时候使用UDP协议
DNS解析:互联网都是通过URL来请求资源的,而URL中的域名需要解析成IP地址才能与远程主机建立连接,将域名解析成IP地址就属于DNS解析的工作范畴。
域名结构
一个完整的域名由2个或2个以上的部分组成,各部分之间用英文的句号“.”来分隔
如域名mail.cctv.com,其中:com为顶级域名( top-level-domain,TLD), cctv为二级域名,mail为三级域名
DNS的解析过程:
用户在自己的浏览器中输入要访问的网站域名。
浏览器首先看看自己缓存和本地操作系统中有没有对应的ip地址,如果有记录则直接响应用户请求
如果没有,浏览器向会本地DNS服务器请求对该域名的解析。
本地DNS服务器中如果缓存有这个域名的解析结果,则直接响应用户的解析请求。
本地DNS服务器中如果没有缓存这个域名的解析结果,就会将此请求发送到根DNS服务器,本地DNS服务器和根DNS服务器会以递归的方式一级一级接近查询的目标,获取解析结果后将其返回给本地DNS服务器。
DNS服务器在返回给浏览器
CDN原理,CDN加速过程
CDN就是根据用户位置分配最近的资源
通过将网络内容发布到最靠近用户的『边缘节点』,使不同地区的用户在访问相同页面、图片或视频时就可以就近获取。
CDN本质是一种分布式缓存系统,无需考虑数据持久化,如果缓存服务器出现问题,在缓存集群中标记为删除即可。
CDN的优点:
- 根据用户与业务服务器的距离,自动选择就近的cache服务器
- 镜像服务,消除运营商之间互联的瓶颈影响,保证不同网络的用户都能得到良好的访问质量
- 分担网络流量,减轻压力
引入CDN后访问网站的流程
当用户输入网址回车后,经过本地DNS系统解析,DNS会将最终的域名解析权交给CNAME 指向的CDN 专用DNS服务器。
CDN的DNS服务器将 CDN的全局负载均衡 目的ip 地址返回给浏览器
用户向 CDN的全局负载均衡服务器 发起内容url 请求
CDN全局负载均衡服务器根据 用户请求的IP地址,url等信息,选择一台离用户距离近,缓存服务器上有用户所需内容,以及负载均衡合适的边缘服务器,发给用户
用户向这个选中的边缘服务器发起请求。边缘服务器响应用户请求,将用户所需内容传送到用户终端。如果边缘服务器上没有用户想要的内容,那么这台服务器就会向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器,并将内容拉取到本地。
网页解析全过程
- DNS 解析
当用户输入一个网址并按下回车键的时候,浏览器获得一个域名,而在实际通信过程中,我们需要的是一个 IP 地址,因此我们需要先把域名转换成相应 IP 地址。 - TCP 连接
浏览器通过 DNS 获取到 Web 服务器真正的 IP 地址后,便向 Web 服务器发起 TCP 连接请求,通过 TCP 三次握手建立好连接后,浏览器便可以将 HTTP 请求数据发送给服务器了。 - 发送 HTTP 请求
浏览器向 Web 服务器发起一个 HTTP 请求,HTTP 协议是建立在 TCP 协议之上的应用层协议,其本质是在建立起的TCP连接中,按照HTTP协议标准发送一个索要网页的请求。
GET和POST区别
- Get是不安全的,因为在传输过程,数据被放在请求的URL中;Post的所有操作对用户来说都是不可见的。
- Get请求提交的url中的数据最多只能是2048字节,Post请求则没有大小限制。
- Get执行效率比Post快
- GET产生一个TCP数据包;POST产生两个TCP数据包。对于GET方式的请求,浏览器会把http header和data一并发送出去。而对于POST,浏览器先发送header,浏览器再发送data
Session、Cookie和Token的主要区别
HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。
Cookie
cookie是由Web服务器保存在用户浏览器上的小文件,包含用户相关的信息。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户身份。
session
session是依赖Cookie实现的。session是服务器端对象,session 在浏览器和服务器会话过程中,服务器分配的一块储存空间。服务器默认为浏览器在cookie中设置 sessionid,浏览器在向服务器请求过程中传输 cookie 包含 sessionid ,服务器根据 sessionid 获取出会话中存储的信息,然后确定会话的身份信息。
cookie与session区别
- cookie数据存放在客户端上,安全性较差,session数据放在服务器上,安全性相对更高;
- 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie,session无此限制
- session一定时间内保存在服务器上,当访问增多,占用服务器性能,考虑到服务器性能方面,应当使用cookie。
token
Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。
Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
session与token区别
- session机制存在服务器压力增大,CSRF跨站伪造请求攻击,扩展性不强等问题;
- session存储在服务器端,token存储在客户端
- token提供认证和授权功能,作为身份认证,token安全性比session好;
- session这种会话存储方式方式只适用于客户端代码和服务端代码运行在同一台服务器上,token适用于项目级的前后端分离(前后端代码运行在不同的服务器下)
servlet
servlet用来接收客户端的请求数据,然后调用底层service处理数据并生成结果
- 客户端发送请求到服务器端
- 服务器将请求信息发送至Servlet
- Servlet生成响应内容并将其传给服务器。
- 服务器将响应返回给客户端。
servlet的作用域:
- request(请求):它的作用范围是一次请求和响应,是三个作用域中最小的。
- session(会话):它的作用比request要大一点,一次会话过程中,它的作用域就一直存在,(默认是30分钟)
- servletcontext:它作用范围最大,作用于整个服务器中。服务器启动时创建,服务器关闭时销毁。(Application)
servlet生命周期:
- Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;
- 调用Servlet的service()方法,service()方法会根据需要调用与请求对应的doGet或doPost等方法;
- 当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy()方法。
Servlet是线程安全的吗
Servlet不是线程安全的,多线程并发的读写会导致数据不同步的问题。
解决的办法是尽量不要在实现servlet接口的类中定义实例变量,而是要把变量分别定义在doGet()和doPost()方法内。虽然使用synchronized(name){}语句块可以解决问题,但是会造成线程的等待,不是很科学的办法。
跨域问题
使用@CrossOrigin注解
Socket
计算机之间进行通信的一种约定。网络上的两个程序通过一个双向的链路连接实现数据的交换,这个双向链路的一端称为一个Socket,一个Socket由一个IP地址和一个端口确定唯一性。
Socket编程主要是指基于TCP/IP协议的网络编程
socket连接就是所谓的长连接,客户端和服务器需要互相连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉的,但是有时候网络波动还是有可能的。
socket 的典型应用
Web 服务器和浏览器:
浏览器获取用户输入的 URL,向服务器发起请求,服务器分析接收到的 URL,将对应的网页内容返回给浏览器,浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。
Socket通讯的过程
- 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把响应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
- 基于UDP:UDP 协议是用户数据报协议的简称,也用于网络数据的传输。虽然 UDP 协议是一种不太可靠的协议,但有时在需要较快地接收数据并且可以忍受较小错误的情况下,UDP 就会表现出更大的优势。我客户端只需要发送,服务端能不能接收的到我不管
HTTP
http协议是超文本传输协议。它是基于TCP协议的应用层传输协议,即客户端和服务端进行数据传输的一种规则。该协议本身HTTP 是一种无状态的协议。
http和https的区别
HTTPS=HTTP协议+TLS/SSL
HTTP存在的不足主要是不安全,容易被第三方窃听篡改和冒充
HTTPS是添加了加密和认证机制的HTTP
由于加密处理,会消耗更多的CPU和内存资源
HTTPS引入了证书,HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段
HTTPS的整体过程分为证书验证和数据传输阶段
证书验证阶段
浏览器发起 HTTPS 请求服务端返回 HTTPS 证书客户端验证证书是否合法,如果不合法则提示告警
数据传输阶段
当证书验证合法后,在本地生成随机数通过公钥加密随机数,并把加密后的随机数传输到服务端服务端通过私钥对随机数进行解密服务端通过客户端传入的随机数构造对称加密算法
HTTPS的优化:
- 通信内容进行加密,防止信息在传输过程中泄露
- 保证数据完整性、准确性
- 对数据来源进行验证,确保来源无法伪造
HTTPS是使用了证书和加密的一个混合密码系统,其中证书的作用在于传递会话密钥,以及验证网站的真实性
HTTPS工作原理:
- 服务器将公钥证书返回给客户端
- 客户端验证公钥证书
- 客户端生成会话密钥,然后用公钥进行加密,发给服务端
- 服务端用私钥进行解密得到会话密钥客户端和服务器就都存在
- 服务端使用会话密钥加密明文内容
- 客户端使用会话密钥进行解密得到明文内容
- 客户端也可以通过会话密钥加密明文内容发给服务端,服务端通过会话密钥解密得到明文内容
一次完整的HTTP请求所经历几个步骤?
- 根据域名和 DNS 解析到服务器的IP地址 (DNS + CDN)
- 获得IP地址对应的物理机器的MAC地址
- 浏览器对服务器发起 TCP 3 次握手
- 建立 TCP 连接后发起 HTTP 请求报文
- 服务器响应 HTTP 请求,将响应报文返回给浏览器
- 短连接情况下,请求结束则通过 TCP 四次挥手关闭连接,长连接在没有访问服务器的若干时间后,进行连接的关闭
- 浏览器得到响应信息中的 HTML 代码
- 浏览器对页面进行渲染并呈现给用户
常用HTTP状态码
1xx 服务器收到请求,需要请求者继续执行操作
2xx 成功
3xx 重定向,需要进一步的操作以完成请求
4xx 客户端错误
5xx 服务器错误
HTTP1.0和HTTP1.1和HTTP2.0的区别
HTTP1.0和HTTP1.1的区别:
HTTP1.1支持长连接
HTTP1.1中新增了24个错误状态响应码
HTTP1.1支持可以只发送header信息(header主要来存放cookie,token等信息的,
body主要用来存放post的一些数据,),不带任何body信息,如果服务器认为客户端有权限请求服务器,则返回100,客户端接收到100才开始把请求body发送到服务器;如果返回401,客户端就可以不用发送请求body了节约了带宽。(在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname),HTTP1.0没有host域。随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。)HTTP1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误(400 Bad Request)
HTTP1.1和HTTP2.0的区别:
- HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求
- HTTP2.0对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
- HTTP2.0引入了server push,它允许服务端提前推送相关响应给浏览器
Socket和http的区别
socket连接就是所谓的长连接,理论上客户端和服务器端一旦建立起连接将不会主动断掉
http连接就是所谓的短连接,即客户端向服务器端发送一次请求,服务器端响应后连接即会断开等待下次连接
对称加密与非对称加密
- 对称密钥加密是指加密和解密使用同一个密钥的方式
- 非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。由于非对称加密的方式不需要发送用来解密的私钥,所以可以保证安全性;
Get和Post的区别
- 功能不同:
get是从服务器上获取数据。
post是向服务器传送数据。 - 传输数据方式不同:
Get请求的数据会附加到URL中,传输数据的大小受到url的限制。
post在发送数据前会先将请求头发送给服务器进行确认,然后才真正发送数据。 - 安全性不同
get安全性非常低。
post安全性较高。
网络攻击
CSRF
攻击者通过伪造用户的浏览器的请求,向访问一个用户自己曾经认证访问过的网站发送出去,使目标网站接收并误以为是用户的真实操作而去执行命令
SYN攻击
SYN攻击即利用TCP协议缺陷,通过发送大量的半连接请求,占用半连接队列,耗费CPU和内存资源。
优化:
- 缩短SYN Timeout时间
- 记录IP,若连续受到某个IP的重复SYN报文,从这个IP地址来的包会被一概丢弃。
转发和重定向
转发是服务器行为。服务器直接向目标地址访问URL,将相应内容读取之后发给浏览器,用户浏览器地址栏URL不变,转发页面和转发到的页面可以共享request里面的数据。
重定向是利用服务器返回的状态码来实现的,如果服务器返回301或者302,浏览器收到新的消息后自动跳转到新的网址重新请求资源。用户的地址栏url会发生改变,而且不能共享数据。
操作系统
进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
进程的有独立的有独立的代码和数据空间,进程之间的切换会有较大的开销
上下文切换:任务从保存到再加载的过程就是一次上下文切换
线程调度是指按照特定机制为多个线程分配 CPU 的使用权。Java 虚拟机的一项任务就是负责线程的调度。
有两种调度模型:分时调度模型和抢占式调度模型。Java虚拟机采用抢占式调度模型。
- 分时调度模型:让所有的线程轮流获得 cpu 的使用权,平均分配每个线程占用的 CPU 的时间片。
- 抢占式调度模型:根据线程优先级、线程饥饿情况等数据算出一个总的优先级,优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。
什么是内核态和用户态?
内核态是操作系统管理程序执行时所处的状态·,能够执行包含特权指令在内的一切指令,能够访问系统内所有的存储空间。
用户态是用户程序执行时处理器所处的状态,不能执行特权指令,只能访问用户地址空间。
用户程序运行在用户态,操作系统内核运行在内核态
如何实现内核态和用户态的切换?
- 系统调用
Spring
Spring是一个轻量级Java开源框架,两个核心特性是DI和AOP
Spring采取了以下4种关键策略
亲呕生少代
基于POJO的轻量级和最小侵入性编程;
通过DI和面向接口实现松耦合;
基于切面和惯例进行声明式编程;
通过切面和模板减少样板式代码。
Spring的优点
- 方便解耦,简化开发。Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理
- AOP编程的支持。Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
- 方便集成各种优秀框架
Spring的缺点
- Spring依赖反射,反射影响性能
Spring由哪些模块组成
- spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
- spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
- spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
- spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。
Spring 框架中都用到了哪些设计模式?
工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
单例模式:Bean默认为单例模式。
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
模板方法:用来解决代码重复的问题。比如:RestTemplate, JmsTemplate, JpaTemplate。
观察者模式:定义对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被动更新,如Spring中listener的实现–ApplicationListener。
控制反转(IOC)
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对对象组件控制权的转移,从程序代码本身转移到了外部容器。
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。)
控制反转(IoC)有什么作用
- 管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的
解耦,由容器去维护具体的对象
托管了类的整个生命周期,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的
Spring IoC 的实现机制:
Spring 中的 IoC 的实现原理就是工厂模式加反射机制
BeanFactory 和 ApplicationContext有什么区别
- 加载方式:BeanFactroy时懒加载,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化、ApplicationContext是在容器启动时,一次性创建了所有的Bean
Spring的DI:
组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法,让容器去决定依赖关系。
有哪些不同类型的依赖注入实现方式
- 构造器注入:容器通过调用一个类的构造器来实现的,该构造器有一系列参数,每个参数都必须注入。
- Setter方法注入是容器通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法来实现的依赖注入。
面向切面编程(AOP)
通过面向切面编程减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。常用于权限认证、日志等。
Spring AOP中的动态代理
- JDK动态代理
只提供接口的代理,不支持类的代理 - CGLIB动态代理
如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类
Spring AOP里面的几个名词
- 切面(Aspect):切面是通知和切点的结合。
- 切点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点
- 连接点(Join point):一个连接点总是代表一个方法的执行
- 通知(Advice):切面的工作被称为通知。
- 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。
Spring通知有哪些类型?
- 前置通知
- 后置通知
- 返回通知
- 异常通知
- 环绕通知
Spring Beans
解释Spring支持的几种bean的作用域
通过scope属性来定义作用域:常用的有singleton(默认)和prototype
Spring框架中的单例bean是线程安全的吗?
很多个线程会共享这个bean,是对同一块内存进行操作。
- 如果bean是无状态(有状态就是有数据存储功能(比如dao 类)),spring的单例bean是线程安全的,
- 如果bean是有状态(无状态就是不会保存数据(比如比如 view model 对象)),spring的单例bean是线程不安全的。
- 有状态的话通过原型模式就可以保证线程安全
Spring如何处理线程并发问题?
只有无状态的Bean才可以在多线程环境下共享
采用ThreadLocal进行处理或者进行加锁,解决线程安全问题。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
Spring中bean的生命周期
bean 是由 Spring IoC 容器实例化、组装和管理的对象。
Spring中bean的生命周期:主要由实例化、属性赋值、初始化、销毁这 4 个大阶段。
Spring对bean进行实例化:
- 通过反射来进行实例化
Spring为 bean 设置相关属性和依赖:
- 解析自动装配
初始化:
- 检查aware的相关接口并设置依赖
- 检查是否实现InitializingBean接口
- BeanPostProcessor进行前后置处理进行属性填充和代理封装
销毁
- destroyMethod和DisposableBean进行销毁
Spring注解
@Autowired自动装配
在启动spring IoC时,容器就会自动装载了一个后置处理器。当容器扫描到@Autowied、@Resource时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的属性;
如果查询的结果不止一个,会抛出异常,需要配合@Qualifier注解根据名称来查找;
@Autowired和@Resource之间的区别
@Autowired默认按类型装配、可以和@Qualifier结合使用按名字装配
@Resource 默认是按照名称进行装配
Spring的源注解
- @Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
- @Controller:标注控制层组件
- @Service:标注业务层组件
- @Repository:标注DAO层组件
Spring事务
Spring事务的本质其实就是数据库对事务的支持
Spring支持的事务管理类型/Spring事务实现方式有哪些
Spring支持两种类型的事务管理:
- 编程式事务管理:通过编程的方式管理事务
- 声明式事务管理:将业务代码和事务管理分离,只需用注解和XML配置来管理事务。
spring 的事务隔离级别
和数据库的隔离级别是一致的
Spring MVC
通过把模型-视图-控制器分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
Spring MVC的主要组件
- 前端控制器 DispatcherServlet
- 处理器映射器HandlerMapping(根据请求的URL来查找Handler)
- 处理器Handler
- 处理器适配器HandlerAdapter
- 视图解析器 ViewResolver(进行视图的解析,根据视图逻辑名解析成真正的视图(view))
- 视图View
请描述Spring MVC的工作流程
- 用户发送请求至前端控制器DispatcherServlet;
- DispatcherServlet解析用户访问的 URL
- 并调用处理器映射器获取handler(controller)
- DispatcherServlet 调用处理器适配器和视图解析器进行返回视图
- DispatcherServlet 对视图进行渲染返回给客户端
SpringBoot
Spring Boot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简化了繁重的配置,提供了各种启动器,开发者能快速上手。
Spring Boot 有哪些优点?
- 容易上手,提升开发效率
- 没有代码生成,也不需要XML配置。
- 避免大量的 Maven 导入和各种版本冲突。
Spring Boot的启动流程
- 启动入口
- @SpringBootApplication + psvm(main方法)+ new SpringApplication().run(XXXX.class, args)
- @SpringBootApplication 注解由@Configuration(注解类),@EnableAutoConfiguration(将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器),@ComponentScan(自动扫描并加载符合条件的组件)组成
- 进行实例化
设置初始化器(Initializer)
设置监听器(Listener)
- 启动方法
创建应用上下文
ApplicationContext前置处理
ApplicationContext刷新
ApplicationContext后置处理
Spring Boot Starter是什么
可以理解为一个可拔插式的插件
你想使用Reids插件,那么可以导入spring-boot-starter-redis依赖
spring-boot-starter-parent 有什么用
引入和定义starter相关的东西
spring boot 核心配置文件
bootstrap.properties 和application.properties
application :由ApplicatonContext 加载,用于 spring boot 项目的自动化配置。
bootstrap :在 Spring Cloud 中会用到
Spring Boot 打成的 jar 和普通的 jar 有什么区别
Spring Boot 打成的 jar是可执行 jar,不可以作为普通的 jar 被其他项目依赖
并发
JVM和本地的操作系统的线程有着一种对应的关系
并发编程三要素(线程的安全性问题体现在)
线程切换–原子性:一个或多个操作要么全部执行成功要么全部执行失败。–Atomic开头的原子类、synchronized、lock,可以解决原子性问题
缓存–可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。–volatile、synchronized、lock
编译优化–有序性:程序执行的顺序按照代码的先后顺序执行,避免指令重排。–volatile、Happens-Before
创建线程的四种方法
- Thread类(最终也是实现Runnable接口)
- Runnbable接口 (通过run方法来开启它的生命周期。run方法必须是公有的不带任何参数,没有返回值,且不抛出异常)
- Callable接口(有返回值,和Future混合使用,callable用来产生数据,future用来汇总数据)
- 线程池
start() 方法用于启动线程,run() 方法用于执行线程任务
前两个是在run方法实现逻辑
callable是在call方法实现逻辑
什么是 Future 和 FutureTask?
Future 接口表示异步计算的任务,他提供了判断任务是否完成,中断任务,并可以通过get方法获取任务执行结果,该方法会阻塞直到任务返回结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
FutureTask 类间接实现了 Future 接口,可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
线程的基本操作:
interrupted(中断)其他线程可以调用该线程的interrupt()方法对其进行中断操作
join 让一个线程去等待另一个线程,直到另一个线程全部执行完成后,才可以执行该线程
sleep让某个线程让出CPU一会
sleep方法没有释放锁Thread,而wait方法释放了锁Object
wait() 方法通常被用于线程间交互/通信,sleep() 通常被用于暂停线程执行。
wait得使用notify才可以被唤醒
yield(避让)只避让优先级比他高的线程
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
notifyAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态
中断线程
interrupt()是给线程设置中断标志;
interrupted()是检测中断并清除中断状态;
isInterrupted()只检测中断。
还有重要的一点就是interrupted()作用于当前线程,interrupt()和isInterrupted()作用于此线程
线程池
在主线程中开启多个线程并发执行一个任务,然后收集各个线程执行返回的结果并最终汇总起来。
线程池的优点
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性,进行统一分配、调优和监控
线程池的使用
- Runnable方式
- 创建服务进行实现线程池
- 执行线程
- 关闭连接
public class C {public static void main(String[] args) {//创建服务进行实现线程池ExecutorService service = Executors.newFixedThreadPool(2);//进行执行线程service.execute(new MyThred());service.execute(new MyThred());service.execute(new MyThred());service.execute(new MyThred());//关闭连接service.shutdown();}}
class MyThred implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}
- Callable方式
- 创建服务进行实现线程池
- 执行线程
- 获取结果
- 关闭连接
public class C implements Callable{@Overridepublic String call() {// run方法线程体return Thread.currentThread().getName();}public static void main(String[] args) throws ExecutionException, InterruptedException {C t1 = new C();C t2 = new C();C t3 = new C();//(1)创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(3);//(2)提交执行Future<String> r1 = ser.submit(t1);Future<String> r2 = ser.submit(t2);Future<String> r3 = ser.submit(t3);//(3)获取结果System.out.println(r1.get());System.out.println(r2.get());System.out.println(r3.get());//(4)关闭服务ser.shutdown();}}
Java线程池的的工作原理
- JVM先根据用户参数创建一定数量的可运行的线程任务,并将其放入队列中,在线程创建后启动这些任务,如果线程数量超过了最大线程数量(用户设置的线程池大小),则超出数量的线程排队等候,在有任务执行完毕后,线程池调度器会返回现有可用的线程,进而再次从队列中取出任务并执行。
- 线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数、以保证高效(通过线程资源复用实现)且安全(通过控制最大线程并发数实现)地运行。使用线程池启动线程:处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
线程池的核心组件
- 线程池管理器:用于创建并管理线程
- 工作线程:线程池中执行具体任务的线程
- 任务接口:用于定义工作线程的调用和执行策略,只有线程执行了该接口,线程中的任务才能被线程池调度
- 任务队列:存储待处理的任务,新的任务会不断增加到队列中去,执行完成的任务将被从队列中移除
线程池的拒绝策略
- 直接抛出异常,阻止线程正常运行
- 根据FIFO将最早的线程移除
- 丢弃当前线程
常用的线程池
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newCachedThreadPool创建了一个可缓存的线程池。当有新的任务提交时,有空闲线程则直接处理任务,没有空闲线程则创建新的线程处理任务
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newWorkStealingPool是一个具有工作窃取功能的线程
线程间的通信
等待唤醒机制:
就是在一个线程进行了规定操作后,就进入等待状态(wait), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify);
Java的锁:
锁主要用于保障并发线程情况下数据的一致性
Java中每个对象都有一把锁,存放在对象头中。锁记录了当前对象被哪个线程占用
对象头由MarkWord(hashCode、锁标志位(无锁、偏向锁、轻量级锁和重量级锁))和ClassPoint(当前对象类型所在方法区中的类型数据)组成
锁的优化:
无锁–>偏向锁–>轻量级锁–>重量级锁
无锁:CAS
偏向锁:只有一个线程,该线程第二次访问就不需要在获取锁,可以直接执行。当有多个线程时,偏向锁直接升级为轻量级锁
轻量级锁:
重量级锁:需要通过monitor来进行控制
适应性自旋锁:其他线程也想获取对象时,会先自旋一定时间(上一次在同一个锁上帝自选时间和锁的状态来决定),规定时间内还没获得锁就会退出CPU。当自旋线程超过一个的时候就会升级重量级锁
AQS:
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程并将共享资源设置为锁定状态,如果被请求的共享资源被占用则将他移入等候队列中。
AQS里面的核心方法有tryAcquire(尝试获取锁)和acquire(获取锁)愿意进入队列(FIFO)等候,直到获取
CAS:
准备要被更新的变量V,旧的预期值A,要修改的新值B
比较失败时不会被立即挂起,看持有锁的线程是否会很快释放锁
synchronized
可以用来同步线程,别编译后会生成monitorenter和monitorexit两给字节码指令来进行线程同步,一次只有一个进程进入monitor,其他线程就要等待。
一个线程进入monitor进入active状态,因为一些原因,该线程需要暂时让出执行权,此时这个线程就进入wait状态,此时另一个线程可以进行执行,执行完任务后。可以使用notify唤醒处于wait的线程
synchronized属于独占式的悲观锁,同时属于可重入锁。
synchronized作用于非静态方法–锁定的是方法的调用者
synchronized作用于静态方法–锁定的是类
synchronized作用于代码块–锁定的是传入的对象
ReentrantLock
可重入性指的是:一个线程可以不用释放锁来获取同一个锁n次,释放的时候也要相应的释放n次
- 继承了lock接口,是可重入的互斥锁,虽然具有与synchronized相同功能,但是会比synchronized更加灵活
- ReentrantLock通过AQS来实现锁的获取与释放
内部最核心的三个内部类:Sync、NonfailSync、FailSync
- sync实现了AQS,内部的方法有 :非公平的尝试获取锁(非公平的尝试获取锁放在SYnc中说明:tryLuck一定是非公平的)、尝试释放锁(true代表完全释放,而不是是否被释放)
- NonfailSync 非公平锁 。内部的方法有:尝试获取锁(一直尝试插队)和获取锁
- FailSync 公平锁。内部的方法有:尝试获取锁(必须现在队列中排队)和获取锁
lock接口:
lock接口定义了6个方法:
- lock():获取锁,当前锁被其他线程占用,那么它将会等待知道获取为止
- lockInterruptibly():当前线程在等待锁的过程中被中断,将会推出等待
- tryLock():尝试获取锁
- tryLock(含参数):尝试在一段时间获取锁
- unlock():释放锁
- new Condition()准备阶段
volatile
可见性和禁止重排
数据库
数据库的三大范式
第一范式
每个列都不可以再拆分、所有的属性必须是原子级别的
第二范式
非主属性完全依赖于主键,不能只依赖主属性的一部分
第三范式
非主键只依赖主键,不存在循环依赖
数据库连接泄露
如果在某次使用或者某段程序中没有正确地关闭 Connection、Statement 和 ResultSet 资源,那么每次执行都会留下一些没有关闭的连接,这些连接失去了引用而不能得到重新使用,因此就造成了数据库连接的泄漏
使用连接池+短连接的设置来解决 数据库连接泄露
触发器
在满足定义条件时触发,并执行触发器中定义的 语句集合
索引
索引是一种 数据结构。数据库索引是 DBMS 中一个 排序的数据结构,以 协助快速查询、更新 数据库表中数据。索引的实现通常使用 B 树以及变种 B+ 树或者哈希。
索引就相当于目录,其存在是为了方便数据内容查找,本身也占用物理空间。
索引的优点
- 可以加快数据的 检索速度
- 通过创建 唯一性索引,可以保证数据库表中每一行数据的唯一性
- 可以加速表和表之间的连接
索引的缺点
- 时间上,创建和维护索引都要耗费时间,这种时间随着数据量的增加而增加,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度;
- 空间上,索引需要占 物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
索引的数据结构
B 树索引、Hash 索引 和 位图索引
B+树的好处
由于 B+ 树的内部结点只存放键,不存放值,因此,一次读取,可以在同一内存页中获取更多的键,有利于更快地缩小查找范围。
B+ 树的叶结点由一条链相连,因此当需要进行一次 全数据遍历 的时候,B+ 树只需要使用 O(logN) 时间找到最小结点,然后通过链进行 O(N) 的顺序遍历即可;或者,在找 大于某个关键字或者小于某个关键字的数据 的时候,B+ 树只需要找到该关键字然后沿着链表遍历即可。
B+树索引和Hash索引的区别
- Hash 索引进行等值查询更快(一般情况下),但是却无法进行范围查询;
- Hash 索引不支持使用索引进行排序;
- Hash 索引不支持模糊查询以及多列索引的最左前缀匹配,原理也是因为 Hash 函数的不可预测;
- Hash 索引任何时候都避免不了回表查询数据,而 B+ 树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询;
- Hash 索引虽然在等值查询上较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生 Hash 碰撞,此时效率可能极差;而 B+ 树的查询效率比较稳定,对于所有的查询都是从根结点到叶子结点,且树的高度较低。
前缀索引
有时需要索引很长的字符列,它会使索引变大并且变慢,一个策略就是索引开始的几个字符,而不是全部值,即被称为 前缀索引。
前缀索引的缺点是索引值重复性越低,查询效率也就越高。相应的,索引值重复性越高,查询效率也就越低
最左前缀匹配原则
在 MySQL 建立 联合索引(多列索引) 时会遵守最左前缀匹配原则,即 最左优先,在检索数据时从联合索引的最左边开始匹配。
添加索引的原则
- 为频繁访问的数据创建索引
- 数据经常修改的不建立索引
- 数据很少的时候也不建立索引
聚簇索引和非聚簇索引
聚簇索引就是将数据和索引放在一起,因此从聚簇索引中获取数据比非聚簇索引更快。InnoDB支持聚簇索引,非叶子节点不存放数据,叶子节点存放主键以及对应的行记录
数据库的事务
包含了一组数据库操作命令。
其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行,因此事务是一个不可分割的工作逻辑单元。如果任意一个操作失败,那么整组操作即为失败,会回到操作前状态或者是上一个节点。
数据库的分布式任务:
CAP一致性可用性和分区容错性
两阶段提交:
使分布式数据库的所有节点在事务提交都保持一致性而设计的一种算法
参与者将操作失败通知告诉协调者,
再由协调者根据所有参与者的反馈决定其他参与者提交操作还是终止操作。
三阶段提交:
引入超时节点:如果长时间接不到参与者的反馈,则认为参与者失败
在第一阶段和第二阶段提交中间加入一个预准备阶段,以保证在任务提交之前参与者状态是否一致。
where、groupby、having
where先执行,再groupby分组;groupby先分组,having在执行
索引
索引分为主键索引,唯一索引,联合索引和普通索引
如果非聚簇索引查询的字段,完全命中了索引,索引就不需要回表查询,这个过程被称为索引覆盖
MySQL使用自增主键的好处:
插入数据时减少分页和叶子的移动
MVCC
MVCC并发版本控制实现了读写不阻塞降低了死锁的概率,写加锁,读不加锁。解决了不可重复读读,和一部分幻读问题。
解决幻读还是得间隙锁(一种锁定相邻行的锁)
MVCC的原理:主要通过ReadView和undoLog实现的,unlog用来保存历史数据,readView进行匹配。
将数据的历史版本进行保存,通过比较版本号来验证数据是否修改,所以不需要加锁
每一个事务都有三个隐藏字段(事务id,历史版本,隐藏id)
ReadView通过活跃事务的id和最小id和下一个事务id进行匹配
读已提交和可重复读
在于readView的时机不同
数据库连接泄露:
连接后没有关闭资源,是该资源一直处于不让其他请求访问的状态
SQL优化:
explain:type、id、possibleId
SQL的生命周期:
客户端通过TCP连接到MySQL的服务器、首先查询缓存、进行语法分析,尽心优化,交给执行器进行执行、关闭连接
一条更新语句的执行流程:
WAL 技术:先写日志,再写磁盘
更新流程还涉及两个重要的日志模块,
redolog和binlog
InnoDB 引擎就会先把记录写到 redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面
日志
redolog具有crash-safe的能力
binlog: binlog是服务层特有的日志
redolog是逻辑日志,binlog是物理日志
redolog是循环写,binlog是追加写
数据库的优化:
一个是SQL语言的优化
- 用具体的字段列表代替“*”
- 少使用否定句,因为否定句会使索引失效
- 多用limit 总之就是尽可能少的输出信息
另一个是结构的优化
- 将含有多个字段的表进行拆表细化
- 增加中间表
千万数据的表的优化
限定数据行数
读写分离
缓存
分库分表
MySQL的主从复制原理以及流程:
将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行,从而使得从数据库的数据与主数据库保持一致。
第一步将DML和DDL操作放入binlog中
第二步从数据库开启一个IOThread将binlog里的数据写入到replayLog中
第三步SqL线程会读取中继日志,并按顺序执行该日志的SQL事件,从而与主数据库保持一致
删除数据的速度和创建的索引数量是成正比的,所以在面对海量数据时可以先删除索引在删除无用数据,再将索引创建回来
Java基础
Java为什么能跨平台
因为JVM的存在,不同的操作系统对应不同的JVM。Java源程序经过编译器变成字节码,字节码通过JVM被翻译成机器码。
字节码是什么
Java源代码经过虚拟机编译器编译后产生的文件,只面向虚拟机
字节码的好处
使用字节码既能够解决编译型语言不能跨平台,有解决的解释型语言执行效率低的问题
Java有哪些数据类型
基本数据类型:
基本类型 大小 范围
byte 1 -128--127
short 2 -2^15--2^15-1(5位数)
int 4 -2^31--2^31-1(10位数)
long 8 -2^63--2^63-1
char 2 0--65535
boolean 1 true\false
float 4
double 8
访问修饰符
&&和&的区别
逻辑与&&左边不符合条件,右边就不会执行
按位与&左边不符合条件,右边仍然会执行
final finally finalize区别
- final通常被定义在属性或者方法前,别限定为不可被改变,不可被继承
- finally是在异常处理的关键字,代表着无论是否出现异常,finally都会被执行
- finalize一般由垃圾回收器调用
this super关键字的用法
this执行本身
super只想父类
static存在的主要意义
- 没被static修饰的方法都要依赖于实例进行调用。有了static进行修饰,那么即使没有实例也能进行调用,JVM会优先对static修饰的方法或属性及进行初始化
- static的另一个作用就是可以形成代码块来优化程序的性能
面向对象的三大特性
封装
把一个对象的属性或方法进行私有化,隐藏了的内部实现细节,提供了被外界访问的接口。提高了程序的安全性
继承
子类继承父类,可以继承他的非私有属性和方法,也可以进行扩展。提高了代码的复用性
多态
多态的三个必要条件:继承、重写、向上转型(父类的引用指向子类)
Java多态分为运行时多态和编译时多态
- 运行时多态:重载
- 编译时多态:重写
Animal a=new Cat();
a只可以使用Animal里的方法,但是实现是由Cat来实现的
抽象类和接口的对比
- 抽象类的关系是继承,接口的关系是实现
- 只可以继承一个抽象类,可以实现很多个接口
- 抽象类可以有实现细节,接口不可以有细节
- 抽象类可以有非抽象方法,接口必须是抽象方法
- 抽象类的字段声明是任意的,接口只能是 final static
- 抽象类的修饰可以是任意的,接口的修饰必须是public
Java中只有值传递
引用类型作为参数本传递时也是值传递,只不过值为对应的引用
Java中的IO流
字节流:输入InputStream、输出InputReader
字符流:输入InputReader、输出InputWriter
BIO,NIO,AIO
反射
反射是指在运行状态中,对于任意一个类,都能动态的获取和调用这个类的所有属性和方法
反射能够提高代码的灵活性,但是缺点在于反射使Java代码变得很慢,因为反射会产生大量临时对象,这些对象会占用内存,会导致频繁的垃圾回收
自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;原理:Integer.valueOf() 方法
拆箱:将包装类型转换为基本数据类型;原理:Integer.intValue() 方法
Integer a= 127 与 Integer b = 127相等吗
如果整型字面量的值在-128到127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
字符串常量池
因为String在Java世界中使用过于频繁,为了提高内存的使用率,避免开辟多块空间存储相同的字符串,便引入了字符串常量池(位于堆内存中)
流程:
创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
异常
Throwable包含两个子类,错误和异常
Error在程序运行中不太容易出现,通常为JVM相关错误(比如:系统崩溃,内存不足,堆栈溢出等)应用程序本身是无法恢复的
Exception 是程序正常运行中,可以预料的意外情况,通常遇到这种异常,应对其进行处理,使应用程序可以继续正常运行。
运行时异常(非受检查异常)和编译时异常(受检查异常)
Java 编译器不会检查运行时异常,比如空指针异常数组越界异常
Java 编译器会检查编译时异常,比如ClassNotFoundException
JVM如何处理异常
发生异常–>创建一个异常对象–>交给JVM去处理–>JVM顺着调用栈检查能否处理–>如果不能就返回给默认处理器进行打印异常信息
throw 和 throws 的区别是什么
- throw 关键字用在方法内部,只能用于抛出一种异常
- throws 关键字用在方法声明上,可以抛出多个异常
try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会执行,在 return 前执行。
NoClassDefFoundError 和 ClassNotFoundException 有什么区别
NoClassDefFoundError是一个错误(Error),而ClassNOtFoundException是一个异常
NoClassDefFoundError产生的原因在于:如果JVM或者ClassLoader实例尝试加载类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个时候就会导致NoClassDefFoundError。
ClassNotFoundException的产生原因主要是:一个类已经被某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类。
或者在反射时将指定类加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常。
Java常见的异常
Error
- java.lang.OutOfMemoryError
- java.lang.StackOverflowError
Exception
常见的异常
- IOException 读写异常
- NullPointerException 空指针异常
- ClassCastException 类型转换异常
- IndexOutOfBoundsException 数组越界异常
深拷贝和浅拷贝:
浅拷贝是指增加了一个指针指向内存的地址
深拷贝是指增加了一个指针并新开了一块内存,是这个指针指向新的内存
什么是字节码
字节码时Java特有的一种编码结构,是由源文件经过编译器生成的一种文件。
因为字节码的存在,使得Java能够进行跨平台
switch
switch的作用域除了长整型都可以
static关键字
可以使方法或者属性,不依赖于对象进行调用。static修饰的方法或属性会随着类的加载而优先于对象进行加载
面向对象的优势
具有封装继承多态三大特性,使得代码,具有低耦合、强扩展、易维护、易复用的优势
抽象类和接口的区别
抽象类:可以有非抽象非法,可以有实现细节,类和抽象类的关系是继承,抽象类里的变量可以是任意的
接口:不能有实现细节,可以实现多个接口,接口中只能存在抽象方法,接口中成员变量只能是public static final
抽象类可以被final修饰吗
不可以,因为一旦抽象类被final修饰就不能够被其它类继承
什么是不可变对象
不可变对象是一种一旦被创建就不能再被改变。如String、包装类。不可变对象最大的好处是线程安全
==和equals的区别
- ==在引用类型时比较的是引用是否指向了同一块内存区域,在比较基本类型时比较的是值是否形同
- 对equals进行重写可以比较两个对象的内容是否相同,内部自带重写了String类型
hashcode
一个对象经过Hash运算获得一个哈希码。在进行检索时,根据哈希码能够很快的找到对象在哈希表中的位置。因为有hashCode的存在在检索时首先会比较hashCode的值,如果存在哈希冲突,可以通过引入一个链表的方式来解决,将当前对象和有相同hashCode的值进行equals比较,相同则替换,不同则加入链表,在JDK1.7中是头插法,在JDK1.8是尾插法(防止链表产生循环)。
String、Stringbuffer、Stringbuilder
String是不可变的所以是线程安全的。
是因为Stringbuffer内部用了synchronized关键字进行修饰,也是线程安全的
String为什么设计成不可变的
- 为了保证多线程并发下安全
- 在进行定义url等不想被该表的参数时,用String更加保证了安全
- 基于他是不可表的,所以更便于实现常量池,保证了相同的对象,只需要在堆中开一块内存就行
- 加快了hash时的处理速度,因为String是不可以变的,保证了安全性,使得创建hashCode在创建时就可以放心的进行缓存。
字符串常量池(堆)
主要是为了避免相同的对象在堆内存中多次创建。
String ans=“a”,会优先在常量池中进行检索,如果存在则直接返回。不存在则进行创建
String ans="a"和String ans=new String(“a”)的比较
- 当我们进行字符串创建时,首先会在常量池中进行检索,存在则直接返回。不存在则在常量池中进行创建,并将引用进行返回。
JDK1.6常量池存在于方法区,存储的是对象。JDK1.7常量池存在于堆中,存储的是引用。
String str4 = "abc"+"efg";
String str5 = "abcefg";
//这句代码并没有创建对象,它从常量池中找到了"abcefg" 的引用
System.out.println(str4 == str5);//返回TRUE
- 当我们使用了new 来构造字符串对象的时候,会在堆内存中开辟出一块新空间,如果常量池中没有则同时将被创建的字符串放进常量池,相当于创建了两个对象。如果常量池中有则创建一个对象。
// 如果常量池中没有"aa",创建了两个“aa”对象,一个存在字符串常量池中,一个存在堆中,返回的堆中的对象
String str3 = new String("aaa");
intern()方法
当调用这个方法时,会去检查字符串常量池中是否已经存在这个字符串,如果存在的话,就直接返回,如果不存在的话,就把这个字符串常量加入到字符串常量池中,然后再返回其引用。
- 在JDK1.6中,如果字符串常量池中已经存在该字符串对象,则直接返回池中此字符串对象的引用。否则,将此字符串的对象添加到字符串常量池中,然后返回该字符串对象的引用。
- 在JDK1.7中,如果字符串常量池中已经存在该字符串对象,则返回池中此字符串对象的引用。没有检索到则在堆中进行创建,并把引用加入到常量池,以后别人拿到的就是该字符串常量的引用,实际上存储在堆中。
非new生成的integer对象和new Integer()生成的变量对比
非new生成的integer对象在(-128–127)内是存在于常量池的,不在这个范围的和new Integer()一样在堆中生成。
当值在-128–127时Java会自动装箱,然后对值进行缓存,缓存是通过内部的cache数组来完成的,超出这个范围会在堆中来存储
反射
在运行状态下,对于任意一个类都能通过反射获取这个类的所有属性和方法,增加了代码的可扩展性和复用性。
反射的三种方式:
- Class.forName()
- 类名.Class
- 对象名.getClass()
反射的API:
类、属性、方法、接口
反射的步骤:
- 获取类的对象
- 获取类的构造器进行实例
- invoke进行调用方法
为什么引入反射
对于任意一个类,都能够知道这个类的所有属性和方法
可以最大限度的不破坏代码完整性的前体下进行扩展一些功能,降低模块的耦合性
反射的使用步骤
JDBC连接数据库
Spring框架AOP的动态代理就是通过反射来实现的
反射机制的原理
本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
Java中的泛型
泛型就是将类型参数化进行限定,泛型的原理是类型擦除。泛型是在编译过程完成的
使用泛型的好处是:
- 消除了了强制转换
- 在编译时就能检查出因Java类型不正确导致的ClassCastException异常,符合越早出错代价越小
- 潜在的性能收益。 泛型为较大的优化带来可能。
Java的序列化
序列化:Java对象转换为字节序列的过程
反序列化:字节序列转换为Java对象的过程
序列化与反序列化的作用主要是对象的保存与重构,序列化主要应用于将内存的对象进行持久化或者实现网络传输(不同文件的格式是不一致的,通过序列化进行统一格式)
序列化的实现方式
- Serializable:更方便的进行序列化
- Enternalizable:里面有两个方法,如果不进行实现所有的值都是默认值(0,null)可以进行定制化的序列化,序列化那哪些东西由程序员来决定
SerialVersionUID
通过SerialVersionUID来验证版本的一致性,进行序列化时会先根据属性子自动生成一个唯一的UID,然后根据字节序列一同持久化或者网络传输。进行返序列化时会进行验证版本一致性。如果serialversionuid一致,说明他们的版本是一样的。反之,就说明版本不同,就无法运行或使用相关功能
一般来说都会显示的进行定义,因为一旦被序列化的版本更新后,SerialVersionUID会随着新的属性生成新的UID,那么就会不兼容。一旦显示的进行声明,那么在迭代的过程中也可以匹配
不想序列化的字段
用transient进行修饰,他修饰后变量的值均为初始值
静态变量不会进行初始化
因为序列化是针对对象而言的,而静态变量优先于对象存在的,随着类的加载而加载。
字节流是如何转换为字符流的
输入:InputStreamReader
输出:OutputStreamWriter
字节流和字符流的区别
字节流是按字节进行读取的,字节流适合所有的类型文件进行传输,和文有关的文件一般用字符流
集合
list:
- arraylist 动态数组
- linkedlist 双向循环链表
- vector 动态数组
set:
- hashset
- treeset
- Queue:
map:
- hashMap:存储的是映射关系的键值对,存储在哈希表中
- TableMap
Java集合的fail-fast机制
线程1在遍历集合的时候,线程2对该集合进行修改。就会抛出fail-fast
解决办法:synchronized或者使用CopyOnWriteArrayList来代替Arraylist
集合可以用迭代器进行一边遍历一便删除
ArrayList
ArrayList底层是个动态数组:
add方法有四种:
- 添加元素到末尾,首先判断容量,然后添加在尾部
- 在指定位置添加一个元素,首先判断容量,在判断加入的位置合不合理,将数据整体向后移动一位
- 添加一个集合,将集合转换为数组,增加原数组容量然后挨个向后添加
- 在指定位置,添加一个集合,将原来的数组挨个向后迁移,然后把新的集合加进数组
ArrayList的扩容机制:
- 首先创建一个空数组elementData,第一次插入数据时直接扩充至10,
- 然后如果elementData的长度不足,就扩充至1.5倍,
- 如果扩充完还不够,就使用需要的长度作为elementData的长度。
set是如何检查重读的:
首先比较哈希值然后进行比较equals,hashSet的add方法会使用HashMap的put方法。
HashSet的值是唯一的,hashset中添加进去的值就是作为哈市Map的key,相同时会用新的覆盖旧的值。所以不会重复
HashMap的插入流程:
在put数据的时候,首先计算key的hash得到应该插入的位置(经过两次扰动。高16位与低16位进行异或操作,得到hash,再和length-1进行与运算)
如果Table为空进行resize,得到index后找到对应的位置,如果该位置啥都没有则直接插入,否则进行equals比较,如果存在则进行直接覆盖,不存在则判断是否是树,不是树就插入链表的位置,插完后,进行判定是树化还是扩容
HashMap的扩容操作
①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容;
②.每次扩展的时候,新容量为旧容量的2倍;
③.扩展后元素的位置要么在原位置,要么移动到原位置 + 旧容量的位置。
HashMap为什么线程不安全
- 多线程下扩容死循环
- 多线程put操作可能导致元素丢失
- get和put并发时,可能导致get为null
HashMap 的长度为什么是2的幂次方
在进行第二次哈希扰动的时候前提是length是2的n次方
HashMap是怎么计算Hash值的
首先根据key的hashCode计算hash值,计算方法是将key的hashCode值的高16位与hashCode值进行异或运算。再将hash&(length-1)得到数组中的位置
ConcurentHashMap
1.7是segment继承了ReentrantLock,通过HashEntry数组和segment数组一起联合使用
(一个segment数组被分成很多段HashEntry数组)
1.8是采用Node + CAS + Synchronized来保证并发安全(Node数组)
Swagger
Swagger提供了用于生成,可视化和维护API文档的一系列解决方案,从而使API文档不再需要人工操作。
使用Swagger解决的问题?
保证文档的时效性:只需要少量的注解,Swagger 就可以根据代码自动生成 API 文档,代码变文档跟着变
使用swagger:
1.添加依赖
2.添加Swagger2Config配置类
3.通过注解使用swagger
Swagger2常用注解说明
@Api 用在请求的类上,表示对类的说明
@ApiOperation 用在请求类的方法上,说明方法的用途和作用
@ApiParam可用在方法,参数和字段上,一般用在请求体参数上,描述请求体信息
例子:
@PostMapping
@ApiOperation(value = “新增用户”)
public Boolean insert(@RequestBody @ApiParam(name = “UserDTO”, value = “新增用户参数”) UserDTO userDTO) {
list.add(userDTO);
return true;
}
Redis
Redis是现在最受欢迎的NoSQL数据库之一。
- 基于内存运行,性能高效
- 支持分布式,理论上可以无限扩展
- 支持持久化和集群以及事务
- key-value存储系统
Redis的用途
1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)
Redis的基本知识以及操作
Redis有16个数据库,默认是第0个。
Redis的端口是6379
切换数据库:select 2
显示数据库大小: DBSIZE
清空数据库: flush/flushall查看所有的key: keys *
设置过期时间: expire name 10
查看当前的key的剩余时间:ttl name
判断一个key是否存在: exist name
设置过期时间: persist(坚持)
移除当前的key: move
查看当前key的类型: type name
自动增加1:incr no打印: echo
返回值的类型:type addr如果存在则返回0,不存在则可以存入:setnx
测试连接是否连通:ping
退出连接:quit
返回当前数据库中key的数目:dbsize
为什么单线程的Redis能那么快
- CPU不是redis的性能瓶颈,内存大小和网络带宽才有可能是redis的瓶颈
- Redis 采用多路复用策略,省去了上下文切换的时间
- redis是基于内存的,内存的读写速度非常快
- 高效的数据结构string,list,set,zset,hash
Redis的五大数据类型
当最后一个元素移除时,数据结构都会被删除
Redis所有的数据结构都可以设置过期时间,时间到了,Redis会自动删除相应的对象
String
最简单的类型一个Key对应一个Value
Redis的字符串是动态字符串,内部实现类似于Java中的ArrayList采用预分配冗余空间的方式来减少内存的频繁分配。当字符串长度小于1M时,扩容都是加倍现有空间,当超过1M时扩容只会多扩1M。
hash
添加用户:zadd salary 2500 xiaohong
显示全部的用户 从小到大:ZRANGEBYSCORE salary -inf +inf
从大到进行排序:ZREVRANGE salary 0 -1
是一个String类型的field和value的映射表,他的添加和删除都是O(1)。相当于Java的HashMap,同样是数组和链表的二维结构。
为了不阻塞服务采用了渐进式的rehash策略
list
左侧插入:lpush list 1
移除list的第一个元素:Lpop list
右侧插入:Rpush list 2
移除list的最后一个元素:Rpop list
获取list的值: LRANGE list 0 -1
通过区间获取具体的值:LRANGE list 0 1
通过下标获得 list 中的某一个值:lindex list 1
返回列表的长度:Llen list
主要用于消息排队。
是一个链表结构,相当于Java语言的LinkedList。就是一个每个子元素都是String的双向链表,可以同时作为队列和栈两种数据结构。
并不是一个简单的LinkedList而是一种quicklist的快速列表。qulicklist是ziplist和linked的混合体,他将linkedlist分解成多段,每一段用ziplist来紧凑存储,多个ziplist之间使用双向指针串联起来。
为了进一步节约空间Redis还会对ziplist进行压缩存储使用LZF算法压缩,可以选择压缩深度。
qulicklist默认压缩深度是0,为了支持快速的push/pop操作quicklist的首尾两个ziplist不进行压缩,此时深度为1。
如果首尾的两个不进行压缩,则深度为2。
set
set集合中添加:sadd myset "hello"
查看指定set的所有值:Smembers myset
判断某一个值是不是在set集合中:SISMEMBER myset hello
获取set集合中的内容元素个数:scard myset
交集:SINTER key1 key2
并集:SUNION key1 key2
set是hashSet实现的,具有自动去重的功能,还可以进行取交集(SINTER)和并集(SUNION)(实现共同好友和可能认识的人)
zset
是一个有顺序的Set,每个元素都会关联一个double类型的score,sorted set的实
现是skip list和hash table的混合体。
内部是一种跳跃表
Redis提供了三个高级数据结构
geospatital(地理空间)
推算地理位置、计算两地距离、找附近的人
# getadd 添加地理位置
# 规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
# 有效的经度从-180度到180度。
# 有效的纬度从-85.05112878度到85.05112878度。
# 当坐标位置超出上述指定范围时,该命令将会返回一个错误。 # 参数 key 值()
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqi 114.05 22.52 shengzhen (integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
获得当前定位:一定是一个坐标值
127.0.0.1:6379> GEOPOS china:city beijing # 获取指定的城市的经度和纬度!
1) 1) "116.39999896287918091" 2) "39.90000009167092543" 127.0.0.1:6379> GEOPOS china:city beijing chongqi
1) 1) "116.39999896287918091" 2) "39.90000009167092543"
2) 1) "106.49999767541885376" 2) "29.52999957900659211"
两人之间的距离
127.0.0.1:6379> GEODIST china:city beijing shanghai km # 查看上海到北京的直线距离 "1067.3788" 127.0.0.1:6379> GEODIST china:city beijing chongqi km # 查看重庆到北京的直线距离 "1464.0708"
HyperLogLog
HyperLogLog是Redis的高级数据结构,是统计基数的利器。
UV:统计有多少人访问,一个人多次访问算一次
PV:统计有多少点击量
HyperLogLog提供了三个指令,PFADD(增加计数)和PFCOUNT(获取计数)PFMERGE(将多个PFCOUNT数值累加在一起形成一个新的PFCOUNT)
127.0.0.1:6379> PFadd mykey a b c d e f g h i j # 创建第一组元素 mykey
(integer) 1
127.0.0.1:6379> PFCOUNT mykey # 统计 mykey 元素的基数数量
(integer) 10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m # 创建第二组元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 # 合并两组 mykey mykey2 => mykey3 并集 OK
127.0.0.1:6379> PFCOUNT mykey3 # 看并集的数量!
(integer) 15
位图
应用场景:
- 用户签到
- 用户在线状态
- 统计活跃用户
- 各种状态值
- 自定义布隆过滤器
- 点赞功能
记录打卡:
查看某一天是否有打卡
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
统计操作,统计 打卡的天数
127.0.0.1:6379> bitcount sign # 统计这周的打卡记录,就可以看到是否有全勤!
(integer) 3
位图不是Redis的一种数据结构,它的内容就是普通的字符串。
Redis事务
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。redis 事务不保证原子性,且没有回滚,中间某条命令执行失败,前面已执行的命令不回滚,后续的指令继续执行。
redis的事务本质是一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行过程中会按照顺序来执行。
Redis事务 没有隔离级别的概念,所有的命令在事务中,并没有被直接执行,只有发起执行后命令才会执行
Redis的事务:
- 开启事务:multi
- 命令入队:set key value
- 执行事务:exec
Redis事务命令
- watch:(相当于乐观锁)进行监控,一旦一个键被修改或者被删除,后面的命令都会停。(在当多线程即多个用户对数据修改的时候包保证了安全)
- MULTI:开启一个事务,存放于队列
- EXEC:执行所有事务块内的命令(事务中任意命令执行失败,前面已执行的命令不回滚,后续的命令继续执行。)
批量操作在发送 EXEC(执行) 命令前被放入队列缓存收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,前面已执行的命令不回滚,后续的命令继续执行。
Jedis
是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件
在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettuce。
需要导入对应的依赖
所有的api命令,就是我们对应的上面学习的指令,一个都没有变化!
SpringBoot整合
1、导入依赖
2、配置连接
3、测试
RedisTemplete
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {// 我们为了自己开发方便,一般直接使用 <String, Object> RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();template.setConnectionFactory(factory); // Json序列化配置 Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // String 的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
}
Redis.conf详解
网络
bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式
port 6379 # 端口设置
快照
# 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
save 60 10000
# 我们之后学习持久化,会自己定义这个测试!
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作! rdbcompression yes # 是否压缩 rdb 文件,需要消耗一些cpu资源! rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验! dir ./ # rdb 文件保存的目录!
Redis 缓存处理请求的两种情况
- 缓存命中:Redis 中有相应数据,就直接读取 Redis,性能非常快。
- 缓存缺失:Redis 中没有保存相应数据,就从后端数据库中读取数据,性能就会变慢。而且,一旦发生缓存缺失,为了让后续请求能从缓存中读取到数据,我们需要把缺失的数据写入 Redis,这个过程叫作缓存更新。缓存更新操作会涉及到保证缓存和数据库之间的数据一致性问题。
缓存的类型
按照 Redis 缓存是否接受写请求,我们可以把它分成只读缓存和读写缓存。
只读缓存
只读缓存直接在数据库中更新数据的好处是,所有最新的数据都在数据库中,而数据库是提供数据可靠性保障的,这些数据不会有丢失的风险。
读写缓存
和只读缓存不一样的是,在使用读写缓存时,最新的数据是在 Redis 中,而 Redis 是内存数据库,一旦出现掉电或宕机,内存中的数据就会丢失。
同步直写是指,写请求发给缓存的同时,也会发给后端数据库进行处理,等到缓存和数据库都写完数据,才给客户端返回。
而异步写回策略,则是优先考虑了响应延迟。此时,所有写请求都先在缓存中处理。等到这些增改的数据要被从缓存中淘汰出来时,缓存将它们写回后端数据库。
Redis核心-管道
Redis默认每次执行请求都会创建和断开一次连接池的操作,如果想执行多条命令的时候会在这件事情上消耗过多的时间,因此我们可以使用Redis的管道来一次性发送多条命令并返回多个结果,节约发送命令和创建连接的时间提升效率。
Redis管道的本质:
Redi的过期策略
- 定时删除
- 懒汉删除
- 定期删除
定时删除和定期删除为主动删除:Redis会定期主动淘汰一批已过去的key
惰性删除为被动删除:用到的时候才会去检验key是不是已过期,过期就删除惰性删除为redis服务器内置策略
Redis的持久化
内存快照(RDB)和日志(AOF)
AOF
优点:
1、每一次修改都同步,文件的完整会更加好!
2、每秒同步一次,可能会丢失一秒的数据
3、从不同步,效率最高的!
缺点:
1、相对于数据文件来说,aof远远大于 rdb,修复的速度也比 rdb慢!
2、Aof 运行效率也要比 rdb 慢,所以我们redis默认的配置就是rdb持久化
AOF执行过程
AOF会先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。所以,Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况。
AOF的三种写回策略
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
想要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择 Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。
重写日志
实际上,重写机制具有“多变一”功能。所谓的“多变一”,也就是说,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令。
RDB
优点:
1、适合大规模的数据恢复!
2、对数据的完整性要不高!
缺点:
1、需要一定的时间间隔进程操作!如果redis意外宕机了,这个最后一次修改数据就没有的了!
2、fork进程的时候,会占用一定的内容空间
和 AOF 相比,RDB 记录的是某一时刻的数据,并不是操作,所以,在做数据恢复时,我们可以直接把 RDB 文件读入内存,很快地完成恢复。
Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。
- save:在主线程中执行,会导致阻塞;
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
为了快照而暂停写操作,肯定是不能接受的。所以这个时候,Redis 就会借助操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。
写时复制机制保证快照期间数据可修改:
做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
AOF和RDB的混用
Redis集群的数据同步
Redis的高可靠性:一是数据尽量少丢失,二是服务尽量少中断。AOF 和 RDB 保证了前者,而对于后者,Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。
Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。
- 读操作:主库、从库都可以接收;写操作:首先到主库执行,然后,主库将写操作同步给从库。
- 写操作:首先到主库执行,然后,主库将写操作同步给从库。
主从库间如何进行第一次同步
Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式
- 第一阶段建立连接,进行协商同步,
- 第二阶段主库向同步发送RDB文件将同步数据给从库
- 第三阶段主库发送新的写命令给从库。也可以选择一些从库进行为其他从库进行复制
主从级联模式分担全量复制时的主库压力
我们可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。
主从库间网络断了怎么办?
网络断了之后,主从库会采用增量复制的方式继续同步。
全量复制是同步所有数据,而增量复制只会把主从库网络断连期间主库收到的命令,同步给从库。
当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。
Redis repl_backlog_buffer 的使用
Redis 增量复制流程
不过,有一个地方我要强调一下,因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。
主库挂了,怎么办?
哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。
监控是指哨兵进程在运行时,周期性地给所有的主从库发送 PING 命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就会把它标记为“下线状态”;同样,如果主库也没有在规定时间内响应哨兵的 PING 命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。
这个流程首先是执行哨兵的第二个任务,选主。主库挂了以后,哨兵就需要从很多个从库里,按照一定的规则选择一个从库实例,把它作为新的主库。这一步完成后,现在的集群里就有了新主库。
然后,哨兵会执行最后一个任务:通知。在执行通知任务时,哨兵会把新主库的连接信息发给其他从库,让它们执行 replicaof 命令,和新主库建立连接,并进行数据复制。同时,哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发到新主库上。
哨兵机制,它通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。
新主库的选择过程
首先,哨兵会按照在线状态、网络状态,筛选过滤掉一部分不符合要求的从库,然后,依次按照优先级、复制进度、ID 号大小再对剩余的从库进行打分,只要有得分最高的从库出现,就把它选为新主库。。
哨兵模式
主库挂掉了通过哨兵选出新主库
哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。
- 监控:负责监控 redis master 和 slave 进程是否正常工作
- 选主:如果 master node 挂掉了,会自动转移到 slave node 上(哨兵会先排除一些不适合做主库的从库,然后通过比较进行打分得分最高的推举为新主库)
- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
哨兵实例之间可以相互发现,要归功于 Redis 提供的 pub/sub 机制,也就是发布 / 订阅机制,哨兵只要和主库建立起了连接,就可以在主库上发布消息了,
同时,它也可以从主库上订阅消息,获得其他哨兵发布的连接信息
网络断了之后,主从库会采用增量复制的方式继续同步
哨兵间的通信
哨兵实例之间可以相互发现,要归功于 Redis 提供的 pub/sub 机制,也就是发布 / 订阅机制。
哨兵只要和主库建立起了连接,就可以在主库上发布消息了,比如说发布它自己的连接信息(IP 和端口)。同时,它也可以从主库上订阅消息,获得其他哨兵发布的连接信息。当多个哨兵实例都在主库上做了发布和订阅操作后,它们之间就能知道彼此的 IP 地址和端口。
为了区分不同应用的消息,Redis 会以频道的形式,对这些消息进行分门别类的管理。所谓的频道,实际上就是消息的类别。当消息类别相同时,它们就属于同一个频道。反之,就属于不同的频道。只有订阅了同一个频道的应用,才能通过发布的消息进行信息交换。
Redis发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
subscribe kuangshenshuo(channel) # 订阅一个频道
publish kuangshenshuo "hello" # 发布者发布消息到频道
保证缓存与数据库双写时的数据一致性
- 一种情况是串行化,但是吞吐率会大幅度降低
- 另一种是先更新数据库后删除缓存。通过维护一个删除失败队列(失败后再删除)或者设置一个过期时间
Redis的过期删除策略
- 定时过期:到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源,从而影响缓存的响应时间和吞吐量。
- 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好
- 定期过期:每隔一段时间,对一些key进行检查,删除里面过期的key。
Redis中同时使用了惰性过期和定期过期两种过期策略。通过配合使用这两种过期键的删除策略,
服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。
Redis的内存淘汰策略:
全局的键空间选择性移除:移除最近最少使用的key
设置过期时间的键空间选择性移除:在设置了过期时间的键空间中,移除最近最少使用的key。
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
redis为什么不支持回滚
- Redis 命令只会因为错误的语法而失败
- 错误应该在开发的过程中被发现,而不应该出现在生产环境中。
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
缓存异常:
缓存雪崩是指缓存同一时间大面积的失效:
缓存数据过期时间随机
热点数据不设置过期时间缓存穿透是指缓存和数据库中都没有的数据
从缓存取不到的数据,在数据库中也没有取到,
这时也可以将key-value对写为key-null,缓存有效时间可以设置短点
布隆过滤器缓存击穿
缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是缓存同一时间大面积失效。
热点数据不设置过期时间
SpringBoot使用Redis做缓存
一个系统在于数据库交互的过程中,内存的速度远远快于硬盘速度,当我们重复的获取相同数据时一次又一次的请求数据库,在性能上进行了极大的浪费于是用Redis做缓存是一个不错的选择。
Cache的缓存注解
- @Cacheable:标记在一个方法或者类上,缓存该类所有的方法的返回值。
- @CacheEvict:从缓存中移除数据
- @CachePut:方法支持缓存功能。与@Cacheable区别是不会去检查缓存中是否有这个结果,而是每次都会执行该方法,并将执行的结果以键值对的形式存入指定的缓存中。
io多路复用的三种方法(Linux)
IO复用是Linux中的IO模型之一,IO复用就是进程告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程处理,从而不会在单个IO上阻塞了,Linux中,提供了select、poll、epoll三种接口来实现IO复用
select与poll
epoll
Redis的分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问。Redis中可以使用SETNX命令实现分布式锁
- 可重入锁
- 不可重入锁
分布式锁的缺陷
在集群模式时候并不是绝对安全的
Redis在哨兵模式下主节点挂掉之前某个客户端申请了一把锁。新的主节点并不知道,就把锁分给了另一个客户端。导致同样一把锁被两个客户端同时持有。
为了解决这个问题–红锁
Redis的启动命令
使用配置文件启动Redis的服务器
方法一:独占式启动Redis
方法二:非独占式启动Redis
启动Redis
通过Jedis操作redis
将reids的所有指令转换成对应的方法。
- 连接
- 进行使用操作指令的方法
2.x之后被替换成了lettuce
Mybatis
Mybatis是一种半自动化的Java持久层框架一个orm框架,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
通过注解或xml的方式将对象和SQL联系起来,主要与Spring整合使用
使用过程在pom.xml添加依赖。在xml文件或yml中进行配置端口,密码
JDBC的使用步骤:
- 通过反射加载驱动
- 连接数据库
- 执行SQL语句
- 处理数据
- 关闭连接和其他对象
Mybatis的出现解决了JDBC频繁创建、释放数据库连接对象,造成的资源浪费,影响性能的不好影响减少了代码量。SQL写在XML中,解除SQL与代码的耦合
可以采用连接池的方式进行数据库的连接,Mybatis自动将java对象映射到sql语句中
Mybatis的工作原理:
- 读取Mybatis文件
- 加载映射文件
- 构建会话工厂
- 创建会话对象
#{}和${}的区别:
#{}是占位符,预编译处理,可以防止SQL注入;
#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,
#{}会根据传递进来的值来选择是否加上双引号
${}是普通传值,简单的字符串替换,没有预编译处理,不能防止SQL注入。
Mybatis和Hibernate的区别
Mybatis需要手动编写SQL支持动态SQL,是一种轻量级框架,学习门槛低,适用于大型项目,移植性差
Hibernate 是重量级框架,学习使用门槛高,适用于小型项目
MyBatis的一级(不同的sqlSession中的缓存是互相不能读取的)、
二级缓存(二级缓存是跨SqlSession的作用于表上(mapper上的))
MyBatis默认打开一级缓存
Maven
作用:
依赖管理:就是对jar包的管理,统一由pom.xml文件统一管理依赖,
解决了导入jar包的麻烦,jar包不用每个项目都进行保存
pom的中文是:项目对象模型
maven里面主要包含依赖和插件
中间件以及消息队列
dubbo
是一款高性能、轻量级的开源 RPC 框架,客户端和服务端共用一个接口。客户端面向接口写调用,服务端面向接口写实现。dubbo一般使用 Zookeeper 作为注册中心
dubbo为何而生:
随着流量的增大,常规的垂直应用架构已无法应对。服务间依赖关系变得错踪复杂,负载均衡器的单点压力也越来越大
dubbo的核心组成
- Provider 暴露服务的服务提供方
- Consumer 调用远程服务的服务消费方
- Registry 服务注册与发现的注册中心
- Monitor 统计服务的调用次数和调用时间的监控中心
- Container 服务运行容器
服务器注册与发现的流程:
- 服务容器Container负责启动,加载,运行服务提供者
- 服务提供者Provider在启动时,向注册中心注册自己提供的服务
- 服务消费者Consumer在启动时,向注册中心订阅自己所需的服务
- 注册中心Registry返回服务提供者地址列表给消费者,
- Consumer和provider定时每分钟发送一次统计数据到监控中心Monitor
Spring Cloud和dubbo的区别
Spring Cloud是一个微服务框架,相比Dubbo等RPC框架, Spring Cloud提供的全套的分布式系统解决方案
Spring Cloud 抛弃了 Dubbo 的 RPC 通信,采用的是基于 HTTP 的 REST 方式
Dubbo集群提供了哪些负载均衡策略
随机负载均衡
轮询负载均衡
zookeeper
zookeeper用来充当注册中心和进行负载均衡。哪一个服务由哪一个机器来提供必需让调用者知道,简单来说就是ip地址和服务名称的对应关系
zookeeper通过心跳机制可以检测挂掉的机器并将挂掉机器的ip和服务对应关系从列表中删除
在dubbo中的流程就是provider将自己的服务注册到zk中心上,注册中心将可用的提供者列表给consumer
MQ
消息队列的作用:消息通讯、异步处理、应用解耦、流量削锋
消息队列最常见的问题就是消息的先后顺序和重复问题(根本原因是网络不可达,可以在消费者处进行设置日志保存唯一编号)
RabbitMQ
RabbitMQ 是一个比较成熟的消息中间价,用Erlang编写的,基于AMQP协议的消息中间件,使用信道的方式来传输数据。而且支持集群模式下开发。
RabbitMQ支持消息确认ack。消费者从RabbitMQ收到信息,并处理完成后反馈给RabbitMQ,RabbitMQ收到反馈后将此消息从队列中删除。如果一个消费者处理消息是挂掉,那么就不会有ack反馈,MQ就会认为消息没有正常被消费,会立即将消息传给其他消费者。这种机制保障有故障情况下也能保证
不丢失消息和任务RabbitMQ也支持消息持久化,服务器挂掉可能会丢失任务,通过持久化就能找回任务
Rabbit MQ也支持负载均衡
RabbitMQ通过交换器来进行发布消息
生产者直接将消息给交换器,交换器再细分给各个队列。生产者不直接将消息发送到队列,将消息发送至交换器中,消费由交换器统一进行分配
交换器有三种规则:
- fanout分发:将所有的消息分发给所有绑定的队列
- direct直接:满足绑定的key才给发送
- topic主题:更细化的分发
RabbitMQ运转流程:
生产者发送消息:
- 生产者连接到RabbitMQ的Broker建立连接开启信道(channel),
- 声明一个交换器并设置相关属性(比如交换器类型、是否持久化)
- 生产者将通过路由键将交换器和队列绑定起来
- 生产者将信息发送至RabbitMQ Broker(包含路由键以及交换器等信息)交换器根据收到的路由键查找匹配的队列。将生产者发来消息存入相应队列中
消费者发送消息:
- 消费者连接到RabbitMQ的Broker建立连接开启信道(channel),
- 消费者向RabbitMQ Broker请求消费者对应的队列的信息
- 等待RabbitMQ Broker回应消息
- 消费者确认(ack)接收到消息
- RabbitMq收到确认后删除相应被确认的消息
当然生产者消费者和队列不必部署在同一台机器上
RabbitMQ和Broker建立连接是一个TCP连接,连接后客户端创建一个信道(channel),每个信道都会被指派一个唯一id,很多个channel通过一个Connection和RabbitMQ Broker进行连接
AMQP协议:
生产者将消息发送给交换器,交换器与队列绑定,生产者发送消息时携带的路由键和绑定时的BindingKey匹配时,匹配成功给则消息被存入相应的队列,消费者可以订阅对应的队列来获取消息
AMQP协议包括三层
模型层:定义了一些供客户端调用的命令,客户端通过这些命令实现自己的业务逻辑
会话层:客户端的命令发送给服务器,再将服务端的应答返回给客户端
传输层:传输二进制数据流,提供信道复用等功能
JVM
JVM核心
Java能够摆脱平台的原因就是Java源代码经过编译器变成字节码,字节码经过JVM解释成不同的机器码。而不同的操作系统有不同的JVM。
JVM包含两个组件和两个子系统
- 类加载子系统:将class文件加载到运行时数据取
- 执行引擎:由jit和垃圾回收器组成
- 本地接口:与本地库进行交互
- 运行时数据区:JVM的核心。包含方法区,程序计数器,堆,本地方法栈,虚拟机栈
运行时数据区
程序计数器:当前线程所执行的字节码的行号指示器
Java虚拟机栈:会创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。
本地方法栈:主要区别是Java虚拟机栈执行的是Java方法服务,而本地方法栈执行Native方法服务
Java堆:垃圾回收的主战场,几乎所有的对象实例都是在这里分配的。Java堆又分为老年代(2/3,大对象直接进入老年代)和新生代(1/3)对象优先在 Eden 区分配。
方法区:于存储类信息、常量、静态常量和即时编译后的代码等数据
运行时常量池是方法区的一部分(用于存放编译期生成的各种字面常量和符号引用)
对象创建的过程:
- 虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。
- 类加载通过后,接下来分配内存。分配内存有两种,一种是指针碰撞另一种是空闲列表。
- 因为对象的创建在虚拟机种并发执行的会有危险。可以采用CAS+失败重试或者每个线程在Java堆中预先分配一块内存
除了程序计数器外都有可能发生outofmemory
内存异常有两种情况,
- 一种是内存泄漏:就是申请了内存,但是没有释放,导致内存空间浪费。通俗说法就是有人占着茅坑不拉屎。
- 另一种是内存溢出:是申请内存时,JVM没有足够的内存空间。通俗说法就是去蹲坑发现坑位满了。
方法调用:
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。
JVM垃圾回收
判断对象是否存活:
- 引用计数法:有一个bug就是循环引用
- 可达性分析:从根节点向下延申,对象没有被引用,则判断有无finall关键字,无则直接回收有则进行标记,标记两次后进行回收
垃圾回收算法
标记清除:效率高但是有碎片化空间
标记复制:可有空间变少但是可用内存变小了
标记整理:结合的折中方法,将存活对象放在一端
分代收集:新生代用复制,老年代用清除或者整理。永久代基本不清理
Minor GC新生代、Major GC老年代
由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。它的收集频率较低,耗时较长。
垃圾收集器
- 新生代:Serial、PraNew、Parallel Scavenge(动态调节及及进行高吞吐量,高效利用 CPU)
- 老年代:Serial Old、Parallel Old、CMS(尽可能少的减少停顿时间,有碎片)
- 最强的G1(后台维护一个 优先列表):整体上是标记整理,局部是复制
JVM的类加载
类被编译成字节码文件(具有语言无关性)加载到JVM,并对数据进行验证,准备,解析,初始化后,最终形成可以被虚拟机直接使用的Java类型就是类加载机制。准备,解析,初始化都是在程序运行期间完成的。这种策略虽然会增加一些额外的开销,但是增加了灵活性。
类加载的七个过程:
- 加载:获取此类的字节码、静态存储结构转化为方法区的运行时数据结构、生成对象
- 验证:文件格式验证,字节码验证,符号引用验证
- 准备:为类变量分配内存(被 static 修饰的变量)并设置类变量初始值(数据类型的零值)的阶段。这些变量所使用的内存都将在方法区进行分配
- 解析:将常量池内的符号引用(一组符号来描述所引用的目标,符号可以上任何形式的字面量,只要使用时能无歧义地定位到目标即可。)替换为直接引用(直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄)的过程
- 初始化:执行类中定义的 Java 程序代码。初始阶段是执行类构造器 () 方法的过程。
使用
卸载
类加载器:
- 启动类加载器(c++):加载Java的核心类库
- 扩展类加载器:加载Java的扩展类库
- 应用程序类加载器:Java应用的类一般都是在这加载的
- 用户自定义加载器:用户自己定义的加载器,继承自ClassLoader
双亲委派模式:
当一个类加载器收到一个类加载请求的时候,他首先不会自己先去加载,而是交给父类加载,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个类加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。双亲委派模式主要是保证加载的唯一性,防止类重名而造成的歧义
项目
用户管理
登录模块:
- 前端传来的参数,到数据库中进行查找。
- 首先验证用户名字是否存在。
- 如果用户不存在或者用户的密码不正确,就会向前端返回相应的对应的状态码;如果存在,向前端返回正确相应的状态码。
- 在整个请求过程中我通过id和密码创造了一个token用来保证请求的安全性(访问资源的令牌,目的是保证请求的安全并限定过期时间)。
- 然后token和返回对应的状态码一起返回前端。
登陆成功模块:
登陆成功后进入主页面,
前端将token传给后端,
后端进行解析token再将解析出的数据传给前端。
在所有的执行过程中我用拦截器进行拦截所有的请求,再通过aop思想,
进行写日志和对token的验证(对token进行验证密码,相同则认为请求安全)执行前用拦截器进行拦截所有的请求,通过重写拦截器内部的方法preHandle进行前置增强,对token进行解码后和对应的密码判断是否相等来进行验证token(验证带有UserLoginToken注解的方法)。保证运行的安全性
执行成功后为了监控程序的运行,将项目中所有(想要暴露的)接口展现在页面上。进行写日志时候我通过注解的形式进行织入,我设置了一个注解切点(SystemLog),放在每个功能的前面。
日志用来记录,执行的持续时间(在前置增强的时候,我用了一个threadlocal来记录执行的开始时间),url,访问IP,日志标题以及请求参数等等。
相关参数的获取我是通过反射,获取类中的所有方法与执行的方法进行匹配,如果一致就将该方法的SystemLog的两个参数和其他数据一起存入数据库。
日志会将每个功能过程中每一步的情况都保存到数据库中更适合前后端分开开发。
人脸登陆模块:
- 使用的是百度的人脸识别库。我首先定义了一个人脸识别模块。因为所有的人脸数据都依赖于一个用户组,所以我用单例模式的懒汉模式进行实例人脸识别模块,加载前端传进来的人脸信息(因为前端的人脸传过来是json格式,然后进行解析成字符串模式)和用户组里的人脸进行比对,如果匹配的score>80,就认为是一个人匹配成功后将状态码和和token一并返回给前端
注册模块:
我会先进行访问数据库,判断当前的用户有没有被注册,没有注册我就会将用户信息插入数据库,并且根据option返回主键和用户信息同步到百度人脸识别库上
返回给前端状态码
品牌管理
品牌列表模块(模糊查询):
因为品牌有很多,所以我用了一个pagehelper分页技术,我的想法是把新的东西优先显示,所以在数据库里对品牌进行了一个标记。然后根据标记进行倒排序,通过通配符进行关键字查找。将当前页的数据写入redis进行缓存,并返回给前端
添加品牌模块:
- 将前端插入的数据保存进数据库,并放进redis缓存。
- 其中上传图片是比较难的地方。 我定义了一个上传的controller,用来上传图片先判断上传格式,如果是文件则取出文件域(用来存储图片)进行遍历(迭代器)。生成一个唯一的文件id。用来确定路径的唯一性(在相应文件下图片的唯一性)。在公共api定义一个文件存储的路径(因为上传商品的时候也会用到)。首先进行判断文件的后缀(由后往前遍历的第一个点 “.jpg”)得到文件格式。然后将图片上传到指定的路径。返回给前端图片的大小,名字,路径等信息
删除品牌模块:
根据主键去删除数据库和缓存(删除所有含有这个字段的实体)里的品牌
更新品牌模块:
首先根据id得到品牌信息
商品管理
商品列表模块:
- 我设置了一个字段进行标记是否被删除,删除的时候就将该字段置为假就不进行显示
- 前端可以通过商品名称进行模糊、品牌id、商品分类进行查询,如果缓存里有就在缓存里查询,没有就从从数据库里进行查询返回给前端。
- 后端会根据设置的标记进行判断是否还有该商品,通过criteria进行限制
商品分类模块:
在数据库中,为每一个商品定义一个级别属性,为了降低耦合度,我新建了一个等级的数据库,每一个二级产品都有一个对应的大区的分类,通过级别属性可以进行分类查找。
商品的创建模块:
分为普通用户价格、会员用户价格以及满减价格和库存。
**通过反射(通过传入的对象获取运行时类,再invoke方法)写了一个通用方法,用来计算不同情况下的优惠价格或者库存量
添加商品的时候在添加商品分类时将所有的分类都发送到前端,供用户选择
商品的订单管理
查询订单、
获取订单详情(分页)、
取消单个超时订单(未付款的订单过一定时间后会自动弹出队列,恢复到到数据库中):订单延迟队列来完成。
由生产端向消费者发送,消费者再对mysql和redis进行操作
在进行取消订单时,需要将缓存删掉
首先将订单状态进行修改状态为超时未支付
然后取出该条订单的所有货物信息
你好,offer(最终版)相关推荐
- 剑指Offer第二版Java代码实现
剑指Offer第二版Java代码实现 A.单例模式 面试题 2:实现Singleton模式 B.面试需要的基础知识 面试题 3:数组中重复的数字 面试题 4:二维数组的查找 面试题 5:替换空格 面试 ...
- 各大IT公司待遇—公司更多,数据更加真实(最终版)
2013年:各大IT公司待遇-公司更多,数据更加真实(最终版) 1:本人西电通院2013届毕业硕士,根据今年找工作的情况以及身边同学的汇总,总结各大公司的待遇如下,吐血奉献给各位学弟学妹,公司比较全, ...
- IE9最终版透露IE10信息 或将自动在线升级
IE9才刚刚发布几天,互联网上已开始了对IE下一个版本的猜测. 今天,在IE9最终版里发现的一些隐藏资源显示,微软已有了对IE10的预先计划.俄罗斯网站TheVista.ru披露了一个提及IE10的对 ...
- 赫夫曼树建立c语言源程序编译结果详细解释,哈夫曼树的建立与实现最终版(备份存档)...
<哈夫曼树的建立与实现.doc>由会员分享,可免费在线阅读全文,更多与<哈夫曼树的建立与实现(最终版)>相关文档资源请在帮帮文库(www.woc88.com)数亿文档库存里搜索 ...
- 微型计算机技术及应用选择题,微机(微型计算机技术及应用)选择题及答案(最终版).docx...
微机(微型计算机技术及应用)选择题及答案(最终版) 第1章 微型计算机概述微型计算机的硬件系统包括____A____.A. 控制器.运算器.存储器和输入输出设备 B控制器.主机.键盘和显示器C. 主机 ...
- 华为鸿蒙发布会新手机,曝华为 P50/Pro 系列最终版确定,6 月 2 日揭晓鸿蒙手机发布时间...
IT之家 5 月 28 日消息 此前有消息称,华为 P50 系列手机将延期到 7 月发布.而近期,华为已正式宣布,将于 6 月 2 日 20:00 召开鸿蒙操作系统及华为全场景新品发布会,正式公布可以 ...
- linux 用mutex定义一个linkedlist,【基于LINUX的操作系统实验教程最终版材料】
(基于LINUX的操作系统实验教程)(最终版) <基于LINUX的操作系统实验教程.doc>由会员分享,可免费在线阅读全文,更多与<(基于LINUX的操作系统实验教程)(最终版)&g ...
- 考勤管理系统c语言,C语言课程设计学生考勤系统最终版(范文1)
<C语言课程设计学生考勤系统.doc>由会员分享,可免费在线阅读全文,更多与<C语言课程设计学生考勤系统(最终版)>相关文档资源请在帮帮文库(www.woc88.com)数亿文 ...
- 制作U盘启动的并可保持更改更新和设置的BT4最终版完全手册
你是否为制作的U盘启动的BT4最终版不能保存你安装的程序,设置的硬件,安装的汉化补丁而烦恼不已. 按bt4官方介绍的办法绝对可以达成你要求,制作出一个全新的,可保持更改.更新和设置的BT4.<? ...
最新文章
- c++中介者模式mediator
- P2575 高手过招
- lua菜鸟教程_Lua语言学习
- linux awk浅析(转)
- Java8 新JavaScript脚本引擎Nashorn小试
- java 日期calendar_Java中用Calendar类计算周和周的起始日期(转)
- WPS新建文字分享微信.docx形式_DOC和DOCX文件的区别
- FileZilla Server连接服务器失败
- lua redisson执行lua脚本
- 外网无法访问nginx服务器默认端口问题解决
- 树状知识汇总流程图模板分享
- 音视频开发系列(2)PCM音量控制(高级篇)
- phpword 实现word文件模板字符替换
- google-services简介
- GIS添加图层、查询详细
- magicmatch java_签名图片一键批量生成 使用Java的Webmagic爬虫实现
- 主攻文推荐攻守都有系统_坚守最后一道防线-第五十五章 攻守转换在线阅读-顶点小说...
- RepLKNet实战:使用RepLKNet实现对植物幼苗的分类(非官方)(二)
- idea 根目录获取方法
- matlab disparity函数