Java面试宝典
一、java基础
1、java基础知识
【1】面向对象的特征(了解)
● 封装:把对象的属性和行为结合成一个独立的整体
● 继承:子类继承父类的数据属性和行为,并根据自己需求扩展出新行为,提高代码的复用性
super:
this:
● 多态:一种事物的多种表现形式,一个接口多种实现
● 抽象:对问题领域进行分析、设计得出的抽象概念,是对一系列看上去不同但本质相同的具体概念的抽象。
问题:多态相关,子类接收对象时,会不会执行父类空参构造,原因
解答:会,??????
【2】java的基础数据类型(了解)
数据类型 |
关键字 |
字节数 |
取值范围 |
整数类型 |
byte |
1 |
-128~127 |
short |
2 |
-32768~32767 |
|
int(默认) |
4 |
-2的31次方到2的31次方-1 |
|
long |
8 |
-2的63次方到2的63次方-1 |
|
浮点类型 |
float |
4 |
负数:-3.402823E+38到-1.401298E-45 正数: 1.401298E-45到3.402823E+38 |
double(默认) |
8 |
负数:-1.797693E+308到-4.9000000E-324 正数:4.9000000E-324 到1.797693E+308 |
|
字符类型 |
char |
2 |
0-65535 |
布尔类型 |
boolean |
1 |
true,false |
【3】int 和Integer 区别(了解)
Int |
基本数据类型 |
0 |
不需要 |
直接存储数值 |
Integer |
包装类 |
Null |
变量实例化才可用 |
对对象的引用 |
【4】JDK、JRE、JVM区别 ▲
【5】重载和重写的区别 ▲
● 重载:在同一类中,方法名相同,参数不同
● 重写:父子类中,子类重写父类方法,方法名,参数均相同
【6】= = 和 equals 的区别 ▲
● == 在基本类型中比较值,在引用类型中比较地址值
● Equals 在引用类型中默认比较地址值,呗String、Integer、Date重写比较内容
【7】String、StringBuffer、StringBuilder区别 ▲
● String:字符串常量
● StringBuffer:字符串变量,线程安全,效率低,多线程使用
● StringBuilder:字符串变量,线程不安全,效率高,单线程使用
【8】接口 和 抽象类 的区别 ▲
● 接口:只能有抽象方法,多实现,默认使用public修饰
● 抽象类:有一个抽象方法就叫抽象类,可以有非抽象方法。单继承,可以有构造方法,可以有main方法,可以运行,任意修饰符
【9】什么是单例模式? 有几种 ▲
单例模式:某个类的实例在多线程环境下只能被创建一次
饿汉式:一开始就初始化,线程安全
懒汉式:延迟初始化(什么时候用,什么时候初始化),线程不安全
双检索:延迟初始化,线程安全
【10】反射(了解):通过字节码文件获取这个类的信息,私有只能通过反射获取
获取字节码文件的三种方式:
1> 类名。class
2> 对象名。getClass( ) new 类名的返回值是对象名
3> Class。Forname(“类的全路径”)???
【11】string常用的方法有哪些?(了解)
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
【12】jdk1.8 新特性(高薪)
1、Lambda 表达式 :只能用于函数式接口,接口中只有一个抽象方法
(匿名内部类:可以用于接口、类,对方法个数无要求)
(参数……)-> { 方法体 }
Lambda 允许把函数作为一个方法的参数。
使用场景:结合 forEach 遍历,结合sort排序
2、函数式接口
1> 只有一个抽象方法的接口叫做函数式接口
2> 函数式接口可以被隐式转换为 Lambda 表达式
3> 通常函数式接口上会添加 @FunctionalInterface 注解。
3、 方法引用:允许直接引用已有 Java 类或对象的方法或构造方法。
上例中我们将 System.out::println 方法作为静态方法来引用。
4、从 JDK8 开始,接口允许定义默认非抽象方法和静态方法
5、Stream API
将元素集合看作流,流在管道中传输,在管道节点处理任务,比如筛选,排序,聚合,循环,过滤,映射等。
6、日期/时间类改进
比如日期时间的创建、比较、调整、格式化、时间间隔等。这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
7、Optional 类
Optional 类:可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get()方法会返回该对象。
8、java 8 内置了 Base64 编码的编码器和解码器。
【13】java 异常 ▲
父类:Throwable
子类:error:重要错误
Exception:运行时异常,编译时异常
常见的RunTime异常几种如下:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
【14】BIO、NIO、AIO区别(高薪)
● BIO:同步阻塞式IO,简单方便,并发处理能力低
● NIO:同步非阻塞式IO,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
● AIO:异步非阻塞式IO,基于事件的回调机制
【19】为什么重写equals方法,还要重写hashcode方法?
1.使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
2.保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。
【20】手写冒泡排序 ▲
2、get和post请求方法的区别
● get请求:请求参数会保留在url地址中,
所以请求参数到的长度是有限的,
对外暴露不安全,不能用来传递敏感信息
● post请求:通过request body传递参数
get请求:浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
post请求:浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
2、集合 ★
【1】常见的数据结构
【2】list、set、map区别 ▲
● List:有序、可重复
● Set:无序,唯一
● Map:无序,键不可重复,值可以重复
【3】集合分类
【4】Hashmap底层原理 ▲
● 1.8之前底层是:数组+链表,1.8之后底层是数组+链表+红黑树;
● 链表节点 > 8 转换为红黑树,节点 ≤ 6 退化为链表;
● Hashmap刚被创建的时候底层没有创建数组,是在调用put方法时,创建一个长度为16的Node【】数组;
● 数组容量16 × 加载因子0.75 =12是HashMap容量,超过这个值且没有位置,会进行扩容(扩大一倍)组成新的数组;
● 原来的所有数据会调用HashCode()方法重新计算哈希值分配到新的数组,所以扩容非常的消耗性能。
map.put(k,v)实现原理
(1)先将k,v封装到Node对象当中(节点)。
(2)再调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(3)下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
map.get(k)实现原理
【5】Hashmap、Hashtable、CocurrentHashMap区别 ▲
(从线程去全方向讲,以及为什么安全,为什么不安全)
● Hashmap:数组+链表/红黑树、键、值可为null,线程不安全,高效,不同步,适用单线程
● Hashtable:数组+链表、键、值不可为null,线程安全(在修改数据时锁住整个HashTable),低效,同步,适用多线程、初始size为11
(不用Hashtable理由:1> 内部没优化、冗余 2> CocurrentHashMap替代)
● CocurrentHashMap:底层数组+链表/红黑树,1.8取消分段锁,采用CAS和synchronized来保证线程安全
【6】ArrayList和LinkedList的区别
1、ArrayList:数组数组 LinkedList:双向链表
2、当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
3、当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,数组添加修改对索引下标有影响,造成数据移动
4、ArrayList:手动的设置固定大小,使用方便,
LinkedList,能够动态的随数据量的变化而变化,便于使用。
5、ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
【7】list如何去重
1、把list集合放到hashset里面,再转回list
2、遍历,equals比较,删除重复
3、stream流过滤
【8】
3、多线程 ★
【1】 进程:正在运行中的程序
线程:进程中的一个单元,任务
多线程:进程中多个任务同时运行,提高运行效率
并发:某一时间段
并行:同一时刻能干的事情
【2】创建线程的几种方式?
【3】start 和 run 方法的区别?
● start:开启线程
● run:线程的普通方法,用于主线程书写任务,方法调用
【4】Java中的线程状态
● new(新建)创建未启动 创建线程的时候属于新建状态
● runable(可运行)线程可以在虚拟机中使用 调用start 方法时转换为就绪状态
● blocked(锁阻塞)线程由于某种原因暂停运行,进入阻塞状态(等待、超时等待、同步阻塞),直到线程进入就绪状态
● Waiting(无线等待)必须唤醒
● time_waiting(计时等待)
● treminated(被终止)线程完成或者异常退出,会转换为死亡状态
【5】线程方法
等待:wait 睡眠:sleep 唤醒:notify notifyAll 等待其他线程终止:join
4、线程池??????
【1】线程池的核心参数
核心线程数:corePoolSize
最大线程数:maximumPoolSize
空闲时间:keepAliveTime
时间单位:unit
阻塞队列:workQueue
线程工厂类:threadFactory
拒绝策略:handler
【2】拒绝策略
阻塞队列????????
【2】线程池分类
1、newCachedThreadPool:创建一个可进行缓存重复利用的线程池
2、newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
3、newSingleThreadExecutor:创建一个使用单个 worker 线程的Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程将会排在队列中以此执行
4、newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
5、newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
6、newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的CPU个数
【3】线程池原理
【4】线程池作用:重复利用已有现成,避免创建、销毁线程造成的消耗,提高系统的响应速度
【5】线程池的状态:
【6】项目中哪里用到了多线程?怎么用的?
● 秒杀 下单处理
● 获取支付结果 第三方系统做了限流
● 批量商品入库 (ES索引库)
● 商品的图片 鉴黄鉴曝涉毒
不要说哪三种方式,直接说线程池,线程的创建与销毁也是需要消耗的。
【7】线程安全问题,如何解决?
● 是否存在多线程环境
● 多线程环境下是否存在共享变量(共享变量:@threadlocal)
● 多线程环境下是否存在共享变量(写)操作
解决:1、加锁 2、synchroized(同步代码块、同步方法、显示锁)
推荐使用:共享变量被volatile修饰,加原子性操作的对象类
【8】为什么要用线程池(线程池的优点有哪些)?
● 降低资源消耗。反复创建线程会消耗系统资源,线程池实现线程的重复利用,减少资源消耗。
● 提高响应时间。任务到达时不需要等待线程创建,可以直接执行任务。
● 提高线程的可管理性。使用线程池可以对线程进行统一的分配,调优和监控。
9、线程死锁条件问题,手写死锁案例???
10、JMM内存模型???
11、volatile关键字???
12、CAS原理(ABA)???
13、原子包 ???
5、锁
【1】同步锁、死锁、乐观锁、悲观锁(高薪)
● 同步锁:同一时间内,只允许一个线程访问共享数据
● 死锁:多线程同时被阻塞,一个或全部都在等待某个资源被释放
● 乐观锁:假设最好的情况,每次拿数据都认为别人不会修改,所以不会上锁,但是在更新时会判断有没有人在更新数据,可以使用版本号机制和CAS算法。适用于多读,提高吞吐量。
● 悲观锁:总是假设最坏的情况,每次拿数据都认为有人会修改,所以先上锁,其他线程阻塞,用完后再把资源转给其他线程。比如行锁、表锁、读锁、写锁等都是先上锁。synchronized和ReentranLock等独占锁就是悲观锁思想。
【2】ThreadLoal的原理(高薪)
ThreadLocal通过静态内部类ThreadLocalMap(其类似于Map),以键值对形式为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
ThreadLocal 作为 key用的是弱引用,因为没有强引用链,弱引用在GC的时候可能会被回收。这样就会存在一些key为null的键值对,无法访问,也不会被清除的。使用完ThreadLocal之后,需要手动调用remove方法删除对应key。 如果不删除会造成内存泄漏,导致内存即不回收也无法访问。
在不使用线程池的前提下,即使不调用remove方法,线程的"变量副本"也会被gc回收,即不会造成内存泄漏的情况。
【3】synchronized 底层实现原理?(高薪)
【4】synchronized、volatile、lock的区别什么?(高薪)▲
synchronized |
Lock |
java内置关键字 |
java类 |
无法判断是否获取到锁 |
可以判断是否获取到锁 |
自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁) |
finally中手动释放锁(unlock()方法释放锁), 否则容易造成线程死锁; |
线程1获得锁,线程2线程等待。 线程1阻塞,线程2一直等待 |
Lock如果尝试获取不到锁, 线程可以不用一直等待就结束; |
可重入、不可中断、非公平 |
可重入、可判断、可公平(两者皆可) |
synchronized锁适合代码少量的同步问题 |
Lock锁适合大量同步的代码的同步问题 |
【5】死锁
【6】redisson-分布式锁
(1)分布式锁原理
(集群:不同服务器,相同事情 分布式:不同服务器节点,不同事情)
分布式锁:不同的服务,操作共享数据,把原来的锁提出来,让所有的服务共享这把锁,调用不存在就等待。
Lua脚本:通过封装在lua脚本中发送给redis,由于redis是单线程,可以保证原子性特征
(2)分布式锁流程
(3)WatchDog自动延期机制
情况1:使用锁的线程宕机,默认30秒自动释放锁,防止死锁发生。(可设置有效时间)。
情况2:线程A有效期内业务没执行完,启动看门狗后台线程,不断延长锁key的生存时间。
【7】项目中用的什么锁,讲一讲??????、、
6、Java并发编程的三大特性??????
● 原子性:一个线程在CPU执行过程中不会被其他线程中断
(如何保证:加锁:synchronized同步加锁,lock加锁)
● 内存可见性:
● 有序性:
二、数据库
1、事务
【1】事务特性
● 原子性:事务要不全被执行,要捕全部被执行
● 一致性:事务执行使数据库从个一种正确状态转变成另一种正确状态
● 隔离性:事务在正确提交之前,不允许把数据的改变交给其他事务
● 持久性:事务提交后,结果会永远保存在数据库中,即使后面出现其他故障,事务结果也会保存
【2】mysql事务的隔离级别,会产生什么影响?举例子(四个隔离级别讲述详细)
● 读未提交:产生脏读
● 读已提交:避免藏独,但是会出现不可重复读和虚读问题
● 可重复度:MySQL默认隔离级别,会产生幻读
● 可串行化:事务的最高级别,强制事务排序,读取事务的时候加上共享锁,解决幻读,可能导致大量超时和锁竞争,为了提升吞吐量不会考虑。
【3】事务原理
项目哪里用到了事务?修改
【4】事务为什么会失效?事务失效了怎么办?
2、索引
【1】索引概念以及优缺点
概念:索引存储在内存中,帮助服务器快速查询的一种数据结构。
优点:
● 提高查询速度,降低IO操作
● 对数据进行排序,降低CPU消耗
缺点:
● 增删改会对索引底层树形结构重新排序,速度慢
● 索引也是一张表,会占用硬盘空间
(架构、部署、多少并发)
【2】索引结构
BTree(多录平衡搜索树)
● 解决二叉树层数过高的问题
● n叉树有n-1个key
B+Tree(n树n key,排序,非叶子节点是key索引)
● n叉树有n个key
● 上面只存索引,只在叶子节点存储key信息,并按照key的大小排序
● 所有非叶子节点都是key的索引(查询所有数据都要从根节点走到叶子节点,所以更稳定)
MySQL的B+Tree(添加链表指针,适合区间查询)
● 添加相邻叶子节点的链表指针,形成带有顺序指针的b+tree,提高区间访问性能
(例:使用b+tree先查树左边数据,然后查树右边数据,需要返回根节点再查询,有了链表直接可以直接和右边节点的首位相比,调阅查询直到找到数据)
【3】索引分类
● 单值索引(单列索引):一个索引只包含单个列,一个表可以有多个单列索引
● 唯一索引:字段唯一,可以有null
● 主键索引:特殊的唯一索引,字段唯一,不可以有null
● 复合索引(联合索引):多个字段上建立的索引,能够加速复合查询条件的检索
● 聚簇索引:每张表的主键后塍一个B+Tree,叶子结点称为数据页,存放整张表的行记录数。这一特性决定了索引组织表中的数据也是缩印的一部分,每张表只能用友一个聚簇索引
● 非聚簇索引
【4】索引设计原则
(1)查询频次高,数据量比较大。
(2)最佳候选列应从where条件中提取(最常用、过滤效果最好的列)
(3)使用唯一索引,区分度越高,使用索引的效率越高。
(4)使用短索引,占用内存空间
(5)组合索引,最左匹配原则,那么相当于是创建了N个索引
【5】哪些字段不适合加索引
● 数据量多,但种类少:性别、城市
● 经常变化,更新
【6】是不是索引越多越好?
不是
● 索引虽然查询快,但是创建慢
● 一个索引相当于一张表,占用磁盘内存
● 对于增删改比较多的表,维护索引的代价大
● MySQL也会犯选择困难病
【7】为什么索引失效?索引失效怎么办?如何避免索引失效现象?
● 最左匹配法则
● 范围查询右边列( where name = "小米科技" and status >= "1" and address = "北京市";)
address索引失效, 因为status是大于号是范围查询.
● 索引上使用运算
● 字符串不加引号
● 以%开头的like 模糊查询
● in走索引,not in 不走索引
● or分割条件,or前有索引, or后无索引(一定用or, 条件列都加索引)
【8】索引语法
● 创建索引
(1)创表时,创建由c2和c3两列组成的索引。
(2)为列添加索引,指定索引名、表名、列名
Create index 索引名 on 表名(列名);
● 查看索引
Show index from 表名;
● 删除索引
DROP INDEX 索引名 ON 表名 ;
● ALTER命令
主键索引:alter table tb_name add primary key(column_list);
唯一索引:alter table tb_name add unique index_name(column_list);
3、SQL
【1】SQL优化的步骤
1、先定位慢查询的sql(慢查询日志、spl、spf)
2、根据explain分析sql慢的原因(explain type key extra....)
* type 级别提高(级别记住):system > const > eq_ref > ref > range > index > ALL
3、优化sql(加索引、改sql)
4、验证是否优化生效
【2】SQL优化
1、查询结果只有一条是可以使用limit
2、limit后面基数过大时,可以使用between…and
3、避免使用select *
4、为搜索字段添加索引
【3】SQL注入??????
【4】Mysql主从复制数据不一致问题??????
4、范式:设计表时需要遵守的规则1
第一范式:表的每一类不可分割
第二范式:一张表描述一件事情,每张表都依赖主键,表与表主外键关联(消除部分依赖)
第三范式:(消除传递依赖)非主属性数据不依赖其他非主属性数据,只依赖主键
反三范式:为了提高性能,增加冗余
5、select语句执行顺序
from→on→join→where→group by→having→select→distimct→order by→limit
6、笛卡尔积现象
原理:两张表数据交叉组合(乘法原理)
解决:使用内连接
7、联级操作:修改或删除主表主键时,从表外键被修改或删除
8、怎么连接数据库
使用mybatis,datesource、diver、user、password
9、如何保证数据的原子性????????
10、如何查询前1000条数据
● select top 1000 from……
● limit 1000
● between 1 and 1000
11、数据库持久层:mybatis(持久层:把数据写到磁盘中)
12、数据库的分库分表?
①当数据比较大的时候,对数据进行分表操作,首先要确定需要将数据平均分配到多少张表中,也就是:表容量。
用户中心数据库切分方法|范围法
以用户中心的业务uid为划分依据,将数据水平切分到两个数据库实例上去,user_db1存储0到1千万的uid数据,user_db2存储1到2千万的数据切分策略简单,扩容简单,数据量和请求量不均匀
用户中心数据库切分方法|哈希法
以用户中心的业务uid为划分依据,将数据水平切分到两个数据库实例上去, user_db1存储uid%2=0的数据,user_db2存储uid%2=1的数据
切分策略简单,数据量和请求量均匀,扩容麻烦,增加一个库需要重新hash.
垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。
13、分库分表带来的问题?
①事务一致性问题: 当更新内容同时分布在不同库中,不可避免会带来跨库事务问题,解决方案: 分布式事务能最大限度保证了数据库操作的原子性。事务补偿的方式,可以基于日志进行对比,定期同标准数据来源进行同步
②跨节点关联查询 join 问题: 全局表, 可以将这类表在每个数据库中都保存一份; 数据组装,分几次查询,再根据id将得到的数据进行拼接; ER分片, 将那些存在关联关系的表记录存放在同一个分片上
全局主键避重问题: UUID是主键是最简单的方案,本地生成,性能高
支持分库分表中间件:Vitess(谷歌),mycat(基于cobar)
三、框架
1、 Mybatis框架
【1】Mybatis
MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,
支持定制化 SQL、存储过程以及高级映射。
使用简单的 XML 或注解来配置和映射原生类型、接口、 POJO类到数据库。
【2】在mybatis中,${} 和 #{} 的区别
● ${} :字符串替换,替换成变量值
● #{} :占位符,预编译处理,替换为?号,防止SQL注入,提高系统安全性。
(SQL注入:传入的参数作为SQL语句的一部分)
【3】resultType和ResultMap的区别
● 数据库列名与实体类属性名相同,使用 resultType 进行自动映射
● 数据库列名与实体类属性名不同,使用 resultMap 进行手动映射
【4】动态SQL的标签
1、
2、
3、
4、
5、
【5】mybatis缓存机制
一级缓存:SqlSession,默认开启,无法关闭
二级缓存:Mapper(Namespace),手动配置cacheEnabled=true开启,实现Serializable序列化接口,查询顺序变为:二级缓存→一级缓存→数据库。
【6】mybatis的编程步骤
【7】原始JDBC有哪些问题?mybatis如何解决?
【8】mybatis的mapper接口调用有什么要求?
【9】mybatis中foreash标签怎么是使用?
2、 Spring框架
【1】spring两大核心
● IOC:控制反转,把创建对象的控制权交给spring容器,spring在配置文件中配置了类的字节码文件,通过反射创建对象,说白了不用new对象了,在spring容器中直接获取。
● AOP:面向切面编程,在程序运行期间,不改变源码的基础上对方法进行增强。SpringAOP使用动态代理,生成临时的AOP对象,包含木变对象的所有属性和方法,并在特定的切点做方法增强。
● DI注入:给类的成员变量赋值
【2】SpringIOC的三种注入方式 :
● 构造器注入 ● setter方法注入 ● 根据注解注入。
【3】Spring动态代理的两种方式
核心接口InvocationHandler 通过 invoke( )方法,反射调用目标类中的代码,动态地将横切逻辑和业务编织在一起,Proxy 类再通过接口动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
(2) CGLIB:是一个代码生成的类库,通过继承的方式,在运行时动态的生成指定类的子对象,覆盖其中特定方法并添加增强代码。在代理类没有实现 InvocationHandler 接口时使用。如果该类被标记为 final无法使用。
【2】spring作用域(5种)
(1)singleton:默认单例作用域,bean,整个容器共享一个实例。
(2)prototype:每一个bean请求创建一个实例。
(3)request:每一个request请求创建一个实例,完成后调用垃圾回收器回收。
(4)session:同一个session会话共享一个实例。
(5)global-session:全局作用域。
【3】Spring的通知类型 动态代理:通过反射对呗代理类的方法增强
前置通知 [before]:切点前运行
后置通知 [after-returning]:切点后运行
异常通知 [after-throwing]
最终通知 [after]
环绕通知 :自定义通知位置,解决其他通知时序问题
【4】Spring框架中的设计模式?
1. 工厂模式:BeanFactory用来创建实例对象
2. 单例模式:Bean默认为单例模式
3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
4. 模板方法:用来解决代码重复的问题。
5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新。
【5】Spring的对象默认是单例的还是多例的? 单例bean存不存在线程安全问题呢?
(1)spring默认单例对象,也可配置多例。
(2)单例bean存在安全问题原因:共享变量,操作桶一个变量肯定有线程安全问题
(多线程操作共享变量,其他线程无法访问该bean对象,造成数据混乱。)
(3)解决办法:bean对象中避免共享变量;或定义ThreadLocal将共享变量保存在ThreadLocal中。
【6】spring事务实现原理:对数据库事务的支持数据库的事务提交、回滚通过binlog和redolog实现
spring事务实现方式:
声明式事务:@Transactional或AOP
编程式事务:beginTransaction(),commit(),rollback()等事务管理相关方法
【7】Spring的事务传播行为(多个事务同时存在,spring如何处理)
Propagation(传播)_require必须 有事务加入,无事务创建
propagation_suppor支持 有事务加入,五十五以非事务状态运行
propagation_mandatory 强制托管
propagation_requires-new 需要新建
propagation_not -supported不支持
propagation_never从不
propagation_nested嵌套的
【8】Spring中的事务隔离:脏读、幻读、不可重复读
Spring中的隔离级别:(ISOLATION:隔离)
① ISOLATION_DEFAULT:默认隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务看到未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,解决脏读问题
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。行锁
⑤ ISOLATION_SERIALIZABLE:事务执行过程中看不到其他事务对数据的修改。表锁
spring事务失效的几种情况
【9】Spring的常用注解(必会)
(1)@Component(任何层) @Controller @Service @Repository(dao)
(2)@Scope : 设置Spring对象的作用域
(3)@PostConstruct :创建对象后
@PreDestroy : 销毁前
(4)@Value: 简单属性的依赖注入
@Autowired: (属性、方法)类型注入,没有搭配 @Qualifier 按名称注入
@Qualifier
@Resource:按照属性名称注入
(5)@ComponentScan: 组件扫描
(6)@Bean: 方法返回值对象放入容器
(7)@Configuration:配置类
@Import: 配置类中导入其它配置类
@PropertySource: 引入其它的配置文件
(8)@Transactional (类、方法)方法具有事务管理功能
【10】SpringBean的配置方式
● 基于XML的配置
● 基于注解的配置(@Component、@Repository、@Controller @Service)
● 基于Java类+配置注解的方式(@Configuration @Bean)
Spring框架优点:
● IOC解耦,简化开发
● AOP面向切面编程
● 声明式事务管理
● 整合第三方优秀框架
3、SpringMVC框架
SpringMVC:(model模型:封装数据、view试图:展示数据、controller:与浏览器交互,处理业务)
【1】SpringMVC的常用注解(必会)
(1)@RequestMapping:处理请求 url 映射
(2)@RequestBody:接收http请求的json数据 转换 为java对象
@ResponseBody:方法返回对象 转化 为json对象
(3)@PathVariable("要获取的参数名"): 从url路径上获取指定参数
(4)@RequestParam: 限制传入参数(参前)
(value:指定传入参数名称 required:指定此参数是否必传 defaultValue:非必传参数指定默认值)
(5)@ControllerAdvice:全局异常处理的类(类)
(6)@ExceptionHandler(Exception.class) :方法可以处理的异常类型(方)
【2】SpringMVC的执行流程
Propagation
4、springboot
【1】Springboot优点
Springboot是对spring的优化,可以快速构建spring项目,基于约定大于配置的思想,减少一切xml配置,做到开箱即用,提高开发效率,缩短项目周期。
● 版本锁定:使用starter启动器管理依赖并对版本几种控制,解决maven依赖版本冲突
● 起步依赖:整合常用jar包
● 自动配置:集合所有约定的默认配置
● 内置Tomcat
【2】springboot常用注解
● @SpringBootApplication:
封装(@SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan)
● @MapperScan:指定mybatis的接口类路径
● @Service:标记业务层组件
● @RestController:( @Controller + @ResponseBody)
● @PutMapping、@DeleteMapping、@PostMapping、@GetMapping
● @PathVariable:路径变量注解,
● @ControllerAdvice和@ExceptionHander配合完成统一异常拦截处理
【3】springboot原理
springboot约定大于配置,简化开发,而且提供了启动类
1.maven父子依赖
2.自动配置
3.内置tomcat
SpringBoot自动化配置流程总结:
● 程序启动找到自动化配置包下 META-INF/spring.factories 的EnableAutoConfiguration
● SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
● 每个自动配置类按照**条件**进行生效,默认都会绑定配置文件指定的值。**xxxxProperties**里面拿。xxxProperties和配置文件进行了绑定
● 生效的配置类就会给容器中装配很多组件
● 只要容器中有这些组件,相当于这些功能就有了
● 定制化配置
● 用户直接自己@Bean替换底层的组件
● 用户去看这个组件是获取的配置文件什么值就去修改。
开发人员使用步骤总结:
● 引入场景依赖
● 查看自动配置了哪些(选做)
● 自己分析,引入场景对应的自动配置一般都生效了
● 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
● 自己分析是否需要修改
● 参照文档修改配置项,xxxxProperties绑定了配置文件的哪些。
● 自定义加入或者替换组件,@Bean、@Component等
5、springcloud
【1】springcloud的组件
(1)Nacos注册中心
(2)Ribbon负载均衡
(3)Hystrix熔断器
有时候可能是网络问题, 一些其它问题, 导致代码无法正常运行, 这是服务就挂了, 崩溃了. 熔断器就是为了解决无法正常访问服务的时, 提供的一种解决方案.解决因为一个服务崩溃而引起的一系列问题, 使问题只局限于这个服务中,不会影响其他服务.
Hystrix提供了两种功能, 一种是服务降级, 一种是服务熔断.
● Hystrix为每个服务分配了小的线程池, 当用户发请求过来, 会通过线程池创建线程来执行任务, 当创建的线程池已满或者请求超时(这里和多线程线程池不一样,不存在任务队列), 则启动服务降级功能.
● 降级指的请求故障时, 不会阻塞, 会返回一个友好提示(可以自定义, 例如网站维护中请稍后重试), 也就是说不会影响其他服务的运行.
状态机有3个状态:
● Closed:关闭状态(断路器关闭),所有请求都正常访问。
● Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,请求次数最少不低于20次。
● Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。
(4)GateWay网关
(5)Feign远程调用
(6)Spring Cloud Config: 配置中心
【2】SpringBoot和SpringCloud的关系(必会)
● SpringBoot是为了解决Spring配置文件冗余问题, 简化开发的框架.
● SpringCloud是为了解决微服务之间的协调和配置问题, 还有服务之间的通信, 熔断, 负载均衡远程调度任务框架.
● SpringCloud需要依赖SpringBoot搭建微服务, SpringBoot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,SpringCloud很大的一部分是基于SpringBoot来实现。
● SpringBoot不需要依赖SpringCloud就可以独立开发. SpringBoot也可以集成Dubbo进行开发.
【3】SpringCloud和Dubbo的区别(高薪常问)
(1)SpringCloud和Dubbo都是主流的微服务架构.
n● SpringCloud是Apache下的Spring体系下的微服务解决方案.
n● Dubbo是阿里系统中分布式微服务治理框架.
(2)技术方面对比
n● SpringCloud功能远远超过Dubbo, Dubbo只实现了服务治理(注册和发现). 但是SpringCloud提供了很多功能, 有21个子项目
n● Dubbo可以使用Zookeeper作为注册中心, 实现服务的注册和发现, SpringCloud不仅可以使用Eureka作为注册中心, 也可以使用Zookeeper作为注册中心.
n● Dubbo没有实现网关功能, 只能通过第三方技术去整合. 但是SpringCloud有zuul路由网关, 对请求进行负载均衡和分发. 提供熔断器, 而且和git能完美集成.
(3)性能方面对比
n● 由于Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC。
n● 而SpringCloud是基于Http协议+Rest接口调用远程过程的,相对来说,Http请求会有更大的报文,占的带宽也会更多。
n● 使用Dubbo时, 需要给每个实体类实现序列化接口, 将实体类转化为二进制进行RPC通信调用.而使用SpringCloud时, 实体类就不需要进行序列化.
四、Redis缓存
1、Redis、Redis 的存储结构
【1】概述:Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能非关系型(NoSQL)的(key-value)键值对数据库。可以用作数据库、缓存、消息中间件等。
【2】存储结构
String,字符串,是 redis 的最基本的类型,一个 key 对应一个 value。是二进制安全的,最大能存储 512MB。
Hash,散列,是一个键值(key=>value)对集合。string 类型的 field 和value 的映射表,特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40 多亿)
List,列表,是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列边或者尾部(右边)。最多可存储 232 - 1 元素(4294967295, 每个列表可存储 40 亿)
Set,集合, 是 string 类型的无序集合,最大的成员数为 232 -1(4294967295, 每个集合可存储 40 多亿个成员)。
Sorted set,有序集合,和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。
2、Redis事务机制
【1】redis支持伪事务,基于队列实现
【2】 开启事务(multi): 把命令放到队列中,不执行
提交事务(exec): 执行队列中的命令
取消事务(discard):释放队列,不执行
【3】事务处理,一旦出错,丢弃命令:
1)编译:全丢,所有命令都不执行
2)运行:谁错丢谁
3、redis的持久化
Redis 提供了两种持久化的方式,分别是 RDB(Redis DataBase)和 AOF(Append Only File)。
RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上。
AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。
3、持久化机制▲
【1】持久化机制
● Redis将数据保存在内存中,持久化就是将内存中的数据保存到磁盘中。内存快于硬盘。
● 目的:避免服务器宕机,数据丢失
● Redis提供了两种持久化机制:RDB、AOF。
判断:一断电,redis数据就丢失了 答:错,如果没有持久化机制,那么一断电,redis数据就丢失了
【2】RDB快照:(默认持久化机制)
(1)条件触发,将内存数据快照保存到硬盘,文件后缀名:.rdb
redis.conf文件中配置:6.0版本
save 3600 1 #表示1小时内至少1个键被更改则进行快照。
save 300 100 #表示5分钟(300秒)内至少100个键被更改则进行快照。
save 60 10000 #表示1分钟内至少10000个键被更改则进行快照。
注:最多丢失一分钟以内数据。 6.0之前:save 900 1。 正常关闭会进行一次持久化
(2)手动触发快照命令:
save:(同步执行)阻塞进程,直到拍照完成
bgsave:(默认)(异步子线程执行)fork一个相同的子线程来负责,操作时仍可处理命令
(3)举例:条件触发:延迟拍照
save手动触发:站着不动拍
bgsave手动触发:抓拍(你做你的,我拍我的)
(4)优点:(效率高,重性能)
二进制文件,占用空间少,文件传输快。
自定义规则,可控制。
缺点:(安全性低)
默认配置中最多会丢失一分钟内的数据。
异步执行时创建子线程也会占用时间(此时线程堵塞)
【3】AOF
(1)AOF解决RDB方式会出现数据丢失的问题,需要手动开启,把每次增删改操作redis的命令先保存到缓存区(aof_buf),然后写入到aof文件中,最后同步到硬盘。
(2)AOF触发方式:(折中,所以默认everysec):
● always:每次执行增删改命令时都将缓存区内容全部存进AOF文件,并直接同步到硬盘(最安全,最慢)
● everysec:全部存进,并每隔一秒同步到硬盘。
● no:全部存进,但不同步,由操作系统做同步处理(30秒一次,最快,最不安全)
(4)AOF重写:日积月累,AOF文件过大,效率降低,当AOF达到一定大小时,触发AOF文件重写,redis开启子线程创建一个新的AOF文件替代(一种压缩)
(6)触发配置:
//当前aof文件大小超过上一次aof文件大小的百分之多少时进行重写。如果之前没有重写过,以启动时aof文件大小为准
auto-aof-rewrite-percentage 100
//限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化
auto-aof-rewrite-min-size 64mb
【4】RDB与AOF对比
● RDB默认开启,AOF需手动开启。
● RDB性能高,AOF安全。
● AOF优先级高于RDB。
● RDB存储某个时刻的数据快照,AOF存储写命令。
● RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF默认使用everysec,每秒保存一次,最多丢失两秒以内的数据。
4、Redis 的优点?
【1】因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。
【2】单线程操作,避免了频繁的上下文切换。
【3】采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟踪每个 I/O 流的状态,来管理多个 I/O 流。
4、为什么要用 Redis
高性能:数据存在缓存中,操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
5、Redis集群
【1】高可用-主从复制(如何保证百分百数据不丢失:AOF+主从复制,不能达到百分百)
(1)高可用:行话---几个9(99.999%,一年中有多长时间不可用:5分钟)
(2)原理:
搭建集群,多个从服务器(Slave)复制主服务器(Master)内容实现同步,一般主服务器只做增删改,从服务器只做查询(读写分离,主写从读),解决了高并发(负载均衡)和数据尽量不丢失。
(3)疑问
问题:如果master和所有的slave都开启持久化的话,性能相对来说比较低。
解决:从节点上开启持久化、在主节点关闭持久化
问题:是否会出现宕机,数据丢失?
解答:主节点将数据同步到从节点,主节点宕机,会提升从节点为主节点,原主节点恢复降为从节点完成数据同步。
(4)全量复制:全部复制
增量复制:有旧内容,只更新新内容
(5)主从复制的作用
● 读写分离:主写从读,提高服务器的读写负载能力
● 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
● 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
● 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
● 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
主从复制
主从复制原理
从服务器连接主服务器,发送 SYNC 命令。主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成RDB 文件并使用缓冲区记录此后执行的所有写命令。主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令。从服务器收到快照文件后丢弃所有旧数据,载入收到的快照。主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令。
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令(从服务器初始化完成)。主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)。
优点
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,写服务仍然必须由 Master 来完成Slave同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力Master Server 是以非阻塞的方式为 Slaves 提供服务。所以在Master-Slave 同步期间,客户端仍然可以提交查询或修改请求。Slave Server 同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis 则返回同步之前的数据。
缺点
Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP才能恢复。主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP后还会引入数据不一致的问题,降低了系统的可用性。Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
【2】哨兵机制
● 也是Redis服务器,不读不写,只监控masker节点
● 奇数个哨兵(最少三台),一个哨兵发现宕机,投标选举,是否更改主节点,如果未选出,则集群会等待一段时间(哨兵故障转移超时时间的 2 倍),再重新选举
● 哨兵三大工作任务
○ 监控:1)主机死机没 2)收集从节点状态
○ 提醒:哨兵之间相互通信
○ 自动故障迁移:
1)将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器
2)当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器
● 一主二从三哨兵
● 启动顺序:先启动主再启动从,最后启动3个哨兵
● 哨兵端口:【26379】
哨兵模式
当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。为此,Redis2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控 Redis 系统的运行状况,它的功能包括以下两个。
1、监控主服务器和从服务器是否正常运行。
2、主服务器出现故障时自动将从服务器转换为主服务器。
哨兵的工作方式
每个 Sentinel (哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器,Slave 从务器以及其他 Sentinel(哨兵)进程发送一个 PING 命令。如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被Sentinel(哨兵)进程标记为主观下线(SDOWN)。如果一个 Master 主服务器被标记为主观下线(SDOWN),则正在监视这个 Master 主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了主观下线状态(SDOWN),则 Master 主服务器会被标记为客观下线(ODOWN)。
在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一169 / 196次的频率向集群中的所有Master主服务器,Slave从服务器发送 INFO命令。当 Master 主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。若没有足够数量的 Sentinel(哨兵)进程同意 Master 主服务器下线, Master 主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel (哨兵)进程发送 PING 命令返回有效回复,Master 主服务器的主观下线状态就会被移除。
优点
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。主从可以自动切换,系统更健壮,可用性更高。
缺点
Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
【3】高可扩-Redis Cluster分片集群
1、无中心化,每个服务器都是入口,一个分片集群共有16384个哈希槽,只分配给主服务器,根据哈希槽查询或写数据,至少6个redis服务器,三主三从,实现高可用,高可扩,大量的数据存储。
2、如果槽没有分配,全部不可用
3、先还槽,再删除
4、CRC16算法的目的:???????
5、分片解决问题:海量数据存储(并发)、高可用、高可扩
6、CPU、内存、固态硬盘可以组成电脑,运行内存64、32(分3g)
Redis-Cluster 集群
redis 的哨兵模式基本已经可以实现高可用,读写分离,但是在这种模式下每台 redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0 上加入了 cluster 模式,实现的 redis 的分布式存储,也就是说每台 redis 节点上存储不同的内容。Redis-Cluster 采用无中心结构,它的特点如下:
所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。节点的fail是通过集群中超过半数的节点检测失效时才生效。客户端与 redis 节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
工作方式
在 redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的取值范围是:0-16383。还有一个就是 cluster,可以理解为是一个集群管理的插件。当我们的存取的 key 到达的时候,redis会根据 crc16 的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。为了保证高可用,redis-cluster 集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与A 通信超时,那么认为主节点 A 宕机了。如果主节点 A 和它的从节点A1 都宕机了,那么该集群就无法再提供服务了。
6、key的过期删除策略(key过期什么时候被删除)
【1】定时删除:设置键过期时间的同时,创建一个定时器,键过期立即删除。这个策略能够保证过期键的尽快删除,快速释放内存空间。
【2】惰性删除:获取key时检查是否过期,如果过期就删除。对CPU友好,但浪费大量内存空间。
【3】定期删除:每隔一段时间进行一次删除。默认每秒运行10次,获取20个key,并删除其中已经过期的key。如果其中过期key的比例超过25%,则继续扫描删除。
7、内存淘汰策略
【1】问题:由于不能保证所有的过期key都被删除,同时Redis还在不断插入新数据,最终会造成内存空间不足的问题。
【2】解决:使用内存淘汰策略,在添加数据时,Redis会先检查内存空间大小,如超过最大内存,则触发内存淘汰策略。Redis中默认提供了三类八种淘汰策略。通过修改redis.conf中maxmemory-policy属性值设置不同的内存淘汰策略,默认使用noeviction。
【3】三类八种内存淘汰策略
lru:最近最久未使用的key
lfu:最近使用频率最少的key
ttl:即将过期的key
random:随机删除
noevction:不删除,内存空间不足,直接返回错误信息
8、Redis 的缺点(缓存灾难问题解决)
【1】缓存和数据库双写一致性问题
一致性的问题很常见,因为加入了缓存之后,请求是先从 redis中查询,如果 redis 中存在数据就不会走数据库了,如果不能保证缓存跟数据库的一致性就会导致请求获取到的数据不是最新的数据。
解决方案:
1、编写删除缓存的接口,在更新数据库的同时,调用删除缓存
的接口删除缓存中的数据。这么做会有耦合高以及调用接口失败的情况。
2、消息队列:ActiveMQ,消息通知。
【2】缓存的并发竞争问题
并发竞争,指的是同时有多个子系统去 set 同一个 key 值。
解决方案:
1、最简单的方式就是准备一个分布式锁,大家去抢锁,抢到
锁就做 set 操作即可
【3】缓存雪崩问题
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
1、给缓存的失效时间,加上一个随机值,避免集体失效。
2、使用互斥锁,但是该方案吞吐量明显下降了。
3、搭建 redis 集群。
(4)缓存雪崩
问题:缓存层承载大量请求,有效保护存储层,当缓存层不能提供服务时,所有请求会达到存储层,存储层由于调用量暴增导致挂掉的情况。大量的key到期
解决方案:
● 缓存高可用
● 限流
● 数据预热
● 队列术限流
● 加锁
● 多级缓存(推荐)
Redis雪崩怎么处理:热点key永久化,设置不同的过期时间,错峰延期,……
【4】缓存击穿问题
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
1、利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
2、采用异步更新策略,无论 key 是否取到值,都直接返回,value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。
(3)缓存击穿
1>问题:缓存过期的同时有大量并发请求,发现缓存过期,从后端DB加载数据并返回到缓存,把后端压垮。
2>解决方案:
● 定时器
● 多级缓存
● 分布式锁
● 队列术
(1)缓存穿透
问题:恶意查询导致redis缓存无数据,每次查询都从数据库查,这种现象叫缓存穿透。
解决:如果为null也把数据缓存到redis中。布隆过滤器也是缓存穿透方案之一。
(2)布隆过滤器:
1>作用:能够迅速判断一个元素是否在一个集合中。
2>三个使用场景:
1、网页爬虫对URL的去重:避免爬取相同url地址
2、反垃圾邮件:判断某邮箱是否垃圾邮箱(同理,垃圾短信)
3、缓存穿透:当黑客访问不存在的缓存时迅速返回,避免缓存及DB挂掉。
3>布隆过滤器的计算原理:
(布隆过滤器有一个误判率的概念,误判率越低,则数组越长,所占空间越大。误判率越高则数组越小,所占的空间越小。)内部维护全为0的bit数组,输入集合,集合中的数据通过计算得到数值,把数值对应的数组下标的值设为1。把要判断的数据通过计算得到对比值,判断对比值所对应的数组下标是否为1,是则在集合中,不是则认为不在集合中。
4> 优点:思路简单、保证一致性、性能强
缺点:代码复杂度增大、需要另外维护一个集合来存放缓存的Key 、布隆过滤器不支持删值操作
1.8 Redis的分布式锁
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
1>安全特性:互斥访问,即永远只有一个 client 能拿到锁
2>避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
3>容错性:只要大部分 Redis 节点存活就可以正常提供服务
Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值:设置成功,返回 1 。设置失败,返回 0 。
使用SETNX完成同步锁的流程及事项如下:
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
释放锁,使用DEL命令将锁数据删除
五、Redisson分布式锁
【1】为什么使用分布式锁?
分布式环境下,普通的锁不能锁住各个微服务对为暴露
(集群:不同服务器,相同事情 分布式:不同服务器节点,不同事情)
分布式锁:不同的服务,操作共享数据,把原来的锁提出来,让所有的服务共享这把锁,调用不存在就等待。
【2】分布式锁流程
【3】分布式锁加锁的数据结构?
Hash类型,hash数据类型的key值包含了当前线程的信息
【4】为什么底层使用lua脚本?保证原子性
Lua脚本:通过封装在lua脚本中发送给redis,由于redis是单线程,可以保证原子性特征
【5】底层如何实现高性能,可重入锁机制?????????
【6】如何实现锁续期?——WatchDog自动延期机制
每隔10s自动扫描当前线程
情况1:使用锁的线程宕机,默认30秒自动释放锁,防止死锁发生。(可设置有效时间)。
情况2:线程A有效期内业务没执行完,启动看门狗后台线程,不断延长锁key的生存时间。
【7】优、缺点
优点:性能极高,看门狗解决了原子性问题和续期问题
缺点:MasterSlave集群下,可能导致锁上加锁现象
【8】方法
Lock接口方法
(1)加锁(无参默认30秒):void lock( );
(2)加锁(手动设置有效时间):void lock(long leaseTime, TimeUnit unit );
(3)尝试加锁:成功true,败false(即锁已被其他线程获取):boolean tryLock( );
(4)尝试加锁,拿不到锁等待一定时间。时间内拿到true,拿不到false:
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
(5)尝试加锁,参数(等待时间,有效时间,时间单位)
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
(6)解锁:void unlock();
注:lock( )、tryLock( ):主线程未释放,且未调用unlock方法,进入看门狗机制
六、分布式事务
1、如何查看是否存在分布式事务?
● 多服务多数据源(多个服务,每隔服务有自己的数据库)
● 多服务之前存在远程调用出现增删改
2、CAP定理:布鲁尔定理
● 强一致性:Consistency所有用户看到的数据一致,数据分布在不同节点,一个节点数据改变,其他节点数据也改变
● 高可用性:Availability任何时候当前服务器都可用,非故障节点在合理时间(请求不能阻塞)返回合理响应(明确正确返回结果,不是错误和超时的响应)
● 分区容错性:Tolerance to Network Partition鸡蛋不要方法哦同一个班篮子里,出现网络分区,系统还能继续工作,比如,集群有多台机器,一台网络故障,集群仍能正常工作
七、RocketMQ
1、为什么要使用MQ?
因为项目比较大,做了分布式系统,所有远程服务调用请求都是同步执行经常出问题,所以引入了mq
2、消息中间件的区别
3、RocketMQ由哪些角色组成,每个角色作用和特点是什么?
● 生产者(Producer):负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。
● 消费者(Consumer):负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。
● 消息服务器(Broker):是消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息。
● 名称服务器(NameServer):用来保存 Broker 相关 Topic 等元信息并给 Producer ,提供 Consumer 查找 Broker 信息。
4、RocketMQ消费模式有几种?
消费模型由Consumer决定,消费维度为Topic。
集群消费
1.一条消息只会被同Group中的一个Consumer消费
2.多个Group同时消费一个Topic时,每个Group都会有一个Consumer消费到数据
广播消费
消息将对一 个Consumer Group 下的各个 Consumer 实例都消费一遍。即即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。
5、RocketMQ如何做负载均衡?
通过Topic在多Broker中分布式存储实现。
(1)producer端
发送端指定message queue发送消息到相应的broker,来达到写入时的负载均衡
默认策略是随机选择:
其他实现:
也可以自定义实现MessageQueueSelector接口中的select方法
MessageQueue select(final List
(2) consumer端
采用的是平均分配算法来进行负载均衡。
其他负载均衡算法
平均分配策略(默认)(AllocateMessageQueueAveragely)
环形分配策略(AllocateMessageQueueAveragelyByCircle)
手动配置分配策略(AllocateMessageQueueByConfig)
机房分配策略(AllocateMessageQueueByMachineRoom)
一致性哈希分配策略(AllocateMessageQueueConsistentHash)
靠近机房策略(AllocateMachineRoomNearby)
追问:当消费负载均衡consumer和queue不对等的时候会发生什么?
Consumer和queue会优先平均分配,如果Consumer少于queue的个数,则会存在部分Consumer消费多个queue的情况,如果Consumer等于queue的个数,那就是一个Consumer消费一个queue,如果Consumer个数大于queue的个数,那么会有部分Consumer空余出来,白白的浪费了。
6、消息重复消费如何解决?
影响消息正常发送和消费的重要原因是网络的不确定性。
出现原因
正常情况下在consumer真正消费完消息后应该发送ack,通知broker该消息已正常消费,从queue中剔除
当ack因为网络原因无法发送到broker,broker会认为词条消息没有被消费,此后会开启消息重投机制把消息再次投递到consumer。
消费模式:在CLUSTERING模式下,消息在broker中会保证相同group的consumer消费一次,但是针对不同group的consumer会推送多次
解决方案
7、如何让RocketMQ保证消息的顺序消费
你们线上业务用消息中间件的时候,是否需要保证消息的顺序性?
如果不需要保证消息顺序,为什么不需要?假如我有一个场景要保证消息的顺序,你们应该如何保证?
首先多个queue只能保证单个queue里的顺序,queue是典型的FIFO,天然顺序。多个queue同时消费是无法绝对保证消息的有序性的。所以总结如下:
同一topic,同一个QUEUE,发消息的时候一个线程去发送消息,消费的时候 一个线程去消费一个queue里的消息。
追问:怎么保证消息发到同一个queue?
Rocket MQ给我们提供了MessageQueueSelector接口,可以自己重写里面的接口,实现自己的算法,举个最简单的例子:判断i % 2 == 0,那就都放到queue1里,否则放到queue2里。
8、RocketMQ如何保证消息不丢失
首先在如下三个部分都可能会出现丢失消息的情况:
Producer端
Broker端
Consumer端
2.8.1 Producer端如何保证消息不丢失
采取send()同步发消息,发送结果是同步感知的。
发送失败后可以重试,设置重试次数。默认3次。
producer.setRetryTimesWhenSendFailed(10);
集群部署,比如发送失败了的原因可能是当前Broker宕机了,重试的时候会发送到其他Broker上。
2.8.2 Broker端如何保证消息不丢失
修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。
flushDiskType = SYNC_FLUSH
集群部署,主从模式,高可用。
2.8.3 Consumer端如何保证消息不丢失
完全消费正常后在进行手动ack确认。
9、RocketMQ的消息堆积如何处理
1. 如果可以添加消费者解决,就添加消费者的数据量.
2、如果出现了queue,但是消费者多的情况。可以使用准备一个临时的topic,同时创建一些queue,在临时创建一个消费者来把这些消息转移到topic中,让消费者消费。
10、RocketMQ如何实现分布式事务?
1、生产者向MQ服务器发送half消息。
2、half消息发送成功后,MQ服务器返回确认消息给生产者。
3、生产者开始执行本地事务。
4、根据本地事务执行的结果(UNKNOW、commit、rollback)向MQ Server发送提交或回滚消息。
5、如果错过了(可能因为网络异常、生产者突然宕机等导致的异常情况)提交/回滚消息,则MQ服务器将向同一组中的每个生产者发送回查消息以获取事务状态。
6、回查生产者本地事物状态。
7、生产者根据本地事务状态发送提交/回滚消息。
8、MQ服务器将丢弃回滚的消息,但已提交(进行过二次确认的half消息)的消息将投递给消费者进行消费。
Half Message:预处理消息,当broker收到此类消息后,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中
检查事务状态:Broker会开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会向消息发送者确认事务执行状态(提交、回滚、未知),如果是未知,Broker会定时去回调在重新检查。
超时:如果超过回查次数,默认回滚消息。
也就是他并未真正进入Topic的queue,而是用了临时queue来放所谓的half message,等提交事务后才会真正的将half message转移到topic下的queue。
应用场景:消息一步消费,分发试卷
11、任何一台Broker突然宕机了怎么办?
Broker主从架构以及多副本策略。Master收到消息后会同步给Slave,这样一条消息就不止一份了,Master宕机了还有slave中的消息可用,保证了MQ的可靠性和高可用性。而且Rocket MQ4.5.0开始就支持了Dlegder模式,基于raft的,做到了真正意义的HA。
RabbitMQ:erlong开发,大量消息积压会导致性能下降,每秒处理十万到几十万请求
八、MongoDb
1、MongoDB是什么?
mongodb是属于文档型的非关系型数据库,是开源、高性能、高可用、可扩展的
数据逻辑层次关系:文档=>集合=>数据库
在关系型数据库中每一行的数据对应mongodb里是一个文档。mongodb的文档是以BSON(binary json)格式存储的,其格式就是json格式。
1>集合
集合是一组文档(即上面的 users 集合)。集合相当于关系数据库中的表,但集合中的文档长度可不同(集合中的文档中的键值对个数可不同)、集合中文档的key可不同。向集合中插入第一个文档时,集合会被自动创建。
2>文档
文档是一组键值对,用{ }表示,字段之间用逗号分隔。相当于关系数据库中的一行(一条记录)。示例:一个文档
1 2 3 4 5 6 |
{ id: "1", name: "张三", age: 28, email: "[email protected]" } |
这样写是为了方便看字段,也可以写在一起{id:"1",name:"张三",age:28,email:"[email protected]"},一样的。
说明:
文档中的键值对是有序的
一个文档中不能有重复的key(对应关系数据库中的一条记录)
以"_"开头的key是保留的,有特殊含义。
3>字段
即一个键值对,key必须是String类型,value可以是任意类型。
2、MongoDB和关系型数据库mysql区别
上图中,左边的是 MySQL 数据库中 users 表,右边是 MongoDB 中 users 集合。虽然表现形式不同,但是数据内容还是一样的。其中:
test:表示数据库
users:表示集合,类似MySQL中的表
{id:"1",name:"张三",age:28,email:"[email protected]"}:表示一个文档,类似于MySQL中的记录
id、name、age和email:表示字段
3、MangoDB和Redis的区别
实际存储空间不同
Mango:io存储、多key、key唯一
Redis:内存存储、单key、key唯一
4、MongoDB有3个数据库
一个MongoDB中可以建立多个数据库,这些数据库是相互独立的,有自己的集合和权限。不同的数据库使用不同的文件存储(不存储在一个文件中)。
MongoDB默认有3个数据库:
admin: 从权限的角度来看,这是"root"数据库。将一个用户添加到这个数据库,这个用户会自动继承所有数据库的权限。一些特定的服务器端命令也只能在这个数据库中运行,比如列出所有的数据库或者关闭服务器。
local: 这个数据库永远不会被复制,里面的数据都是本地的(不会复制到其他MongoDB服务器上),可以用来存储限于本地单台服务器的任意集合
config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
5、Mongo中的数据类型
1. null
2. false和true
3. 数值
4. UTF-8字符串
5. 日期 new Date()
6. 正则表达式
7. 数组
8. 嵌套文档
9. 对象ID ObjectId()
10. 二进制数据
11. 代码
6、MongoDB适用业务场景
网站数据:MongoDB 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性
缓存:由于性能很高,MongoDB 也适合作为信息基础设施的缓存层。在系统重启之后,由 MongoDB 搭建的持久化缓存层可以避免下层的数据源过载
大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储
高伸缩性的场景:MongoDB 非常适合由数十或数百台服务器组成的数据库。MongoDB的路线图中已经包含对 MapReduce 引擎的内置支持
用于对象及 JSON 数据的存储:MongoDB的 BSON 数据格式非常适合文档化格式的存储及查询。
九、Nginx
1、Nginx是什么?
Nginx 是一个高性能的 HTTP 和反向代理服务器,及电子邮件代理服务器,同时也是一个非常高效的反向代理、负载平衡。
2、Nginx的作用?
1、反向代理,将多台服务器代理成一台服务器。
2、负载均衡,将多个请求均匀的分配到多台服务器上,减轻每台服务器的压力,提高 服务的吞吐量。
3、动静分离,nginx 可以用作静态文件的缓存服务器,提高访问速度
3、Nginx的优势?
(1)可以高并发连接(5 万并发,实际也能支持 2~4 万并发)。
(2)内存消耗少。
(3)成本低廉。
(4)配置文件非常简单。
(5)支持 Rewrite 重写。
(6)内置的健康检查功能。
(7)节省带宽。
(8)稳定性高。
(9)支持热部署。
4、什么是反向代理?
反向代理是指以代理服务器来接受 internet 上的连接请求,然后将请求,发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
反向代理总结就一句话:代理端代理的是服务端.
5、什么是正向代理?
一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
正向代理总结就一句话:代理端代理的是客户端。
6、什么是负载均衡?
负载均衡即是代理服务器将接收的请求均衡的分发到各服务器中,负
载均衡主要解决网络拥塞问题,提高服务器响应速度,服务就近提供,达到更好的访问质量,减少后台服务器大并发压力。
7、Nginx是如何处理一个请求的?
首先,nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip地址,然后在 nginx 的 master 进程里面先初始化好这个监控的 socket,再进行listen,然后再 fork 出多个子进程出来, 子进程会竞争 accept 新的连接。
此时,客户端就可以向 nginx 发起连接了。当客户端与 nginx 进行三次握手,与 nginx
建立好一个连接后,此时,某一个子进程会 accept 成功,然后创建 nginx 对连接的封装,即 ngx_connection_t 结构体,接着,根据事件调用相应的事件处理模块,如 http 模块与客户端进行数据的交换。
最后,nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了。
8、为什么Nginx性能这么高?
得益于它的事件处理机制:异步非阻塞事件处理机制:运用了 epoll模型,提供了一个队列,排队解决。
FastDFS 是一个开源的轻量级分布式文件系统,它可以对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大 容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、 视频网站等等。
1. Storage server(存储服务器)
Storage server一般都是以组(group)为组织单位,一个组中有多个Storageserver,数据互为备份(意味着每个 Storageserver 的内容是一致的,他们之间没有主从之分),组的存储空间以组内最小的 Storage server 为准,所以为了避 免浪费存储空间最好的话每个 Storage server 的配置最好相同。
2. Tracker server(调度服务器、追踪服务器)
232Tracker server 主要负责管理所有的 Storage server 和 group,每个 storage 在启动后会连接 Tracker,告知自己所属的 group 等信息,并保持周期性的心跳,tracker 根据 storage 的心跳信息,建立 group==>[storage server list]的映射表。
1、选择 tracker server
当集群中不止一个 tracker server 时,由于 tracker 之间是完全对等
的关系,客户端在 upload 文件时可以任意选择一个 trakcer。
2、选择存储的 group
当 tracker 接收到 upload file 的请求时,会为该文件分配一个可以
存储该文件的 group,支持如下选择 group 的规则:
1. Round robin,所有的 group 间轮询。
2. Specified group,指定某一个确定的 group。
3. Load balance,剩余存储空间多多 group 优先。
4. 选择 storage server。
3、选择 storage server
233当选定 group 后,tracker 会在 group 内选择一个 storage server
给客户端,支持如下选择 storage 的规则:
1. Round robin,在 group 内的所有 storage 间轮询。
2. First server ordered by ip,按 ip 排序。
3. First server ordered by priority,按优先级排序(优先级在
storage 上配置)。
4、选择 storage path
当分配好 storage server 后,客户端将向 storage 发送写文件请求,
storage 将会为文件分配一个数据存储目录,支持如下规则:
1. Round robin,多个存储目录间轮询。
2. 剩余存储空间最多的优先。
5、生成 Fileid
选定存储目录之后,storage 会为文件生一个 Fileid,由 storage server ip、文件创建时间、文件大小、文件 crc32 和一个随机数拼接而成,然后将这个二进制串进行 base64 编码,转换为可打印的字符串。
6、选择两级目录
当选定存储目录之后,storage 会为文件分配一个 fileid,每个存储目录下有两级 256*256 的子目录,storage 会按文件 fileid 进行两次 hash (猜测),路由到其中一个子目录,然后将文件以 fileid 为文件名存储到该子目录下。
7、生成文件名
当文件存储到某个子目录后,即认为该文件存储成功,接下来会为234该文件生成一个文件名,文件名由 group、存储目录、两级子目录、fileid、 文件后缀名(由客户端指定,主要用于区分文件类型)拼接而成。
当客户端完成文件写至 group 内一个 storage server 之后即认为文件上传成功,storage server 上传完文件之后,会由后台线程将文件同步至同 group 内其他的 storage server。后台线程同步参考的依据是每个 storageserver 在 写完文件后,同时会写一份 binlog,binlog 中只包含文件名等元信息,storage 会记录向 group 内其他 storage 同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有 server 的时钟保持同步。
JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。
在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。
每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。
6.1组成
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{"typ":"JWT","alg":"HS256"}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码(http://base64.xpcha.com/),编码后的字符串如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(playload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:
1. 标准中注册的声明(建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token
2 .公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
3. 私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim, JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。定义一个payload:
{"sub":"1234567890","name":"John Doe","admin":true}
然后将其进行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRta W4iOnRydWV9
签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
6.2 使用场景
1. 一次性验证
比如用户注册后需要发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其它可能的账户……这种场景就和 jwt 的特性非常贴近,jwt 的 payload中固定的参数:iss 签发者和 exp 过期时间正是为其做准备的。
2. restful api 的无状态认证
使用 jwt 来做 restful api 的身份认证也是值得推崇的一种使用方案。客户端和服务端共享 secret;过期时间由服务端校验,客户端定时刷新;签名信息不可被修改......spring security oauth jwt 提供了一套完整的 jwt 认证体系,以笔者的经验来看:使用 oauth2 或 jwt 来做 restful api 的认证都没有大问题,oauth2 功能更多,支持的场景更丰富,后者实现简单。
3.使用 jwt 做单点登录+会话管理(不推荐)
6.3面试问题:
使用 https 加密你的应用,返回 jwt 给客户端时设置 httpOnly=true 并且使用 cookie 而不是 LocalStorage 存储 jwt,这样可以防止 XSS 攻击和 CSRF攻击。
jwt 唯一存储在服务端的只有一个 secret,个人认为这个 secret 应该设计成和用户相关的属性,而不是一个所有用户公用的统一值。这样可以有效的避免一些注销和修改密码时遇到的窘境。
传统的 session+cookie 方案用户点击注销,服务端清空 session 即可,因为状态保存在服务端。但 jwt 的方案就比较难办了,因为 jwt 是无状态的,服务端通过计算来校验有效性。没有存储起来,所以即使客户端删除了 jwt,但是该 jwt 还是在有效期内,只不过处于一个游离状态。分析下痛点:注销变得复杂的原因在于 jwt 的无状态。提供几个方案,视具体的业务来决定能不能接受:
仅仅清空客户端的 cookie,这样用户访问时就不会携带 jwt,服务端就认为用户需要重新登录。这是一个典型的假注销,对于用户表现出退出的行为,实际上这个时候携带对应的 jwt 依旧可以访问系统。
清空或修改服务端的用户对应的 secret,这样在用户注销后,jwt 本身不变,但是由于 secret 不存在或改变,则无法完成校验。这也是为什么将 secret 设计成和用户相关的原因。
借助第三方存储自己管理 jwt 的状态,可以以 jwt 为 key,实现去 Redis 一类的缓存中间件中去校验存在性。方案设计并不难,但是引入 Redis 之后,就把无状态的 jwt 硬生生变成了有状态了,违背了 jwt 的初衷。实际上这个方案和 session 都差不多了。
修改密码则略微有些不同,假设号被到了,修改密码(是用户密码,不是 jwt 的 secret)之后,盗号者在原 jwt 有效期之内依旧可以继续访问系统,所以仅仅清空 cookie 自然是不够的,这时,需要强制性的修改 secret。
传统的 cookie 续签方案一般都是框架自带的,session 有效期 30 分钟,30 分钟内如果有访问,session 有效期被刷新至 30 分钟。而 jwt 本身的 payload 之中也有一个 exp 过期时间参数,来代表一个 jwt 的时效性,而 jwt 想延期这个 exp 就有点身不由己了,因为 payload 是参与签名的,一旦过期时间被修改,整个 jwt 串就变了,jwt 的特性天然不支持续签。
解决方案
1. 每次请求刷新 jwt。
jwt 修改 payload 中的 exp 后整个 jwt 串就会发生改变,那就让它变好了,每次请求都返回一个新的 jwt 给客户端。只是这种方案太暴力了,会带来的性能问题。
2.只要快要过期的时候刷新 jwt
此方案是基于上个方案的改造版,只在前一个jwt的最后几分钟返回给客户端一个新的 jwt。这样做,触发刷新 jwt 基本就要看运气了,如果用户恰巧在最后几分钟访问了服务器,触发了刷新,万事大吉。如果用户连续操作了 27 分钟,只有最后的 3 分钟没有操作,导致未刷新 jwt,无疑会令用户抓狂。
3. 完善 refreshToken
借鉴 oauth2 的设计,返回给客户端一个 refreshToken,允许客户端主动刷新 jwt。一般而言,jwt 的过期时间可以设置为数小时,而 refreshToken 的过期时间设置为数天。
4. 使用 Redis 记录独立的过期时间
在 Redis 中单独为每个 jwt 设置了过期时间,每次访问时刷新 jwt 的过期时间,若 jwt 不存在与 Redis 中则认为过期。
令牌:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJObzAwMDEiLCJpYXQiOjE1NjkxNTg4MDgsInN1YiI6IuS4u-mimCIsImlzcyI6Ind3dy5pdGhlaW1hLmNvbSIsImV4cCI6MTU2OTE1ODgyMywiYWRkcmVzcyI6IuS4reWbvSIsIm1vbmV5IjoxMDAsImFnZSI6MjV9.lkaOahBKcQ-c8sBPp1Op-siL2k6RiwcEiR17JsZDw98
如果令牌被盗,只要该令牌不过期,任何服务都可以使用该令牌,有可能引起不安全操作。我们可以在每次生成令牌的时候,将用户的客户端信息获取,同时获取用户的IP信息,然后将IP和客户端信息以MD5的方式进行加密,放到令牌中作为载荷的一部分,用户每次访问微服务的时候,要先经过微服务网关,此时我们也获取用户客户端信息,同时获取用户的IP,然后将IP和客户端信息拼接到一起再进行MD5加密,如果MD5值和载荷不一致,说明用户的IP发生了变化或者终端发生了变化,有被盗的嫌疑,此时不让访问即可。这种解决方案比较有效。
当然,还有一些别的方法也能减少令牌被盗用的概率,例如设置令牌超时时间不要太长。
elsticsearch
1、elasticsearch了解多少,说说你们公司es的集群架构,索引数据大小,分片有多少,以及一些调优手段 。
解答:
如实结合自己的实践场景回答即可。
比如:ES集群架构13个节点,索引根据通道不同共20+索引,根据日期,每日递增20+,索引:10分片,每日递增1亿+数据,
每个通道每天索引大小控制:150GB之内。
仅索引层面调优手段:
1.1、设计阶段调优
1)根据业务增量需求,采取基于日期模板创建索引,通过roll over API滚动索引;
2)使用别名进行索引管理;
3)每天凌晨定时对索引做force_merge操作,以释放空间;
4)采取冷热分离机制,热数据存储到SSD,提高检索效率;冷数据定期进行shrink操作,以缩减存储;
5)采取curator进行索引的生命周期管理;
6)仅针对需要分词的字段,合理的设置分词器;
7)Mapping阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。 …
1.2、写入调优
1)写入前副本数设置为0;
2)写入前关闭refresh_interval设置为-1,禁用刷新机制;
3)写入过程中:采取bulk批量写入;
4)写入后恢复副本数和刷新间隔;
5)尽量使用自动生成的id。
1.3、查询调优
1)禁用wildcard;
2)禁用批量terms(成百上千的场景);
3)充分利用倒排索引机制,能keyword类型尽量keyword;
4)数据量大时候,可以先基于时间敲定索引再检索;
5)设置合理的路由机制。
2、elasticsearch的倒排索引是什么?
面试官:想了解你对基础概念的认知。
解答:通俗解释一下就可以。
传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。
而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。
有了倒排索引,就能实现o(1)时间复杂度的效率检索文章了,极大的提高了检索效率。
学术的解答方式:
倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。
加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。
lucene从4+版本后开始大量使用的数据结构是FST。FST有两个优点:
1)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
2)查询速度快。O(len(str))的查询时间复杂度。
3、elasticsearch 索引数据多了怎么办,如何调优,部署?
面试官:想了解大数据量的运维能力。
解答:索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户检索或者其他业务受到影响。
如何调优,正如问题1所说,这里细化一下:
4、elasticsearch是如何实现master选举的?
解答:
前置前提:
1)只有候选主节点(master:true)的节点才能成为主节点。
2)最小主节点数(min_master_nodes)的目的是防止脑裂。
这个我看了各种网上分析的版本和源码分析的书籍,云里雾里。
核对了一下代码,核心入口为findMaster,选择主节点成功返回对应Master,否则返回null。选举流程大致描述如下:
第一步:确认候选主节点数达标,elasticsearch.yml设置的值discovery.zen.minimum_master_nodes;
第二步:比较:先判定是否具备master资格,具备候选主节点资格的优先返回;若两节点都为候选主节点,则id小的值会主节点。注意这里的id为string类型。
5、详细描述一下Elasticsearch搜索的过程?
面试官:想了解ES搜索的底层原理,不再只关注业务层面了。
解答:
搜索拆解为“query then fetch” 两个阶段。
query阶段的目的:定位到位置,但不取。
步骤拆解如下:
1)假设一个索引数据有5主+1副本 共10分片,一次请求会命中(主或者副本分片中)的一个。
2)每个分片在本地进行查询,结果返回到本地有序的优先队列中。
3)第2)步骤的结果发送到协调节点,协调节点产生一个全局的排序列表。
fetch阶段的目的:取数据。
路由节点获取所有文档,返回给客户端。
6、Elasticsearch在部署时,对Linux的设置有哪些优化方法?
面试官:想了解对ES集群的运维能力。
解答:
1)关闭缓存swap;
2)堆内存设置为:Min(节点内存/2, 32GB);
3)设置最大文件句柄数;
4)线程池+队列大小根据业务需要做调整;
5)磁盘存储raid方式——存储有条件使用RAID10,增加单节点性能以及避免单节点存储故障。
工作日志怎么看??????、tail -100f | grep
垃圾回收机制????????、、
Dubbo???????、
linux常用命令(docker:虚拟化容器管理技术1、开发环建打包2、测试、生产环境创建容器3、解决不同电脑环境不同问题)
如何防止重复请求
1、根据请求获取ip地址,使用redis去重,其他返回false
2、把请求放到队列里面
Session和cookie的区别
Es
为什么使用MQ?
1、因为项目过大,做了分布式系统,引入MQ可以降低系统耦合度
JSP的9个内置对象,分别为request、response、session、application、out、pageContext、config、page和exception。
1、你做的项目是怎么实现第三方通信的? Token(token的组成)
Token由三部分组成,通过加密方式转为base64编码,自定义信息(数据库的信息,希望存入的信息)转为base64编码(不能是敏感信息),使用hs256编译成签名别人看不懂的信息拼在最后。
4、讲述一下请求发送到HTML或者jspde liucheng
7、java设计工厂
项目问题:
1、项目中遇到的最大的问题是什么?
答:这种问题不要说一般的错误,尽量说业务上的问题。例如:单点登录的时候如何解决多系统之间用户登录信息同步以及用户信息共享;登录需要发送短信验证码的时候如何保证消息到达率是100%;如何实现redis与数据库信息同步;开发环境程序正常,生产环境程序bug等。
2、项目中前台与后台是如何进行数据交互的?
答:ajax,http请求,socket。
3、如何实现数据库与redis同步?
答:用消息队列mq实现。具体操作是在添加或者修改数据的时候,用mq来同步到数据库与redis,加上事务,确保reids与数据库数据一致。
4、项目共有多少张表?所做模块用到多少张表?表与表之间的关系?
5、插入商品的话,要求级联插入几张表,你们当时是怎么实现的?
6、项目中用的注解开发还是手动注入?分别如何实现?为什么?
答:注解开发,在类、属性、方法上写注解。因为项目中需要配置的太多,用注解可以简化开发。
7、错误日志的处理?项目中的日志文件存在哪里?保存多长时间?
答:看日志大小存放, 一般是15天或者30天。存放在一个单独的服务器目录。
8、生产环境与开发环境在上线部署的时候应该如何配置?
答:生产环境:
1、上线之前备份之前的项目
2、修改上线项目的相关配置
3、关停服务
4、替换之前的项目
5、启动服务,观察日志,是否异常
9、开发时数据库中数据从哪来?数据量有多大?
答:开发时数据库数据部分来自客户或者运营,部分自己添加,部分来自网络爬虫扒的数据。
10、如何保证库存?
答:用mq+redis。
11、如果日志存储量过大如何处理?
答:定期清除日志,日志一般存放在另一台服务器上,15-30天清理一次。
12、在项目开发过程中如何进行测试?压力测试如何做?
答:对自己所负责模块进行单元测试,然后交给公司测试人员进行测试。一般压力测试都是测试人员做,Visual
Studio 自带的工具,还有Loader
Runner(LR),轻量级的工具有Apache项目中的ApacheBench。
13、项目的并发量有多大?用了多少台服务器?
答:并发量500-1000,服务器数量一般是10-20台左右,具体数量看图
{width=”6.393055555555556in”
height=”3.770138888888889in”}
14、项目的安全问题是如何解决的?
答:单点登录用token来校验。或者可以说有专门负责项目安全的人员。或者说花钱买服务。
环境安全:初期通过购买云服务
程序安全;token +签名
15、所负责模块中删除数据的时候直接删除就可以么?如果不是需要做哪些操作?
答:如果单表的可以进行逻辑删除,不会进行物理删除。级联删除,删除该条数据不影响到其他表中的数据就可以直接删除,否则要进行级联删除,这里也是指的逻辑删除。
16、如何提高代码质量?在项目中如何优化代码?
答:尽量减少不必要的操作,尽量不要用到三层以上的for循环与递归。写代码的时候要给关键代码写上注释。相同功能的代码进行抽取,抽取原则不影响功能的正常运行。
17、在项目中如何调试bug?
答:1.以dug方式运行项目,打断点调试。2.查看项目中的错误日志。3.测试人员使用专业测试工具进行测试。4.运行脚本对代码进行测试。
18、查询商品的时候如果redis没有数据,可以抛异常么?如果不可以如何做?
答:不可以用throws抛异常,可以用trycatch捕获异常。因为在redis中查询不到数据,还要对数据库进行查询,如果throws抛异常则不能按正常业务运行。
19、服务器宕机如何处理?全部宕机如何处理?
答:配置主从服务器,运维人员搭建集群后,从服务器会给主服务器发送信息,如果主服务器没有响应,那就启用从服务器。一般不会全部宕机,如果全部挂掉,就重启。
20、.dubbo服务开发流程,运行流程?
答:dubbo主要是发布服务和调用服务。
使用流程:
第一步:要在系统中使用dubbo应该先搭建一个注册中心,一般推荐使用zookeeper。
第二步:有了注册中心然后是发布服务,发布服务需要使用spring容器和dubbo标签来发布服务。并且发布服务时需要指定注册中心的位置。
第三步:服务发布之后就是调用服务。一般调用服务也是使用spring容器和dubbo标签来引用服务,这样就可以在客户端的容器中生成一个服务的代理对象,在action或者Controller中直接调用service的方法即可。
消息中间件acitveMQ的作用、原理?几种模式,每种的特点及使用问题?MQ发送消息失败怎么办?
21、浏览器跨域问题
22、海量数据的存储问题
使用非关系性数据库快速查询与插入数据,使用数据库分片将数据由一个数据库分散到多个数据库中,使用数据库中间件mycat读写分离快速定位数据所在的库
Java面试宝典
一、java基础
1、java基础知识
【1】面向对象的特征(了解)
● 封装:把对象的属性和行为结合成一个独立的整体
● 继承:子类继承父类的数据属性和行为,并根据自己需求扩展出新行为,提高代码的复用性
super:
this:
● 多态:一种事物的多种表现形式,一个接口多种实现
● 抽象:对问题领域进行分析、设计得出的抽象概念,是对一系列看上去不同但本质相同的具体概念的抽象。
问题:多态相关,子类接收对象时,会不会执行父类空参构造,原因
解答:会,??????
【2】java的基础数据类型(了解)
数据类型 |
关键字 |
字节数 |
取值范围 |
整数类型 |
byte |
1 |
-128~127 |
short |
2 |
-32768~32767 |
|
int(默认) |
4 |
-2的31次方到2的31次方-1 |
|
long |
8 |
-2的63次方到2的63次方-1 |
|
浮点类型 |
float |
4 |
负数:-3.402823E+38到-1.401298E-45 正数: 1.401298E-45到3.402823E+38 |
double(默认) |
8 |
负数:-1.797693E+308到-4.9000000E-324 正数:4.9000000E-324 到1.797693E+308 |
|
字符类型 |
char |
2 |
0-65535 |
布尔类型 |
boolean |
1 |
true,false |
【3】int 和Integer 区别(了解)
Int |
基本数据类型 |
0 |
不需要 |
直接存储数值 |
Integer |
包装类 |
Null |
变量实例化才可用 |
对对象的引用 |
【4】JDK、JRE、JVM区别 ▲
【5】重载和重写的区别 ▲
● 重载:在同一类中,方法名相同,参数不同
● 重写:父子类中,子类重写父类方法,方法名,参数均相同
【6】= = 和 equals 的区别 ▲
● == 在基本类型中比较值,在引用类型中比较地址值
● Equals 在引用类型中默认比较地址值,呗String、Integer、Date重写比较内容
【7】String、StringBuffer、StringBuilder区别 ▲
● String:字符串常量
● StringBuffer:字符串变量,线程安全,效率低,多线程使用
● StringBuilder:字符串变量,线程不安全,效率高,单线程使用
【8】接口 和 抽象类 的区别 ▲
● 接口:只能有抽象方法,多实现,默认使用public修饰
● 抽象类:有一个抽象方法就叫抽象类,可以有非抽象方法。单继承,可以有构造方法,可以有main方法,可以运行,任意修饰符
【9】什么是单例模式? 有几种 ▲
单例模式:某个类的实例在多线程环境下只能被创建一次
饿汉式:一开始就初始化,线程安全
懒汉式:延迟初始化(什么时候用,什么时候初始化),线程不安全
双检索:延迟初始化,线程安全
【10】反射(了解):通过字节码文件获取这个类的信息,私有只能通过反射获取
获取字节码文件的三种方式:
1> 类名。class
2> 对象名。getClass( ) new 类名的返回值是对象名
3> Class。Forname(“类的全路径”)???
【11】string常用的方法有哪些?(了解)
indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。
【12】jdk1.8 新特性(高薪)
1、Lambda 表达式 :只能用于函数式接口,接口中只有一个抽象方法
(匿名内部类:可以用于接口、类,对方法个数无要求)
(参数……)-> { 方法体 }
Lambda 允许把函数作为一个方法的参数。
使用场景:结合 forEach 遍历,结合sort排序
2、函数式接口
1> 只有一个抽象方法的接口叫做函数式接口
2> 函数式接口可以被隐式转换为 Lambda 表达式
3> 通常函数式接口上会添加 @FunctionalInterface 注解。
3、 方法引用:允许直接引用已有 Java 类或对象的方法或构造方法。
上例中我们将 System.out::println 方法作为静态方法来引用。
4、从 JDK8 开始,接口允许定义默认非抽象方法和静态方法
5、Stream API
将元素集合看作流,流在管道中传输,在管道节点处理任务,比如筛选,排序,聚合,循环,过滤,映射等。
6、日期/时间类改进
比如日期时间的创建、比较、调整、格式化、时间间隔等。这些类都在 java.time 包下,LocalDate/LocalTime/LocalDateTime。
7、Optional 类
Optional 类:可以为 null 的容器对象。如果值存在则 isPresent()方法会返回 true,调用 get()方法会返回该对象。
8、java 8 内置了 Base64 编码的编码器和解码器。
【13】java 异常 ▲
父类:Throwable
子类:error:重要错误
Exception:运行时异常,编译时异常
常见的RunTime异常几种如下:
NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常
【14】BIO、NIO、AIO区别(高薪)
● BIO:同步阻塞式IO,简单方便,并发处理能力低
● NIO:同步非阻塞式IO,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
● AIO:异步非阻塞式IO,基于事件的回调机制
【19】为什么重写equals方法,还要重写hashcode方法?
1.使用hashcode方法提前校验,可以避免每一次比对都调用equals方法,提高效率
2.保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。
【20】手写冒泡排序 ▲
2、get和post请求方法的区别
● get请求:请求参数会保留在url地址中,
所以请求参数到的长度是有限的,
对外暴露不安全,不能用来传递敏感信息
● post请求:通过request body传递参数
get请求:浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
post请求:浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
2、集合 ★
【1】常见的数据结构
【2】list、set、map区别 ▲
● List:有序、可重复
● Set:无序,唯一
● Map:无序,键不可重复,值可以重复
【3】集合分类
【4】Hashmap底层原理 ▲
● 1.8之前底层是:数组+链表,1.8之后底层是数组+链表+红黑树;
● 链表节点 > 8 转换为红黑树,节点 ≤ 6 退化为链表;
● Hashmap刚被创建的时候底层没有创建数组,是在调用put方法时,创建一个长度为16的Node【】数组;
● 数组容量16 × 加载因子0.75 =12是HashMap容量,超过这个值且没有位置,会进行扩容(扩大一倍)组成新的数组;
● 原来的所有数据会调用HashCode()方法重新计算哈希值分配到新的数组,所以扩容非常的消耗性能。
map.put(k,v)实现原理
(1)先将k,v封装到Node对象当中(节点)。
(2)再调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(3)下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
map.get(k)实现原理
【5】Hashmap、Hashtable、CocurrentHashMap区别 ▲
(从线程去全方向讲,以及为什么安全,为什么不安全)
● Hashmap:数组+链表/红黑树、键、值可为null,线程不安全,高效,不同步,适用单线程
● Hashtable:数组+链表、键、值不可为null,线程安全(在修改数据时锁住整个HashTable),低效,同步,适用多线程、初始size为11
(不用Hashtable理由:1> 内部没优化、冗余 2> CocurrentHashMap替代)
● CocurrentHashMap:底层数组+链表/红黑树,1.8取消分段锁,采用CAS和synchronized来保证线程安全
【6】ArrayList和LinkedList的区别
1、ArrayList:数组数组 LinkedList:双向链表
2、当随机访问List时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
3、当对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,数组添加修改对索引下标有影响,造成数据移动
4、ArrayList:手动的设置固定大小,使用方便,
LinkedList,能够动态的随数据量的变化而变化,便于使用。
5、ArrayList主要控件开销在于需要在lList列表预留一定空间;而LinkList主要控件开销在于需要存储结点信息以及结点指针信息。
【7】list如何去重
1、把list集合放到hashset里面,再转回list
2、遍历,equals比较,删除重复
3、stream流过滤
【8】
3、多线程 ★
【1】 进程:正在运行中的程序
线程:进程中的一个单元,任务
多线程:进程中多个任务同时运行,提高运行效率
并发:某一时间段
并行:同一时刻能干的事情
【2】创建线程的几种方式?
【3】start 和 run 方法的区别?
● start:开启线程
● run:线程的普通方法,用于主线程书写任务,方法调用
【4】Java中的线程状态
● new(新建)创建未启动 创建线程的时候属于新建状态
● runable(可运行)线程可以在虚拟机中使用 调用start 方法时转换为就绪状态
● blocked(锁阻塞)线程由于某种原因暂停运行,进入阻塞状态(等待、超时等待、同步阻塞),直到线程进入就绪状态
● Waiting(无线等待)必须唤醒
● time_waiting(计时等待)
● treminated(被终止)线程完成或者异常退出,会转换为死亡状态
【5】线程方法
等待:wait 睡眠:sleep 唤醒:notify notifyAll 等待其他线程终止:join
4、线程池??????
【1】线程池的核心参数
核心线程数:corePoolSize
最大线程数:maximumPoolSize
空闲时间:keepAliveTime
时间单位:unit
阻塞队列:workQueue
线程工厂类:threadFactory
拒绝策略:handler
【2】拒绝策略
阻塞队列????????
【2】线程池分类
1、newCachedThreadPool:创建一个可进行缓存重复利用的线程池
2、newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
3、newSingleThreadExecutor:创建一个使用单个 worker 线程的Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程将会排在队列中以此执行
4、newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
5、newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
6、newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的CPU个数
【3】线程池原理
【4】线程池作用:重复利用已有现成,避免创建、销毁线程造成的消耗,提高系统的响应速度
【5】线程池的状态:
【6】项目中哪里用到了多线程?怎么用的?
● 秒杀 下单处理
● 获取支付结果 第三方系统做了限流
● 批量商品入库 (ES索引库)
● 商品的图片 鉴黄鉴曝涉毒
不要说哪三种方式,直接说线程池,线程的创建与销毁也是需要消耗的。
【7】线程安全问题,如何解决?
● 是否存在多线程环境
● 多线程环境下是否存在共享变量(共享变量:@threadlocal)
● 多线程环境下是否存在共享变量(写)操作
解决:1、加锁 2、synchroized(同步代码块、同步方法、显示锁)
推荐使用:共享变量被volatile修饰,加原子性操作的对象类
【8】为什么要用线程池(线程池的优点有哪些)?
● 降低资源消耗。反复创建线程会消耗系统资源,线程池实现线程的重复利用,减少资源消耗。
● 提高响应时间。任务到达时不需要等待线程创建,可以直接执行任务。
● 提高线程的可管理性。使用线程池可以对线程进行统一的分配,调优和监控。
9、线程死锁条件问题,手写死锁案例???
10、JMM内存模型???
11、volatile关键字???
12、CAS原理(ABA)???
13、原子包 ???
5、锁
【1】同步锁、死锁、乐观锁、悲观锁(高薪)
● 同步锁:同一时间内,只允许一个线程访问共享数据
● 死锁:多线程同时被阻塞,一个或全部都在等待某个资源被释放
● 乐观锁:假设最好的情况,每次拿数据都认为别人不会修改,所以不会上锁,但是在更新时会判断有没有人在更新数据,可以使用版本号机制和CAS算法。适用于多读,提高吞吐量。
● 悲观锁:总是假设最坏的情况,每次拿数据都认为有人会修改,所以先上锁,其他线程阻塞,用完后再把资源转给其他线程。比如行锁、表锁、读锁、写锁等都是先上锁。synchronized和ReentranLock等独占锁就是悲观锁思想。
【2】ThreadLoal的原理(高薪)
ThreadLocal通过静态内部类ThreadLocalMap(其类似于Map),以键值对形式为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
ThreadLocal 作为 key用的是弱引用,因为没有强引用链,弱引用在GC的时候可能会被回收。这样就会存在一些key为null的键值对,无法访问,也不会被清除的。使用完ThreadLocal之后,需要手动调用remove方法删除对应key。 如果不删除会造成内存泄漏,导致内存即不回收也无法访问。
在不使用线程池的前提下,即使不调用remove方法,线程的"变量副本"也会被gc回收,即不会造成内存泄漏的情况。
【3】synchronized 底层实现原理?(高薪)
【4】synchronized、volatile、lock的区别什么?(高薪)▲
synchronized |
Lock |
java内置关键字 |
java类 |
无法判断是否获取到锁 |
可以判断是否获取到锁 |
自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁) |
finally中手动释放锁(unlock()方法释放锁), 否则容易造成线程死锁; |
线程1获得锁,线程2线程等待。 线程1阻塞,线程2一直等待 |
Lock如果尝试获取不到锁, 线程可以不用一直等待就结束; |
可重入、不可中断、非公平 |
可重入、可判断、可公平(两者皆可) |
synchronized锁适合代码少量的同步问题 |
Lock锁适合大量同步的代码的同步问题 |
【5】死锁
【6】redisson-分布式锁
(1)分布式锁原理
(集群:不同服务器,相同事情 分布式:不同服务器节点,不同事情)
分布式锁:不同的服务,操作共享数据,把原来的锁提出来,让所有的服务共享这把锁,调用不存在就等待。
Lua脚本:通过封装在lua脚本中发送给redis,由于redis是单线程,可以保证原子性特征
(2)分布式锁流程
(3)WatchDog自动延期机制
情况1:使用锁的线程宕机,默认30秒自动释放锁,防止死锁发生。(可设置有效时间)。
情况2:线程A有效期内业务没执行完,启动看门狗后台线程,不断延长锁key的生存时间。
【7】项目中用的什么锁,讲一讲??????、、
6、Java并发编程的三大特性??????
● 原子性:一个线程在CPU执行过程中不会被其他线程中断
(如何保证:加锁:synchronized同步加锁,lock加锁)
● 内存可见性:
● 有序性:
二、数据库
1、事务
【1】事务特性
● 原子性:事务要不全被执行,要捕全部被执行
● 一致性:事务执行使数据库从个一种正确状态转变成另一种正确状态
● 隔离性:事务在正确提交之前,不允许把数据的改变交给其他事务
● 持久性:事务提交后,结果会永远保存在数据库中,即使后面出现其他故障,事务结果也会保存
【2】mysql事务的隔离级别,会产生什么影响?举例子(四个隔离级别讲述详细)
● 读未提交:产生脏读
● 读已提交:避免藏独,但是会出现不可重复读和虚读问题
● 可重复度:MySQL默认隔离级别,会产生幻读
● 可串行化:事务的最高级别,强制事务排序,读取事务的时候加上共享锁,解决幻读,可能导致大量超时和锁竞争,为了提升吞吐量不会考虑。
【3】事务原理
项目哪里用到了事务?修改
【4】事务为什么会失效?事务失效了怎么办?
2、索引
【1】索引概念以及优缺点
概念:索引存储在内存中,帮助服务器快速查询的一种数据结构。
优点:
● 提高查询速度,降低IO操作
● 对数据进行排序,降低CPU消耗
缺点:
● 增删改会对索引底层树形结构重新排序,速度慢
● 索引也是一张表,会占用硬盘空间
(架构、部署、多少并发)
【2】索引结构
BTree(多录平衡搜索树)
● 解决二叉树层数过高的问题
● n叉树有n-1个key
B+Tree(n树n key,排序,非叶子节点是key索引)
● n叉树有n个key
● 上面只存索引,只在叶子节点存储key信息,并按照key的大小排序
● 所有非叶子节点都是key的索引(查询所有数据都要从根节点走到叶子节点,所以更稳定)
MySQL的B+Tree(添加链表指针,适合区间查询)
● 添加相邻叶子节点的链表指针,形成带有顺序指针的b+tree,提高区间访问性能
(例:使用b+tree先查树左边数据,然后查树右边数据,需要返回根节点再查询,有了链表直接可以直接和右边节点的首位相比,调阅查询直到找到数据)
【3】索引分类
● 单值索引(单列索引):一个索引只包含单个列,一个表可以有多个单列索引
● 唯一索引:字段唯一,可以有null
● 主键索引:特殊的唯一索引,字段唯一,不可以有null
● 复合索引(联合索引):多个字段上建立的索引,能够加速复合查询条件的检索
● 聚簇索引:每张表的主键后塍一个B+Tree,叶子结点称为数据页,存放整张表的行记录数。这一特性决定了索引组织表中的数据也是缩印的一部分,每张表只能用友一个聚簇索引
● 非聚簇索引
【4】索引设计原则
(1)查询频次高,数据量比较大。
(2)最佳候选列应从where条件中提取(最常用、过滤效果最好的列)
(3)使用唯一索引,区分度越高,使用索引的效率越高。
(4)使用短索引,占用内存空间
(5)组合索引,最左匹配原则,那么相当于是创建了N个索引
【5】哪些字段不适合加索引
● 数据量多,但种类少:性别、城市
● 经常变化,更新
【6】是不是索引越多越好?
不是
● 索引虽然查询快,但是创建慢
● 一个索引相当于一张表,占用磁盘内存
● 对于增删改比较多的表,维护索引的代价大
● MySQL也会犯选择困难病
【7】为什么索引失效?索引失效怎么办?如何避免索引失效现象?
● 最左匹配法则
● 范围查询右边列( where name = "小米科技" and status >= "1" and address = "北京市";)
address索引失效, 因为status是大于号是范围查询.
● 索引上使用运算
● 字符串不加引号
● 以%开头的like 模糊查询
● in走索引,not in 不走索引
● or分割条件,or前有索引, or后无索引(一定用or, 条件列都加索引)
【8】索引语法
● 创建索引
(1)创表时,创建由c2和c3两列组成的索引。
(2)为列添加索引,指定索引名、表名、列名
Create index 索引名 on 表名(列名);
● 查看索引
Show index from 表名;
● 删除索引
DROP INDEX 索引名 ON 表名 ;
● ALTER命令
主键索引:alter table tb_name add primary key(column_list);
唯一索引:alter table tb_name add unique index_name(column_list);
3、SQL
【1】SQL优化的步骤
1、先定位慢查询的sql(慢查询日志、spl、spf)
2、根据explain分析sql慢的原因(explain type key extra....)
* type 级别提高(级别记住):system > const > eq_ref > ref > range > index > ALL
3、优化sql(加索引、改sql)
4、验证是否优化生效
【2】SQL优化
1、查询结果只有一条是可以使用limit
2、limit后面基数过大时,可以使用between…and
3、避免使用select *
4、为搜索字段添加索引
【3】SQL注入??????
【4】Mysql主从复制数据不一致问题??????
4、范式:设计表时需要遵守的规则1
第一范式:表的每一类不可分割
第二范式:一张表描述一件事情,每张表都依赖主键,表与表主外键关联(消除部分依赖)
第三范式:(消除传递依赖)非主属性数据不依赖其他非主属性数据,只依赖主键
反三范式:为了提高性能,增加冗余
5、select语句执行顺序
from→on→join→where→group by→having→select→distimct→order by→limit
6、笛卡尔积现象
原理:两张表数据交叉组合(乘法原理)
解决:使用内连接
7、联级操作:修改或删除主表主键时,从表外键被修改或删除
8、怎么连接数据库
使用mybatis,datesource、diver、user、password
9、如何保证数据的原子性????????
10、如何查询前1000条数据
● select top 1000 from……
● limit 1000
● between 1 and 1000
11、数据库持久层:mybatis(持久层:把数据写到磁盘中)
12、数据库的分库分表?
①当数据比较大的时候,对数据进行分表操作,首先要确定需要将数据平均分配到多少张表中,也就是:表容量。
用户中心数据库切分方法|范围法
以用户中心的业务uid为划分依据,将数据水平切分到两个数据库实例上去,user_db1存储0到1千万的uid数据,user_db2存储1到2千万的数据切分策略简单,扩容简单,数据量和请求量不均匀
用户中心数据库切分方法|哈希法
以用户中心的业务uid为划分依据,将数据水平切分到两个数据库实例上去, user_db1存储uid%2=0的数据,user_db2存储uid%2=1的数据
切分策略简单,数据量和请求量均匀,扩容麻烦,增加一个库需要重新hash.
垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。
13、分库分表带来的问题?
①事务一致性问题: 当更新内容同时分布在不同库中,不可避免会带来跨库事务问题,解决方案: 分布式事务能最大限度保证了数据库操作的原子性。事务补偿的方式,可以基于日志进行对比,定期同标准数据来源进行同步
②跨节点关联查询 join 问题: 全局表, 可以将这类表在每个数据库中都保存一份; 数据组装,分几次查询,再根据id将得到的数据进行拼接; ER分片, 将那些存在关联关系的表记录存放在同一个分片上
全局主键避重问题: UUID是主键是最简单的方案,本地生成,性能高
支持分库分表中间件:Vitess(谷歌),mycat(基于cobar)
三、框架
1、 Mybatis框架
【1】Mybatis
MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,
支持定制化 SQL、存储过程以及高级映射。
使用简单的 XML 或注解来配置和映射原生类型、接口、 POJO类到数据库。
【2】在mybatis中,${} 和 #{} 的区别
● ${} :字符串替换,替换成变量值
● #{} :占位符,预编译处理,替换为?号,防止SQL注入,提高系统安全性。
(SQL注入:传入的参数作为SQL语句的一部分)
【3】resultType和ResultMap的区别
● 数据库列名与实体类属性名相同,使用 resultType 进行自动映射
● 数据库列名与实体类属性名不同,使用 resultMap 进行手动映射
【4】动态SQL的标签
1、
2、
3、
4、
5、
【5】mybatis缓存机制
一级缓存:SqlSession,默认开启,无法关闭
二级缓存:Mapper(Namespace),手动配置cacheEnabled=true开启,实现Serializable序列化接口,查询顺序变为:二级缓存→一级缓存→数据库。
【6】mybatis的编程步骤
【7】原始JDBC有哪些问题?mybatis如何解决?
【8】mybatis的mapper接口调用有什么要求?
【9】mybatis中foreash标签怎么是使用?
2、 Spring框架
【1】spring两大核心
● IOC:控制反转,把创建对象的控制权交给spring容器,spring在配置文件中配置了类的字节码文件,通过反射创建对象,说白了不用new对象了,在spring容器中直接获取。
● AOP:面向切面编程,在程序运行期间,不改变源码的基础上对方法进行增强。SpringAOP使用动态代理,生成临时的AOP对象,包含木变对象的所有属性和方法,并在特定的切点做方法增强。
● DI注入:给类的成员变量赋值
【2】SpringIOC的三种注入方式 :
● 构造器注入 ● setter方法注入 ● 根据注解注入。
【3】Spring动态代理的两种方式
核心接口InvocationHandler 通过 invoke( )方法,反射调用目标类中的代码,动态地将横切逻辑和业务编织在一起,Proxy 类再通过接口动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
(2) CGLIB:是一个代码生成的类库,通过继承的方式,在运行时动态的生成指定类的子对象,覆盖其中特定方法并添加增强代码。在代理类没有实现 InvocationHandler 接口时使用。如果该类被标记为 final无法使用。
【2】spring作用域(5种)
(1)singleton:默认单例作用域,bean,整个容器共享一个实例。
(2)prototype:每一个bean请求创建一个实例。
(3)request:每一个request请求创建一个实例,完成后调用垃圾回收器回收。
(4)session:同一个session会话共享一个实例。
(5)global-session:全局作用域。
【3】Spring的通知类型 动态代理:通过反射对呗代理类的方法增强
前置通知 [before]:切点前运行
后置通知 [after-returning]:切点后运行
异常通知 [after-throwing]
最终通知 [after]
环绕通知 :自定义通知位置,解决其他通知时序问题
【4】Spring框架中的设计模式?
1. 工厂模式:BeanFactory用来创建实例对象
2. 单例模式:Bean默认为单例模式
3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
4. 模板方法:用来解决代码重复的问题。
5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新。
【5】Spring的对象默认是单例的还是多例的? 单例bean存不存在线程安全问题呢?
(1)spring默认单例对象,也可配置多例。
(2)单例bean存在安全问题原因:共享变量,操作桶一个变量肯定有线程安全问题
(多线程操作共享变量,其他线程无法访问该bean对象,造成数据混乱。)
(3)解决办法:bean对象中避免共享变量;或定义ThreadLocal将共享变量保存在ThreadLocal中。
【6】spring事务实现原理:对数据库事务的支持数据库的事务提交、回滚通过binlog和redolog实现
spring事务实现方式:
声明式事务:@Transactional或AOP
编程式事务:beginTransaction(),commit(),rollback()等事务管理相关方法
【7】Spring的事务传播行为(多个事务同时存在,spring如何处理)
Propagation(传播)_require必须 有事务加入,无事务创建
propagation_suppor支持 有事务加入,五十五以非事务状态运行
propagation_mandatory 强制托管
propagation_requires-new 需要新建
propagation_not -supported不支持
propagation_never从不
propagation_nested嵌套的
【8】Spring中的事务隔离:脏读、幻读、不可重复读
Spring中的隔离级别:(ISOLATION:隔离)
① ISOLATION_DEFAULT:默认隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务看到未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,解决脏读问题
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。行锁
⑤ ISOLATION_SERIALIZABLE:事务执行过程中看不到其他事务对数据的修改。表锁
spring事务失效的几种情况
【9】Spring的常用注解(必会)
(1)@Component(任何层) @Controller @Service @Repository(dao)
(2)@Scope : 设置Spring对象的作用域
(3)@PostConstruct :创建对象后
@PreDestroy : 销毁前
(4)@Value: 简单属性的依赖注入
@Autowired: (属性、方法)类型注入,没有搭配 @Qualifier 按名称注入
@Qualifier
@Resource:按照属性名称注入
(5)@ComponentScan: 组件扫描
(6)@Bean: 方法返回值对象放入容器
(7)@Configuration:配置类
@Import: 配置类中导入其它配置类
@PropertySource: 引入其它的配置文件
(8)@Transactional (类、方法)方法具有事务管理功能
【10】SpringBean的配置方式
● 基于XML的配置
● 基于注解的配置(@Component、@Repository、@Controller @Service)
● 基于Java类+配置注解的方式(@Configuration @Bean)
Spring框架优点:
● IOC解耦,简化开发
● AOP面向切面编程
● 声明式事务管理
● 整合第三方优秀框架
3、SpringMVC框架
SpringMVC:(model模型:封装数据、view试图:展示数据、controller:与浏览器交互,处理业务)
【1】SpringMVC的常用注解(必会)
(1)@RequestMapping:处理请求 url 映射
(2)@RequestBody:接收http请求的json数据 转换 为java对象
@ResponseBody:方法返回对象 转化 为json对象
(3)@PathVariable("要获取的参数名"): 从url路径上获取指定参数
(4)@RequestParam: 限制传入参数(参前)
(value:指定传入参数名称 required:指定此参数是否必传 defaultValue:非必传参数指定默认值)
(5)@ControllerAdvice:全局异常处理的类(类)
(6)@ExceptionHandler(Exception.class) :方法可以处理的异常类型(方)
【2】SpringMVC的执行流程
Propagation
4、springboot
【1】Springboot优点
Springboot是对spring的优化,可以快速构建spring项目,基于约定大于配置的思想,减少一切xml配置,做到开箱即用,提高开发效率,缩短项目周期。
● 版本锁定:使用starter启动器管理依赖并对版本几种控制,解决maven依赖版本冲突
● 起步依赖:整合常用jar包
● 自动配置:集合所有约定的默认配置
● 内置Tomcat
【2】springboot常用注解
● @SpringBootApplication:
封装(@SpringBootConfiguration +@EnableAutoConfiguration +@ComponentScan)
● @MapperScan:指定mybatis的接口类路径
● @Service:标记业务层组件
● @RestController:( @Controller + @ResponseBody)
● @PutMapping、@DeleteMapping、@PostMapping、@GetMapping
● @PathVariable:路径变量注解,
● @ControllerAdvice和@ExceptionHander配合完成统一异常拦截处理
【3】springboot原理
springboot约定大于配置,简化开发,而且提供了启动类
1.maven父子依赖
2.自动配置
3.内置tomcat
SpringBoot自动化配置流程总结:
● 程序启动找到自动化配置包下 META-INF/spring.factories 的EnableAutoConfiguration
● SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
● 每个自动配置类按照**条件**进行生效,默认都会绑定配置文件指定的值。**xxxxProperties**里面拿。xxxProperties和配置文件进行了绑定
● 生效的配置类就会给容器中装配很多组件
● 只要容器中有这些组件,相当于这些功能就有了
● 定制化配置
● 用户直接自己@Bean替换底层的组件
● 用户去看这个组件是获取的配置文件什么值就去修改。
开发人员使用步骤总结:
● 引入场景依赖
● 查看自动配置了哪些(选做)
● 自己分析,引入场景对应的自动配置一般都生效了
● 配置文件中debug=true开启自动配置报告。Negative(不生效)\Positive(生效)
● 自己分析是否需要修改
● 参照文档修改配置项,xxxxProperties绑定了配置文件的哪些。
● 自定义加入或者替换组件,@Bean、@Component等
5、springcloud
【1】springcloud的组件
(1)Nacos注册中心
(2)Ribbon负载均衡
(3)Hystrix熔断器
有时候可能是网络问题, 一些其它问题, 导致代码无法正常运行, 这是服务就挂了, 崩溃了. 熔断器就是为了解决无法正常访问服务的时, 提供的一种解决方案.解决因为一个服务崩溃而引起的一系列问题, 使问题只局限于这个服务中,不会影响其他服务.
Hystrix提供了两种功能, 一种是服务降级, 一种是服务熔断.
● Hystrix为每个服务分配了小的线程池, 当用户发请求过来, 会通过线程池创建线程来执行任务, 当创建的线程池已满或者请求超时(这里和多线程线程池不一样,不存在任务队列), 则启动服务降级功能.
● 降级指的请求故障时, 不会阻塞, 会返回一个友好提示(可以自定义, 例如网站维护中请稍后重试), 也就是说不会影响其他服务的运行.
状态机有3个状态:
● Closed:关闭状态(断路器关闭),所有请求都正常访问。
● Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,请求次数最少不低于20次。
● Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。
(4)GateWay网关
(5)Feign远程调用
(6)Spring Cloud Config: 配置中心
【2】SpringBoot和SpringCloud的关系(必会)
● SpringBoot是为了解决Spring配置文件冗余问题, 简化开发的框架.
● SpringCloud是为了解决微服务之间的协调和配置问题, 还有服务之间的通信, 熔断, 负载均衡远程调度任务框架.
● SpringCloud需要依赖SpringBoot搭建微服务, SpringBoot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,SpringCloud很大的一部分是基于SpringBoot来实现。
● SpringBoot不需要依赖SpringCloud就可以独立开发. SpringBoot也可以集成Dubbo进行开发.
【3】SpringCloud和Dubbo的区别(高薪常问)
(1)SpringCloud和Dubbo都是主流的微服务架构.
n● SpringCloud是Apache下的Spring体系下的微服务解决方案.
n● Dubbo是阿里系统中分布式微服务治理框架.
(2)技术方面对比
n● SpringCloud功能远远超过Dubbo, Dubbo只实现了服务治理(注册和发现). 但是SpringCloud提供了很多功能, 有21个子项目
n● Dubbo可以使用Zookeeper作为注册中心, 实现服务的注册和发现, SpringCloud不仅可以使用Eureka作为注册中心, 也可以使用Zookeeper作为注册中心.
n● Dubbo没有实现网关功能, 只能通过第三方技术去整合. 但是SpringCloud有zuul路由网关, 对请求进行负载均衡和分发. 提供熔断器, 而且和git能完美集成.
(3)性能方面对比
n● 由于Dubbo底层是使用Netty这样的NIO框架,是基于TCP协议传输的,配合以Hession序列化完成RPC。
n● 而SpringCloud是基于Http协议+Rest接口调用远程过程的,相对来说,Http请求会有更大的报文,占的带宽也会更多。
n● 使用Dubbo时, 需要给每个实体类实现序列化接口, 将实体类转化为二进制进行RPC通信调用.而使用SpringCloud时, 实体类就不需要进行序列化.
四、Redis缓存
1、Redis、Redis 的存储结构
【1】概述:Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能非关系型(NoSQL)的(key-value)键值对数据库。可以用作数据库、缓存、消息中间件等。
【2】存储结构
String,字符串,是 redis 的最基本的类型,一个 key 对应一个 value。是二进制安全的,最大能存储 512MB。
Hash,散列,是一个键值(key=>value)对集合。string 类型的 field 和value 的映射表,特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40 多亿)
List,列表,是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列边或者尾部(右边)。最多可存储 232 - 1 元素(4294967295, 每个列表可存储 40 亿)
Set,集合, 是 string 类型的无序集合,最大的成员数为 232 -1(4294967295, 每个集合可存储 40 多亿个成员)。
Sorted set,有序集合,和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。
2、Redis事务机制
【1】redis支持伪事务,基于队列实现
【2】 开启事务(multi): 把命令放到队列中,不执行
提交事务(exec): 执行队列中的命令
取消事务(discard):释放队列,不执行
【3】事务处理,一旦出错,丢弃命令:
1)编译:全丢,所有命令都不执行
2)运行:谁错丢谁
3、redis的持久化
Redis 提供了两种持久化的方式,分别是 RDB(Redis DataBase)和 AOF(Append Only File)。
RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上。
AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。
RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。
3、持久化机制▲
【1】持久化机制
● Redis将数据保存在内存中,持久化就是将内存中的数据保存到磁盘中。内存快于硬盘。
● 目的:避免服务器宕机,数据丢失
● Redis提供了两种持久化机制:RDB、AOF。
判断:一断电,redis数据就丢失了 答:错,如果没有持久化机制,那么一断电,redis数据就丢失了
【2】RDB快照:(默认持久化机制)
(1)条件触发,将内存数据快照保存到硬盘,文件后缀名:.rdb
redis.conf文件中配置:6.0版本
save 3600 1 #表示1小时内至少1个键被更改则进行快照。
save 300 100 #表示5分钟(300秒)内至少100个键被更改则进行快照。
save 60 10000 #表示1分钟内至少10000个键被更改则进行快照。
注:最多丢失一分钟以内数据。 6.0之前:save 900 1。 正常关闭会进行一次持久化
(2)手动触发快照命令:
save:(同步执行)阻塞进程,直到拍照完成
bgsave:(默认)(异步子线程执行)fork一个相同的子线程来负责,操作时仍可处理命令
(3)举例:条件触发:延迟拍照
save手动触发:站着不动拍
bgsave手动触发:抓拍(你做你的,我拍我的)
(4)优点:(效率高,重性能)
二进制文件,占用空间少,文件传输快。
自定义规则,可控制。
缺点:(安全性低)
默认配置中最多会丢失一分钟内的数据。
异步执行时创建子线程也会占用时间(此时线程堵塞)
【3】AOF
(1)AOF解决RDB方式会出现数据丢失的问题,需要手动开启,把每次增删改操作redis的命令先保存到缓存区(aof_buf),然后写入到aof文件中,最后同步到硬盘。
(2)AOF触发方式:(折中,所以默认everysec):
● always:每次执行增删改命令时都将缓存区内容全部存进AOF文件,并直接同步到硬盘(最安全,最慢)
● everysec:全部存进,并每隔一秒同步到硬盘。
● no:全部存进,但不同步,由操作系统做同步处理(30秒一次,最快,最不安全)
(4)AOF重写:日积月累,AOF文件过大,效率降低,当AOF达到一定大小时,触发AOF文件重写,redis开启子线程创建一个新的AOF文件替代(一种压缩)
(6)触发配置:
//当前aof文件大小超过上一次aof文件大小的百分之多少时进行重写。如果之前没有重写过,以启动时aof文件大小为准
auto-aof-rewrite-percentage 100
//限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化
auto-aof-rewrite-min-size 64mb
【4】RDB与AOF对比
● RDB默认开启,AOF需手动开启。
● RDB性能高,AOF安全。
● AOF优先级高于RDB。
● RDB存储某个时刻的数据快照,AOF存储写命令。
● RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF默认使用everysec,每秒保存一次,最多丢失两秒以内的数据。
4、Redis 的优点?
【1】因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。
【2】单线程操作,避免了频繁的上下文切换。
【3】采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟踪每个 I/O 流的状态,来管理多个 I/O 流。
4、为什么要用 Redis
高性能:数据存在缓存中,操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
5、Redis集群
【1】高可用-主从复制(如何保证百分百数据不丢失:AOF+主从复制,不能达到百分百)
(1)高可用:行话---几个9(99.999%,一年中有多长时间不可用:5分钟)
(2)原理:
搭建集群,多个从服务器(Slave)复制主服务器(Master)内容实现同步,一般主服务器只做增删改,从服务器只做查询(读写分离,主写从读),解决了高并发(负载均衡)和数据尽量不丢失。
(3)疑问
问题:如果master和所有的slave都开启持久化的话,性能相对来说比较低。
解决:从节点上开启持久化、在主节点关闭持久化
问题:是否会出现宕机,数据丢失?
解答:主节点将数据同步到从节点,主节点宕机,会提升从节点为主节点,原主节点恢复降为从节点完成数据同步。
(4)全量复制:全部复制
增量复制:有旧内容,只更新新内容
(5)主从复制的作用
● 读写分离:主写从读,提高服务器的读写负载能力
● 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
● 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
● 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
● 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
主从复制
主从复制原理
从服务器连接主服务器,发送 SYNC 命令。主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成RDB 文件并使用缓冲区记录此后执行的所有写命令。主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令。从服务器收到快照文件后丢弃所有旧数据,载入收到的快照。主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令。
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令(从服务器初始化完成)。主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)。
优点
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,写服务仍然必须由 Master 来完成Slave同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力Master Server 是以非阻塞的方式为 Slaves 提供服务。所以在Master-Slave 同步期间,客户端仍然可以提交查询或修改请求。Slave Server 同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis 则返回同步之前的数据。
缺点
Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP才能恢复。主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP后还会引入数据不一致的问题,降低了系统的可用性。Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
【2】哨兵机制
● 也是Redis服务器,不读不写,只监控masker节点
● 奇数个哨兵(最少三台),一个哨兵发现宕机,投标选举,是否更改主节点,如果未选出,则集群会等待一段时间(哨兵故障转移超时时间的 2 倍),再重新选举
● 哨兵三大工作任务
○ 监控:1)主机死机没 2)收集从节点状态
○ 提醒:哨兵之间相互通信
○ 自动故障迁移:
1)将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器
2)当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器
● 一主二从三哨兵
● 启动顺序:先启动主再启动从,最后启动3个哨兵
● 哨兵端口:【26379】
哨兵模式
当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。为此,Redis2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控 Redis 系统的运行状况,它的功能包括以下两个。
1、监控主服务器和从服务器是否正常运行。
2、主服务器出现故障时自动将从服务器转换为主服务器。
哨兵的工作方式
每个 Sentinel (哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器,Slave 从务器以及其他 Sentinel(哨兵)进程发送一个 PING 命令。如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被Sentinel(哨兵)进程标记为主观下线(SDOWN)。如果一个 Master 主服务器被标记为主观下线(SDOWN),则正在监视这个 Master 主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了主观下线状态(SDOWN),则 Master 主服务器会被标记为客观下线(ODOWN)。
在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一169 / 196次的频率向集群中的所有Master主服务器,Slave从服务器发送 INFO命令。当 Master 主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。若没有足够数量的 Sentinel(哨兵)进程同意 Master 主服务器下线, Master 主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel (哨兵)进程发送 PING 命令返回有效回复,Master 主服务器的主观下线状态就会被移除。
优点
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。主从可以自动切换,系统更健壮,可用性更高。
缺点
Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
【3】高可扩-Redis Cluster分片集群
1、无中心化,每个服务器都是入口,一个分片集群共有16384个哈希槽,只分配给主服务器,根据哈希槽查询或写数据,至少6个redis服务器,三主三从,实现高可用,高可扩,大量的数据存储。
2、如果槽没有分配,全部不可用
3、先还槽,再删除
4、CRC16算法的目的:???????
5、分片解决问题:海量数据存储(并发)、高可用、高可扩
6、CPU、内存、固态硬盘可以组成电脑,运行内存64、32(分3g)
Redis-Cluster 集群
redis 的哨兵模式基本已经可以实现高可用,读写分离,但是在这种模式下每台 redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0 上加入了 cluster 模式,实现的 redis 的分布式存储,也就是说每台 redis 节点上存储不同的内容。Redis-Cluster 采用无中心结构,它的特点如下:
所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。节点的fail是通过集群中超过半数的节点检测失效时才生效。客户端与 redis 节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
工作方式
在 redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的取值范围是:0-16383。还有一个就是 cluster,可以理解为是一个集群管理的插件。当我们的存取的 key 到达的时候,redis会根据 crc16 的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。为了保证高可用,redis-cluster 集群引入了主从模式,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与A 通信超时,那么认为主节点 A 宕机了。如果主节点 A 和它的从节点A1 都宕机了,那么该集群就无法再提供服务了。
6、key的过期删除策略(key过期什么时候被删除)
【1】定时删除:设置键过期时间的同时,创建一个定时器,键过期立即删除。这个策略能够保证过期键的尽快删除,快速释放内存空间。
【2】惰性删除:获取key时检查是否过期,如果过期就删除。对CPU友好,但浪费大量内存空间。
【3】定期删除:每隔一段时间进行一次删除。默认每秒运行10次,获取20个key,并删除其中已经过期的key。如果其中过期key的比例超过25%,则继续扫描删除。
7、内存淘汰策略
【1】问题:由于不能保证所有的过期key都被删除,同时Redis还在不断插入新数据,最终会造成内存空间不足的问题。
【2】解决:使用内存淘汰策略,在添加数据时,Redis会先检查内存空间大小,如超过最大内存,则触发内存淘汰策略。Redis中默认提供了三类八种淘汰策略。通过修改redis.conf中maxmemory-policy属性值设置不同的内存淘汰策略,默认使用noeviction。
【3】三类八种内存淘汰策略
lru:最近最久未使用的key
lfu:最近使用频率最少的key
ttl:即将过期的key
random:随机删除
noevction:不删除,内存空间不足,直接返回错误信息
8、Redis 的缺点(缓存灾难问题解决)
【1】缓存和数据库双写一致性问题
一致性的问题很常见,因为加入了缓存之后,请求是先从 redis中查询,如果 redis 中存在数据就不会走数据库了,如果不能保证缓存跟数据库的一致性就会导致请求获取到的数据不是最新的数据。
解决方案:
1、编写删除缓存的接口,在更新数据库的同时,调用删除缓存
的接口删除缓存中的数据。这么做会有耦合高以及调用接口失败的情况。
2、消息队列:ActiveMQ,消息通知。
【2】缓存的并发竞争问题
并发竞争,指的是同时有多个子系统去 set 同一个 key 值。
解决方案:
1、最简单的方式就是准备一个分布式锁,大家去抢锁,抢到
锁就做 set 操作即可
【3】缓存雪崩问题
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
1、给缓存的失效时间,加上一个随机值,避免集体失效。
2、使用互斥锁,但是该方案吞吐量明显下降了。
3、搭建 redis 集群。
(4)缓存雪崩
问题:缓存层承载大量请求,有效保护存储层,当缓存层不能提供服务时,所有请求会达到存储层,存储层由于调用量暴增导致挂掉的情况。大量的key到期
解决方案:
● 缓存高可用
● 限流
● 数据预热
● 队列术限流
● 加锁
● 多级缓存(推荐)
Redis雪崩怎么处理:热点key永久化,设置不同的过期时间,错峰延期,……
【4】缓存击穿问题
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
1、利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
2、采用异步更新策略,无论 key 是否取到值,都直接返回,value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。
(3)缓存击穿
1>问题:缓存过期的同时有大量并发请求,发现缓存过期,从后端DB加载数据并返回到缓存,把后端压垮。
2>解决方案:
● 定时器
● 多级缓存
● 分布式锁
● 队列术
(1)缓存穿透
问题:恶意查询导致redis缓存无数据,每次查询都从数据库查,这种现象叫缓存穿透。
解决:如果为null也把数据缓存到redis中。布隆过滤器也是缓存穿透方案之一。
(2)布隆过滤器:
1>作用:能够迅速判断一个元素是否在一个集合中。
2>三个使用场景:
1、网页爬虫对URL的去重:避免爬取相同url地址
2、反垃圾邮件:判断某邮箱是否垃圾邮箱(同理,垃圾短信)
3、缓存穿透:当黑客访问不存在的缓存时迅速返回,避免缓存及DB挂掉。
3>布隆过滤器的计算原理:
(布隆过滤器有一个误判率的概念,误判率越低,则数组越长,所占空间越大。误判率越高则数组越小,所占的空间越小。)内部维护全为0的bit数组,输入集合,集合中的数据通过计算得到数值,把数值对应的数组下标的值设为1。把要判断的数据通过计算得到对比值,判断对比值所对应的数组下标是否为1,是则在集合中,不是则认为不在集合中。
4> 优点:思路简单、保证一致性、性能强
缺点:代码复杂度增大、需要另外维护一个集合来存放缓存的Key 、布隆过滤器不支持删值操作
1.8 Redis的分布式锁
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
1>安全特性:互斥访问,即永远只有一个 client 能拿到锁
2>避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
3>容错性:只要大部分 Redis 节点存活就可以正常提供服务
Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值:设置成功,返回 1 。设置失败,返回 0 。
使用SETNX完成同步锁的流程及事项如下:
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
释放锁,使用DEL命令将锁数据删除
五、Redisson分布式锁
【1】为什么使用分布式锁?
分布式环境下,普通的锁不能锁住各个微服务对为暴露
(集群:不同服务器,相同事情 分布式:不同服务器节点,不同事情)
分布式锁:不同的服务,操作共享数据,把原来的锁提出来,让所有的服务共享这把锁,调用不存在就等待。
【2】分布式锁流程
【3】分布式锁加锁的数据结构?
Hash类型,hash数据类型的key值包含了当前线程的信息
【4】为什么底层使用lua脚本?保证原子性
Lua脚本:通过封装在lua脚本中发送给redis,由于redis是单线程,可以保证原子性特征
【5】底层如何实现高性能,可重入锁机制?????????
【6】如何实现锁续期?——WatchDog自动延期机制
每隔10s自动扫描当前线程
情况1:使用锁的线程宕机,默认30秒自动释放锁,防止死锁发生。(可设置有效时间)。
情况2:线程A有效期内业务没执行完,启动看门狗后台线程,不断延长锁key的生存时间。
【7】优、缺点
优点:性能极高,看门狗解决了原子性问题和续期问题
缺点:MasterSlave集群下,可能导致锁上加锁现象
【8】方法
Lock接口方法
(1)加锁(无参默认30秒):void lock( );
(2)加锁(手动设置有效时间):void lock(long leaseTime, TimeUnit unit );
(3)尝试加锁:成功true,败false(即锁已被其他线程获取):boolean tryLock( );
(4)尝试加锁,拿不到锁等待一定时间。时间内拿到true,拿不到false:
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
(5)尝试加锁,参数(等待时间,有效时间,时间单位)
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
(6)解锁:void unlock();
注:lock( )、tryLock( ):主线程未释放,且未调用unlock方法,进入看门狗机制
六、分布式事务
1、如何查看是否存在分布式事务?
● 多服务多数据源(多个服务,每隔服务有自己的数据库)
● 多服务之前存在远程调用出现增删改
2、CAP定理:布鲁尔定理
● 强一致性:Consistency所有用户看到的数据一致,数据分布在不同节点,一个节点数据改变,其他节点数据也改变
● 高可用性:Availability任何时候当前服务器都可用,非故障节点在合理时间(请求不能阻塞)返回合理响应(明确正确返回结果,不是错误和超时的响应)
● 分区容错性:Tolerance to Network Partition鸡蛋不要方法哦同一个班篮子里,出现网络分区,系统还能继续工作,比如,集群有多台机器,一台网络故障,集群仍能正常工作
七、RocketMQ
1、为什么要使用MQ?
因为项目比较大,做了分布式系统,所有远程服务调用请求都是同步执行经常出问题,所以引入了mq
2、消息中间件的区别
3、RocketMQ由哪些角色组成,每个角色作用和特点是什么?
● 生产者(Producer):负责产生消息,生产者向消息服务器发送由业务应用程序系统生成的消息。
● 消费者(Consumer):负责消费消息,消费者从消息服务器拉取信息并将其输入用户应用程序。
● 消息服务器(Broker):是消息存储中心,主要作用是接收来自 Producer 的消息并存储, Consumer 从这里取得消息。
● 名称服务器(NameServer):用来保存 Broker 相关 Topic 等元信息并给 Producer ,提供 Consumer 查找 Broker 信息。
4、RocketMQ消费模式有几种?
消费模型由Consumer决定,消费维度为Topic。
集群消费
1.一条消息只会被同Group中的一个Consumer消费
2.多个Group同时消费一个Topic时,每个Group都会有一个Consumer消费到数据
广播消费
消息将对一 个Consumer Group 下的各个 Consumer 实例都消费一遍。即即使这些 Consumer 属于同一个Consumer Group ,消息也会被 Consumer Group 中的每个 Consumer 都消费一次。
5、RocketMQ如何做负载均衡?
通过Topic在多Broker中分布式存储实现。
(1)producer端
发送端指定message queue发送消息到相应的broker,来达到写入时的负载均衡
默认策略是随机选择:
其他实现:
也可以自定义实现MessageQueueSelector接口中的select方法
MessageQueue select(final List
(2) consumer端
采用的是平均分配算法来进行负载均衡。
其他负载均衡算法
平均分配策略(默认)(AllocateMessageQueueAveragely)
环形分配策略(AllocateMessageQueueAveragelyByCircle)
手动配置分配策略(AllocateMessageQueueByConfig)
机房分配策略(AllocateMessageQueueByMachineRoom)
一致性哈希分配策略(AllocateMessageQueueConsistentHash)
靠近机房策略(AllocateMachineRoomNearby)
追问:当消费负载均衡consumer和queue不对等的时候会发生什么?
Consumer和queue会优先平均分配,如果Consumer少于queue的个数,则会存在部分Consumer消费多个queue的情况,如果Consumer等于queue的个数,那就是一个Consumer消费一个queue,如果Consumer个数大于queue的个数,那么会有部分Consumer空余出来,白白的浪费了。
6、消息重复消费如何解决?
影响消息正常发送和消费的重要原因是网络的不确定性。
出现原因
正常情况下在consumer真正消费完消息后应该发送ack,通知broker该消息已正常消费,从queue中剔除
当ack因为网络原因无法发送到broker,broker会认为词条消息没有被消费,此后会开启消息重投机制把消息再次投递到consumer。
消费模式:在CLUSTERING模式下,消息在broker中会保证相同group的consumer消费一次,但是针对不同group的consumer会推送多次
解决方案
7、如何让RocketMQ保证消息的顺序消费
你们线上业务用消息中间件的时候,是否需要保证消息的顺序性?
如果不需要保证消息顺序,为什么不需要?假如我有一个场景要保证消息的顺序,你们应该如何保证?
首先多个queue只能保证单个queue里的顺序,queue是典型的FIFO,天然顺序。多个queue同时消费是无法绝对保证消息的有序性的。所以总结如下:
同一topic,同一个QUEUE,发消息的时候一个线程去发送消息,消费的时候 一个线程去消费一个queue里的消息。
追问:怎么保证消息发到同一个queue?
Rocket MQ给我们提供了MessageQueueSelector接口,可以自己重写里面的接口,实现自己的算法,举个最简单的例子:判断i % 2 == 0,那就都放到queue1里,否则放到queue2里。
8、RocketMQ如何保证消息不丢失
首先在如下三个部分都可能会出现丢失消息的情况:
Producer端
Broker端
Consumer端
2.8.1 Producer端如何保证消息不丢失
采取send()同步发消息,发送结果是同步感知的。
发送失败后可以重试,设置重试次数。默认3次。
producer.setRetryTimesWhenSendFailed(10);
集群部署,比如发送失败了的原因可能是当前Broker宕机了,重试的时候会发送到其他Broker上。
2.8.2 Broker端如何保证消息不丢失
修改刷盘策略为同步刷盘。默认情况下是异步刷盘的。
flushDiskType = SYNC_FLUSH
集群部署,主从模式,高可用。
2.8.3 Consumer端如何保证消息不丢失
完全消费正常后在进行手动ack确认。
9、RocketMQ的消息堆积如何处理
1. 如果可以添加消费者解决,就添加消费者的数据量.
2、如果出现了queue,但是消费者多的情况。可以使用准备一个临时的topic,同时创建一些queue,在临时创建一个消费者来把这些消息转移到topic中,让消费者消费。
10、RocketMQ如何实现分布式事务?
1、生产者向MQ服务器发送half消息。
2、half消息发送成功后,MQ服务器返回确认消息给生产者。
3、生产者开始执行本地事务。
4、根据本地事务执行的结果(UNKNOW、commit、rollback)向MQ Server发送提交或回滚消息。
5、如果错过了(可能因为网络异常、生产者突然宕机等导致的异常情况)提交/回滚消息,则MQ服务器将向同一组中的每个生产者发送回查消息以获取事务状态。
6、回查生产者本地事物状态。
7、生产者根据本地事务状态发送提交/回滚消息。
8、MQ服务器将丢弃回滚的消息,但已提交(进行过二次确认的half消息)的消息将投递给消费者进行消费。
Half Message:预处理消息,当broker收到此类消息后,会存储到RMQ_SYS_TRANS_HALF_TOPIC的消息消费队列中
检查事务状态:Broker会开启一个定时任务,消费RMQ_SYS_TRANS_HALF_TOPIC队列中的消息,每次执行任务会向消息发送者确认事务执行状态(提交、回滚、未知),如果是未知,Broker会定时去回调在重新检查。
超时:如果超过回查次数,默认回滚消息。
也就是他并未真正进入Topic的queue,而是用了临时queue来放所谓的half message,等提交事务后才会真正的将half message转移到topic下的queue。
应用场景:消息一步消费,分发试卷
11、任何一台Broker突然宕机了怎么办?
Broker主从架构以及多副本策略。Master收到消息后会同步给Slave,这样一条消息就不止一份了,Master宕机了还有slave中的消息可用,保证了MQ的可靠性和高可用性。而且Rocket MQ4.5.0开始就支持了Dlegder模式,基于raft的,做到了真正意义的HA。
RabbitMQ:erlong开发,大量消息积压会导致性能下降,每秒处理十万到几十万请求
八、MongoDb
1、MongoDB是什么?
mongodb是属于文档型的非关系型数据库,是开源、高性能、高可用、可扩展的
数据逻辑层次关系:文档=>集合=>数据库
在关系型数据库中每一行的数据对应mongodb里是一个文档。mongodb的文档是以BSON(binary json)格式存储的,其格式就是json格式。
1>集合
集合是一组文档(即上面的 users 集合)。集合相当于关系数据库中的表,但集合中的文档长度可不同(集合中的文档中的键值对个数可不同)、集合中文档的key可不同。向集合中插入第一个文档时,集合会被自动创建。
2>文档
文档是一组键值对,用{ }表示,字段之间用逗号分隔。相当于关系数据库中的一行(一条记录)。示例:一个文档
1 2 3 4 5 6 |
{ id: "1", name: "张三", age: 28, email: "[email protected]" } |
这样写是为了方便看字段,也可以写在一起{id:"1",name:"张三",age:28,email:"[email protected]"},一样的。
说明:
文档中的键值对是有序的
一个文档中不能有重复的key(对应关系数据库中的一条记录)
以"_"开头的key是保留的,有特殊含义。
3>字段
即一个键值对,key必须是String类型,value可以是任意类型。
2、MongoDB和关系型数据库mysql区别
上图中,左边的是 MySQL 数据库中 users 表,右边是 MongoDB 中 users 集合。虽然表现形式不同,但是数据内容还是一样的。其中:
test:表示数据库
users:表示集合,类似MySQL中的表
{id:"1",name:"张三",age:28,email:"[email protected]"}:表示一个文档,类似于MySQL中的记录
id、name、age和email:表示字段
3、MangoDB和Redis的区别
实际存储空间不同
Mango:io存储、多key、key唯一
Redis:内存存储、单key、key唯一
4、MongoDB有3个数据库
一个MongoDB中可以建立多个数据库,这些数据库是相互独立的,有自己的集合和权限。不同的数据库使用不同的文件存储(不存储在一个文件中)。
MongoDB默认有3个数据库:
admin: 从权限的角度来看,这是"root"数据库。将一个用户添加到这个数据库,这个用户会自动继承所有数据库的权限。一些特定的服务器端命令也只能在这个数据库中运行,比如列出所有的数据库或者关闭服务器。
local: 这个数据库永远不会被复制,里面的数据都是本地的(不会复制到其他MongoDB服务器上),可以用来存储限于本地单台服务器的任意集合
config: 当Mongo用于分片设置时,config数据库在内部使用,用于保存分片的相关信息。
5、Mongo中的数据类型
1. null
2. false和true
3. 数值
4. UTF-8字符串
5. 日期 new Date()
6. 正则表达式
7. 数组
8. 嵌套文档
9. 对象ID ObjectId()
10. 二进制数据
11. 代码
6、MongoDB适用业务场景
网站数据:MongoDB 非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性
缓存:由于性能很高,MongoDB 也适合作为信息基础设施的缓存层。在系统重启之后,由 MongoDB 搭建的持久化缓存层可以避免下层的数据源过载
大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵,在此之前,很多时候程序员往往会选择传统的文件进行存储
高伸缩性的场景:MongoDB 非常适合由数十或数百台服务器组成的数据库。MongoDB的路线图中已经包含对 MapReduce 引擎的内置支持
用于对象及 JSON 数据的存储:MongoDB的 BSON 数据格式非常适合文档化格式的存储及查询。
九、Nginx
1、Nginx是什么?
Nginx 是一个高性能的 HTTP 和反向代理服务器,及电子邮件代理服务器,同时也是一个非常高效的反向代理、负载平衡。
2、Nginx的作用?
1、反向代理,将多台服务器代理成一台服务器。
2、负载均衡,将多个请求均匀的分配到多台服务器上,减轻每台服务器的压力,提高 服务的吞吐量。
3、动静分离,nginx 可以用作静态文件的缓存服务器,提高访问速度
3、Nginx的优势?
(1)可以高并发连接(5 万并发,实际也能支持 2~4 万并发)。
(2)内存消耗少。
(3)成本低廉。
(4)配置文件非常简单。
(5)支持 Rewrite 重写。
(6)内置的健康检查功能。
(7)节省带宽。
(8)稳定性高。
(9)支持热部署。
4、什么是反向代理?
反向代理是指以代理服务器来接受 internet 上的连接请求,然后将请求,发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。
反向代理总结就一句话:代理端代理的是服务端.
5、什么是正向代理?
一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。
正向代理总结就一句话:代理端代理的是客户端。
6、什么是负载均衡?
负载均衡即是代理服务器将接收的请求均衡的分发到各服务器中,负
载均衡主要解决网络拥塞问题,提高服务器响应速度,服务就近提供,达到更好的访问质量,减少后台服务器大并发压力。
7、Nginx是如何处理一个请求的?
首先,nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip地址,然后在 nginx 的 master 进程里面先初始化好这个监控的 socket,再进行listen,然后再 fork 出多个子进程出来, 子进程会竞争 accept 新的连接。
此时,客户端就可以向 nginx 发起连接了。当客户端与 nginx 进行三次握手,与 nginx
建立好一个连接后,此时,某一个子进程会 accept 成功,然后创建 nginx 对连接的封装,即 ngx_connection_t 结构体,接着,根据事件调用相应的事件处理模块,如 http 模块与客户端进行数据的交换。
最后,nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了。
8、为什么Nginx性能这么高?
得益于它的事件处理机制:异步非阻塞事件处理机制:运用了 epoll模型,提供了一个队列,排队解决。
FastDFS 是一个开源的轻量级分布式文件系统,它可以对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大 容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、 视频网站等等。
1. Storage server(存储服务器)
Storage server一般都是以组(group)为组织单位,一个组中有多个Storageserver,数据互为备份(意味着每个 Storageserver 的内容是一致的,他们之间没有主从之分),组的存储空间以组内最小的 Storage server 为准,所以为了避 免浪费存储空间最好的话每个 Storage server 的配置最好相同。
2. Tracker server(调度服务器、追踪服务器)
232Tracker server 主要负责管理所有的 Storage server 和 group,每个 storage 在启动后会连接 Tracker,告知自己所属的 group 等信息,并保持周期性的心跳,tracker 根据 storage 的心跳信息,建立 group==>[storage server list]的映射表。
1、选择 tracker server
当集群中不止一个 tracker server 时,由于 tracker 之间是完全对等
的关系,客户端在 upload 文件时可以任意选择一个 trakcer。
2、选择存储的 group
当 tracker 接收到 upload file 的请求时,会为该文件分配一个可以
存储该文件的 group,支持如下选择 group 的规则:
1. Round robin,所有的 group 间轮询。
2. Specified group,指定某一个确定的 group。
3. Load balance,剩余存储空间多多 group 优先。
4. 选择 storage server。
3、选择 storage server
233当选定 group 后,tracker 会在 group 内选择一个 storage server
给客户端,支持如下选择 storage 的规则:
1. Round robin,在 group 内的所有 storage 间轮询。
2. First server ordered by ip,按 ip 排序。
3. First server ordered by priority,按优先级排序(优先级在
storage 上配置)。
4、选择 storage path
当分配好 storage server 后,客户端将向 storage 发送写文件请求,
storage 将会为文件分配一个数据存储目录,支持如下规则:
1. Round robin,多个存储目录间轮询。
2. 剩余存储空间最多的优先。
5、生成 Fileid
选定存储目录之后,storage 会为文件生一个 Fileid,由 storage server ip、文件创建时间、文件大小、文件 crc32 和一个随机数拼接而成,然后将这个二进制串进行 base64 编码,转换为可打印的字符串。
6、选择两级目录
当选定存储目录之后,storage 会为文件分配一个 fileid,每个存储目录下有两级 256*256 的子目录,storage 会按文件 fileid 进行两次 hash (猜测),路由到其中一个子目录,然后将文件以 fileid 为文件名存储到该子目录下。
7、生成文件名
当文件存储到某个子目录后,即认为该文件存储成功,接下来会为234该文件生成一个文件名,文件名由 group、存储目录、两级子目录、fileid、 文件后缀名(由客户端指定,主要用于区分文件类型)拼接而成。
当客户端完成文件写至 group 内一个 storage server 之后即认为文件上传成功,storage server 上传完文件之后,会由后台线程将文件同步至同 group 内其他的 storage server。后台线程同步参考的依据是每个 storageserver 在 写完文件后,同时会写一份 binlog,binlog 中只包含文件名等元信息,storage 会记录向 group 内其他 storage 同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有 server 的时钟保持同步。
JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。
在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。
每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。
6.1组成
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{"typ":"JWT","alg":"HS256"}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码(http://base64.xpcha.com/),编码后的字符串如下:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(playload)
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:
1. 标准中注册的声明(建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token
2 .公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
3. 私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如前面那个结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim, JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。定义一个payload:
{"sub":"1234567890","name":"John Doe","admin":true}
然后将其进行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRta W4iOnRydWV9
签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
6.2 使用场景
1. 一次性验证
比如用户注册后需要发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性(通常只允许几小时之内激活),不能被篡改以激活其它可能的账户……这种场景就和 jwt 的特性非常贴近,jwt 的 payload中固定的参数:iss 签发者和 exp 过期时间正是为其做准备的。
2. restful api 的无状态认证
使用 jwt 来做 restful api 的身份认证也是值得推崇的一种使用方案。客户端和服务端共享 secret;过期时间由服务端校验,客户端定时刷新;签名信息不可被修改......spring security oauth jwt 提供了一套完整的 jwt 认证体系,以笔者的经验来看:使用 oauth2 或 jwt 来做 restful api 的认证都没有大问题,oauth2 功能更多,支持的场景更丰富,后者实现简单。
3.使用 jwt 做单点登录+会话管理(不推荐)
6.3面试问题:
使用 https 加密你的应用,返回 jwt 给客户端时设置 httpOnly=true 并且使用 cookie 而不是 LocalStorage 存储 jwt,这样可以防止 XSS 攻击和 CSRF攻击。
jwt 唯一存储在服务端的只有一个 secret,个人认为这个 secret 应该设计成和用户相关的属性,而不是一个所有用户公用的统一值。这样可以有效的避免一些注销和修改密码时遇到的窘境。
传统的 session+cookie 方案用户点击注销,服务端清空 session 即可,因为状态保存在服务端。但 jwt 的方案就比较难办了,因为 jwt 是无状态的,服务端通过计算来校验有效性。没有存储起来,所以即使客户端删除了 jwt,但是该 jwt 还是在有效期内,只不过处于一个游离状态。分析下痛点:注销变得复杂的原因在于 jwt 的无状态。提供几个方案,视具体的业务来决定能不能接受:
仅仅清空客户端的 cookie,这样用户访问时就不会携带 jwt,服务端就认为用户需要重新登录。这是一个典型的假注销,对于用户表现出退出的行为,实际上这个时候携带对应的 jwt 依旧可以访问系统。
清空或修改服务端的用户对应的 secret,这样在用户注销后,jwt 本身不变,但是由于 secret 不存在或改变,则无法完成校验。这也是为什么将 secret 设计成和用户相关的原因。
借助第三方存储自己管理 jwt 的状态,可以以 jwt 为 key,实现去 Redis 一类的缓存中间件中去校验存在性。方案设计并不难,但是引入 Redis 之后,就把无状态的 jwt 硬生生变成了有状态了,违背了 jwt 的初衷。实际上这个方案和 session 都差不多了。
修改密码则略微有些不同,假设号被到了,修改密码(是用户密码,不是 jwt 的 secret)之后,盗号者在原 jwt 有效期之内依旧可以继续访问系统,所以仅仅清空 cookie 自然是不够的,这时,需要强制性的修改 secret。
传统的 cookie 续签方案一般都是框架自带的,session 有效期 30 分钟,30 分钟内如果有访问,session 有效期被刷新至 30 分钟。而 jwt 本身的 payload 之中也有一个 exp 过期时间参数,来代表一个 jwt 的时效性,而 jwt 想延期这个 exp 就有点身不由己了,因为 payload 是参与签名的,一旦过期时间被修改,整个 jwt 串就变了,jwt 的特性天然不支持续签。
解决方案
1. 每次请求刷新 jwt。
jwt 修改 payload 中的 exp 后整个 jwt 串就会发生改变,那就让它变好了,每次请求都返回一个新的 jwt 给客户端。只是这种方案太暴力了,会带来的性能问题。
2.只要快要过期的时候刷新 jwt
此方案是基于上个方案的改造版,只在前一个jwt的最后几分钟返回给客户端一个新的 jwt。这样做,触发刷新 jwt 基本就要看运气了,如果用户恰巧在最后几分钟访问了服务器,触发了刷新,万事大吉。如果用户连续操作了 27 分钟,只有最后的 3 分钟没有操作,导致未刷新 jwt,无疑会令用户抓狂。
3. 完善 refreshToken
借鉴 oauth2 的设计,返回给客户端一个 refreshToken,允许客户端主动刷新 jwt。一般而言,jwt 的过期时间可以设置为数小时,而 refreshToken 的过期时间设置为数天。
4. 使用 Redis 记录独立的过期时间
在 Redis 中单独为每个 jwt 设置了过期时间,每次访问时刷新 jwt 的过期时间,若 jwt 不存在与 Redis 中则认为过期。
令牌:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJObzAwMDEiLCJpYXQiOjE1NjkxNTg4MDgsInN1YiI6IuS4u-mimCIsImlzcyI6Ind3dy5pdGhlaW1hLmNvbSIsImV4cCI6MTU2OTE1ODgyMywiYWRkcmVzcyI6IuS4reWbvSIsIm1vbmV5IjoxMDAsImFnZSI6MjV9.lkaOahBKcQ-c8sBPp1Op-siL2k6RiwcEiR17JsZDw98
如果令牌被盗,只要该令牌不过期,任何服务都可以使用该令牌,有可能引起不安全操作。我们可以在每次生成令牌的时候,将用户的客户端信息获取,同时获取用户的IP信息,然后将IP和客户端信息以MD5的方式进行加密,放到令牌中作为载荷的一部分,用户每次访问微服务的时候,要先经过微服务网关,此时我们也获取用户客户端信息,同时获取用户的IP,然后将IP和客户端信息拼接到一起再进行MD5加密,如果MD5值和载荷不一致,说明用户的IP发生了变化或者终端发生了变化,有被盗的嫌疑,此时不让访问即可。这种解决方案比较有效。
当然,还有一些别的方法也能减少令牌被盗用的概率,例如设置令牌超时时间不要太长。
elsticsearch
1、elasticsearch了解多少,说说你们公司es的集群架构,索引数据大小,分片有多少,以及一些调优手段 。
解答:
如实结合自己的实践场景回答即可。
比如:ES集群架构13个节点,索引根据通道不同共20+索引,根据日期,每日递增20+,索引:10分片,每日递增1亿+数据,
每个通道每天索引大小控制:150GB之内。
仅索引层面调优手段:
1.1、设计阶段调优
1)根据业务增量需求,采取基于日期模板创建索引,通过roll over API滚动索引;
2)使用别名进行索引管理;
3)每天凌晨定时对索引做force_merge操作,以释放空间;
4)采取冷热分离机制,热数据存储到SSD,提高检索效率;冷数据定期进行shrink操作,以缩减存储;
5)采取curator进行索引的生命周期管理;
6)仅针对需要分词的字段,合理的设置分词器;
7)Mapping阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。 …
1.2、写入调优
1)写入前副本数设置为0;
2)写入前关闭refresh_interval设置为-1,禁用刷新机制;
3)写入过程中:采取bulk批量写入;
4)写入后恢复副本数和刷新间隔;
5)尽量使用自动生成的id。
1.3、查询调优
1)禁用wildcard;
2)禁用批量terms(成百上千的场景);
3)充分利用倒排索引机制,能keyword类型尽量keyword;
4)数据量大时候,可以先基于时间敲定索引再检索;
5)设置合理的路由机制。
2、elasticsearch的倒排索引是什么?
面试官:想了解你对基础概念的认知。
解答:通俗解释一下就可以。
传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。
而倒排索引,是通过分词策略,形成了词和文章的映射关系表,这种词典+映射表即为倒排索引。
有了倒排索引,就能实现o(1)时间复杂度的效率检索文章了,极大的提高了检索效率。
学术的解答方式:
倒排索引,相反于一篇文章包含了哪些词,它从词出发,记载了这个词在哪些文档中出现过,由两部分组成——词典和倒排表。
加分项:倒排索引的底层实现是基于:FST(Finite State Transducer)数据结构。
lucene从4+版本后开始大量使用的数据结构是FST。FST有两个优点:
1)空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间;
2)查询速度快。O(len(str))的查询时间复杂度。
3、elasticsearch 索引数据多了怎么办,如何调优,部署?
面试官:想了解大数据量的运维能力。
解答:索引数据的规划,应在前期做好规划,正所谓“设计先行,编码在后”,这样才能有效的避免突如其来的数据激增导致集群处理能力不足引发的线上客户检索或者其他业务受到影响。
如何调优,正如问题1所说,这里细化一下:
4、elasticsearch是如何实现master选举的?
解答:
前置前提:
1)只有候选主节点(master:true)的节点才能成为主节点。
2)最小主节点数(min_master_nodes)的目的是防止脑裂。
这个我看了各种网上分析的版本和源码分析的书籍,云里雾里。
核对了一下代码,核心入口为findMaster,选择主节点成功返回对应Master,否则返回null。选举流程大致描述如下:
第一步:确认候选主节点数达标,elasticsearch.yml设置的值discovery.zen.minimum_master_nodes;
第二步:比较:先判定是否具备master资格,具备候选主节点资格的优先返回;若两节点都为候选主节点,则id小的值会主节点。注意这里的id为string类型。
5、详细描述一下Elasticsearch搜索的过程?
面试官:想了解ES搜索的底层原理,不再只关注业务层面了。
解答:
搜索拆解为“query then fetch” 两个阶段。
query阶段的目的:定位到位置,但不取。
步骤拆解如下:
1)假设一个索引数据有5主+1副本 共10分片,一次请求会命中(主或者副本分片中)的一个。
2)每个分片在本地进行查询,结果返回到本地有序的优先队列中。
3)第2)步骤的结果发送到协调节点,协调节点产生一个全局的排序列表。
fetch阶段的目的:取数据。
路由节点获取所有文档,返回给客户端。
6、Elasticsearch在部署时,对Linux的设置有哪些优化方法?
面试官:想了解对ES集群的运维能力。
解答:
1)关闭缓存swap;
2)堆内存设置为:Min(节点内存/2, 32GB);
3)设置最大文件句柄数;
4)线程池+队列大小根据业务需要做调整;
5)磁盘存储raid方式——存储有条件使用RAID10,增加单节点性能以及避免单节点存储故障。
工作日志怎么看??????、tail -100f | grep
垃圾回收机制????????、、
Dubbo???????、
linux常用命令(docker:虚拟化容器管理技术1、开发环建打包2、测试、生产环境创建容器3、解决不同电脑环境不同问题)
如何防止重复请求
1、根据请求获取ip地址,使用redis去重,其他返回false
2、把请求放到队列里面
Session和cookie的区别
Es
为什么使用MQ?
1、因为项目过大,做了分布式系统,引入MQ可以降低系统耦合度
JSP的9个内置对象,分别为request、response、session、application、out、pageContext、config、page和exception。
1、你做的项目是怎么实现第三方通信的? Token(token的组成)
Token由三部分组成,通过加密方式转为base64编码,自定义信息(数据库的信息,希望存入的信息)转为base64编码(不能是敏感信息),使用hs256编译成签名别人看不懂的信息拼在最后。
4、讲述一下请求发送到HTML或者jspde liucheng
7、java设计工厂
项目问题:
1、项目中遇到的最大的问题是什么?
答:这种问题不要说一般的错误,尽量说业务上的问题。例如:单点登录的时候如何解决多系统之间用户登录信息同步以及用户信息共享;登录需要发送短信验证码的时候如何保证消息到达率是100%;如何实现redis与数据库信息同步;开发环境程序正常,生产环境程序bug等。
2、项目中前台与后台是如何进行数据交互的?
答:ajax,http请求,socket。
3、如何实现数据库与redis同步?
答:用消息队列mq实现。具体操作是在添加或者修改数据的时候,用mq来同步到数据库与redis,加上事务,确保reids与数据库数据一致。
4、项目共有多少张表?所做模块用到多少张表?表与表之间的关系?
5、插入商品的话,要求级联插入几张表,你们当时是怎么实现的?
6、项目中用的注解开发还是手动注入?分别如何实现?为什么?
答:注解开发,在类、属性、方法上写注解。因为项目中需要配置的太多,用注解可以简化开发。
7、错误日志的处理?项目中的日志文件存在哪里?保存多长时间?
答:看日志大小存放, 一般是15天或者30天。存放在一个单独的服务器目录。
8、生产环境与开发环境在上线部署的时候应该如何配置?
答:生产环境:
1、上线之前备份之前的项目
2、修改上线项目的相关配置
3、关停服务
4、替换之前的项目
5、启动服务,观察日志,是否异常
9、开发时数据库中数据从哪来?数据量有多大?
答:开发时数据库数据部分来自客户或者运营,部分自己添加,部分来自网络爬虫扒的数据。
10、如何保证库存?
答:用mq+redis。
11、如果日志存储量过大如何处理?
答:定期清除日志,日志一般存放在另一台服务器上,15-30天清理一次。
12、在项目开发过程中如何进行测试?压力测试如何做?
答:对自己所负责模块进行单元测试,然后交给公司测试人员进行测试。一般压力测试都是测试人员做,Visual
Studio 自带的工具,还有Loader
Runner(LR),轻量级的工具有Apache项目中的ApacheBench。
13、项目的并发量有多大?用了多少台服务器?
答:并发量500-1000,服务器数量一般是10-20台左右,具体数量看图
{width=”6.393055555555556in”
height=”3.770138888888889in”}
14、项目的安全问题是如何解决的?
答:单点登录用token来校验。或者可以说有专门负责项目安全的人员。或者说花钱买服务。
环境安全:初期通过购买云服务
程序安全;token +签名
15、所负责模块中删除数据的时候直接删除就可以么?如果不是需要做哪些操作?
答:如果单表的可以进行逻辑删除,不会进行物理删除。级联删除,删除该条数据不影响到其他表中的数据就可以直接删除,否则要进行级联删除,这里也是指的逻辑删除。
16、如何提高代码质量?在项目中如何优化代码?
答:尽量减少不必要的操作,尽量不要用到三层以上的for循环与递归。写代码的时候要给关键代码写上注释。相同功能的代码进行抽取,抽取原则不影响功能的正常运行。
17、在项目中如何调试bug?
答:1.以dug方式运行项目,打断点调试。2.查看项目中的错误日志。3.测试人员使用专业测试工具进行测试。4.运行脚本对代码进行测试。
18、查询商品的时候如果redis没有数据,可以抛异常么?如果不可以如何做?
答:不可以用throws抛异常,可以用trycatch捕获异常。因为在redis中查询不到数据,还要对数据库进行查询,如果throws抛异常则不能按正常业务运行。
19、服务器宕机如何处理?全部宕机如何处理?
答:配置主从服务器,运维人员搭建集群后,从服务器会给主服务器发送信息,如果主服务器没有响应,那就启用从服务器。一般不会全部宕机,如果全部挂掉,就重启。
20、.dubbo服务开发流程,运行流程?
答:dubbo主要是发布服务和调用服务。
使用流程:
第一步:要在系统中使用dubbo应该先搭建一个注册中心,一般推荐使用zookeeper。
第二步:有了注册中心然后是发布服务,发布服务需要使用spring容器和dubbo标签来发布服务。并且发布服务时需要指定注册中心的位置。
第三步:服务发布之后就是调用服务。一般调用服务也是使用spring容器和dubbo标签来引用服务,这样就可以在客户端的容器中生成一个服务的代理对象,在action或者Controller中直接调用service的方法即可。
消息中间件acitveMQ的作用、原理?几种模式,每种的特点及使用问题?MQ发送消息失败怎么办?
21、浏览器跨域问题
22、海量数据的存储问题
使用非关系性数据库快速查询与插入数据,使用数据库分片将数据由一个数据库分散到多个数据库中,使用数据库中间件mycat读写分离快速定位数据所在的库